Compare commits
12 Commits
7883cfa908
...
renovate/a
Author | SHA1 | Date | |
---|---|---|---|
582b3c3e87 | |||
b9034de8a0 | |||
d0227cc9e5 | |||
f54b1b870a | |||
b3d6862195 | |||
045fc90364
|
|||
91fe491751
|
|||
e9e80abad0
|
|||
1fda414e05
|
|||
28a1d09974
|
|||
dca625af31
|
|||
bd927840d6 |
2
.env
2
.env
@@ -3,3 +3,5 @@ FOREST_S3_BUCKET=forest
|
|||||||
FOREST_S3_REGION=eu-west-1
|
FOREST_S3_REGION=eu-west-1
|
||||||
FOREST_S3_USER=forestadmin
|
FOREST_S3_USER=forestadmin
|
||||||
FOREST_S3_PASSWORD=forestadmin
|
FOREST_S3_PASSWORD=forestadmin
|
||||||
|
|
||||||
|
FOREST_LOG_LEVEL=forest=trace
|
||||||
|
41
.forest-ci/workflows/build.yaml
Normal file
41
.forest-ci/workflows/build.yaml
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
name: Build Forest
|
||||||
|
|
||||||
|
on:
|
||||||
|
- push
|
||||||
|
- pull_request
|
||||||
|
|
||||||
|
env:
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
RUST_BACKTRACE: 1
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
env:
|
||||||
|
RUSTFLAGS: -D warnings
|
||||||
|
timeout_minutes: 30
|
||||||
|
steps:
|
||||||
|
|
||||||
|
- name: Build application
|
||||||
|
uses: rustlang/rust:nightly
|
||||||
|
run:
|
||||||
|
- export SQLX_OFFLINE=true
|
||||||
|
- cargo build --release
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
uses: rustlang/rust:nightly
|
||||||
|
run:
|
||||||
|
- cargo test
|
||||||
|
|
||||||
|
- name: Check code formatting
|
||||||
|
uses: rustlang/rust:nightly
|
||||||
|
run:
|
||||||
|
- cargo fmt -- --check
|
||||||
|
continue_on_error: true
|
||||||
|
|
||||||
|
- name: Run clippy lints
|
||||||
|
uses: rustlang/rust:nightly
|
||||||
|
run:
|
||||||
|
- rustup component add clippy
|
||||||
|
- cargo clippy -- -D warnings
|
||||||
|
continue_on_error: true
|
||||||
|
|
238
Cargo.lock
generated
238
Cargo.lock
generated
@@ -78,9 +78,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.96"
|
version = "1.0.98"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6b964d184e89d9b6b67dd2715bc8e74cf3107fb2b529990c90cf517326150bf4"
|
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
@@ -124,6 +124,12 @@ dependencies = [
|
|||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bumpalo"
|
||||||
|
version = "3.19.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.10.0"
|
version = "1.10.0"
|
||||||
@@ -138,9 +144,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.31"
|
version = "4.5.41"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767"
|
checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap_builder",
|
"clap_builder",
|
||||||
"clap_derive",
|
"clap_derive",
|
||||||
@@ -148,9 +154,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_builder"
|
name = "clap_builder"
|
||||||
version = "4.5.31"
|
version = "4.5.41"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863"
|
checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstream",
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
@@ -160,9 +166,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_derive"
|
name = "clap_derive"
|
||||||
version = "4.5.28"
|
version = "4.5.41"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed"
|
checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@@ -212,15 +218,6 @@ dependencies = [
|
|||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "deranged"
|
|
||||||
version = "0.3.11"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
|
|
||||||
dependencies = [
|
|
||||||
"powerfmt",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "digest"
|
name = "digest"
|
||||||
version = "0.10.7"
|
version = "0.10.7"
|
||||||
@@ -468,6 +465,17 @@ dependencies = [
|
|||||||
"icu_properties",
|
"icu_properties",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "io-uring"
|
||||||
|
version = "0.7.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "is_terminal_polyfill"
|
name = "is_terminal_polyfill"
|
||||||
version = "1.70.1"
|
version = "1.70.1"
|
||||||
@@ -480,6 +488,40 @@ version = "1.0.14"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
|
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jiff"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49"
|
||||||
|
dependencies = [
|
||||||
|
"jiff-static",
|
||||||
|
"log",
|
||||||
|
"portable-atomic",
|
||||||
|
"portable-atomic-util",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jiff-static"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "js-sys"
|
||||||
|
version = "0.3.77"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kdl"
|
name = "kdl"
|
||||||
version = "6.3.4"
|
version = "6.3.4"
|
||||||
@@ -576,9 +618,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "minijinja"
|
name = "minijinja"
|
||||||
version = "2.7.0"
|
version = "2.11.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cff7b8df5e85e30b87c2b0b3f58ba3a87b68e133738bf512a7713769326dbca9"
|
checksum = "4e60ac08614cc09062820e51d5d94c2fce16b94ea4e5003bb81b99a95f84e876"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
@@ -646,12 +688,6 @@ dependencies = [
|
|||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "num-conv"
|
|
||||||
version = "0.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-integer"
|
name = "num-integer"
|
||||||
version = "0.1.46"
|
version = "0.1.46"
|
||||||
@@ -749,10 +785,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "powerfmt"
|
name = "portable-atomic"
|
||||||
version = "0.2.0"
|
version = "1.11.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "portable-atomic-util"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
|
||||||
|
dependencies = [
|
||||||
|
"portable-atomic",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
@@ -765,9 +810,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quick-xml"
|
name = "quick-xml"
|
||||||
version = "0.37.2"
|
version = "0.38.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "165859e9e55f79d67b96c5d96f4e88b6f2695a1972849c15a6a3f5c59fc2c003"
|
checksum = "8927b0664f5c5a98265138b7e3f90aa19a6b21353182469ace36d4ac527b7b1b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -842,20 +887,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
|
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rusty-s3"
|
name = "rustversion"
|
||||||
version = "0.7.0"
|
version = "1.0.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8f51a5a6b15f25d3e10c068039ee13befb6110fcb36c2b26317bcbdc23484d96"
|
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rusty-s3"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2c27352e274b4aa598516962209bc0bae27efbb2d88d60995ad7f9862b020b0d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"hmac",
|
"hmac",
|
||||||
|
"jiff",
|
||||||
"md-5",
|
"md-5",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"quick-xml",
|
"quick-xml",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sha2",
|
"sha2",
|
||||||
"time",
|
|
||||||
"url",
|
"url",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
@@ -883,18 +934,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.218"
|
version = "1.0.219"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
|
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.218"
|
version = "1.0.219"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
|
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -903,9 +954,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.139"
|
version = "1.0.141"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6"
|
checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"memchr",
|
"memchr",
|
||||||
@@ -942,6 +993,12 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "slab"
|
||||||
|
version = "0.4.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
version = "1.14.0"
|
version = "1.14.0"
|
||||||
@@ -1028,37 +1085,6 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "time"
|
|
||||||
version = "0.3.37"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
|
|
||||||
dependencies = [
|
|
||||||
"deranged",
|
|
||||||
"itoa",
|
|
||||||
"num-conv",
|
|
||||||
"powerfmt",
|
|
||||||
"serde",
|
|
||||||
"time-core",
|
|
||||||
"time-macros",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "time-core"
|
|
||||||
version = "0.1.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "time-macros"
|
|
||||||
version = "0.2.19"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de"
|
|
||||||
dependencies = [
|
|
||||||
"num-conv",
|
|
||||||
"time-core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tinystr"
|
name = "tinystr"
|
||||||
version = "0.7.6"
|
version = "0.7.6"
|
||||||
@@ -1071,17 +1097,19 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.43.0"
|
version = "1.46.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e"
|
checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"backtrace",
|
"backtrace",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
"io-uring",
|
||||||
"libc",
|
"libc",
|
||||||
"mio",
|
"mio",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"signal-hook-registry",
|
"signal-hook-registry",
|
||||||
|
"slab",
|
||||||
"socket2",
|
"socket2",
|
||||||
"tokio-macros",
|
"tokio-macros",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
@@ -1209,11 +1237,13 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.15.1"
|
version = "1.17.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e0f540e3240398cce6128b64ba83fdbdd86129c16a3aa1a3a252efd66eb3d587"
|
checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom",
|
"getrandom",
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1253,6 +1283,64 @@ dependencies = [
|
|||||||
"wit-bindgen-rt",
|
"wit-bindgen-rt",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen"
|
||||||
|
version = "0.2.100"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"once_cell",
|
||||||
|
"rustversion",
|
||||||
|
"wasm-bindgen-macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-backend"
|
||||||
|
version = "0.2.100"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"log",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro"
|
||||||
|
version = "0.2.100"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"wasm-bindgen-macro-support",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro-support"
|
||||||
|
version = "0.2.100"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wasm-bindgen-backend",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-shared"
|
||||||
|
version = "0.2.100"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
|
@@ -13,7 +13,7 @@ dotenvy.workspace = true
|
|||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
uuid.workspace = true
|
uuid.workspace = true
|
||||||
|
|
||||||
rusty-s3 = "0.7"
|
rusty-s3 = "0.8"
|
||||||
url = "2.5"
|
url = "2.5"
|
||||||
kdl = "6.3"
|
kdl = "6.3"
|
||||||
walkdir = "2.5"
|
walkdir = "2.5"
|
||||||
|
@@ -1,12 +1,13 @@
|
|||||||
use std::{net::SocketAddr, path::PathBuf};
|
use std::{net::SocketAddr, path::PathBuf};
|
||||||
|
|
||||||
|
use anyhow::Context as AnyContext;
|
||||||
use clap::{FromArgMatches, Parser, Subcommand, crate_authors, crate_description, crate_version};
|
use clap::{FromArgMatches, Parser, Subcommand, crate_authors, crate_description, crate_version};
|
||||||
use colored_json::ToColoredJson;
|
use colored_json::ToColoredJson;
|
||||||
use kdl::KdlDocument;
|
use kdl::KdlDocument;
|
||||||
use rusty_s3::{Bucket, Credentials, S3Action};
|
use rusty_s3::{Bucket, Credentials, S3Action};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
model::{Context, Plan, Project},
|
model::{Context, ForestFile, Plan, Project, WorkspaceProject},
|
||||||
plan_reconciler::PlanReconciler,
|
plan_reconciler::PlanReconciler,
|
||||||
state::SharedState,
|
state::SharedState,
|
||||||
};
|
};
|
||||||
@@ -47,6 +48,7 @@ fn get_root(include_run: bool) -> clap::Command {
|
|||||||
.author(crate_authors!())
|
.author(crate_authors!())
|
||||||
.version(crate_version!())
|
.version(crate_version!())
|
||||||
.about(crate_description!())
|
.about(crate_description!())
|
||||||
|
.ignore_errors(include_run)
|
||||||
.arg(
|
.arg(
|
||||||
clap::Arg::new("project_path")
|
clap::Arg::new("project_path")
|
||||||
.long("project-path")
|
.long("project-path")
|
||||||
@@ -55,9 +57,8 @@ fn get_root(include_run: bool) -> clap::Command {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if include_run {
|
if include_run {
|
||||||
root_cmd = root_cmd.subcommand(clap::Command::new("run").allow_external_subcommands(true));
|
root_cmd = root_cmd.subcommand(clap::Command::new("run").allow_external_subcommands(true))
|
||||||
}
|
}
|
||||||
|
|
||||||
Commands::augment_subcommands(root_cmd)
|
Commands::augment_subcommands(root_cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,86 +79,263 @@ pub async fn execute() -> anyhow::Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let project_file = tokio::fs::read_to_string(&project_file_path).await?;
|
let project_file = tokio::fs::read_to_string(&project_file_path).await?;
|
||||||
let project_doc: KdlDocument = project_file.parse()?;
|
let doc: KdlDocument = project_file.parse()?;
|
||||||
|
let project: ForestFile = doc.try_into()?;
|
||||||
|
|
||||||
let project: Project = project_doc.try_into()?;
|
match project {
|
||||||
tracing::trace!("found a project name: {}", project.name);
|
ForestFile::Workspace(workspace) => {
|
||||||
|
tracing::trace!("running as workspace");
|
||||||
|
|
||||||
let plan = if let Some(plan_file_path) = PlanReconciler::new()
|
// 1. For each member load the project
|
||||||
.reconcile(&project, &project_path)
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
let plan_file = tokio::fs::read_to_string(&plan_file_path).await?;
|
|
||||||
let plan_doc: KdlDocument = plan_file.parse()?;
|
|
||||||
|
|
||||||
let plan: Plan = plan_doc.try_into()?;
|
let mut workspace_members = Vec::new();
|
||||||
tracing::trace!("found a plan name: {}", project.name);
|
|
||||||
|
|
||||||
Some(plan)
|
for member in workspace.members {
|
||||||
} else {
|
let workspace_member_path = project_path.join(&member.path);
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let context = Context { project, plan };
|
let project_file_path = workspace_member_path.join("forest.kdl");
|
||||||
|
if !project_file_path.exists() {
|
||||||
|
anyhow::bail!(
|
||||||
|
"no 'forest.kdl' file was found at: {}",
|
||||||
|
workspace_member_path.display().to_string()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let matches = if matches.subcommand_matches("run").is_some() {
|
let project_file = tokio::fs::read_to_string(&project_file_path).await?;
|
||||||
tracing::debug!("run is called, building extra commands, rerunning the parser");
|
let doc: KdlDocument = project_file.parse()?;
|
||||||
let root = get_root(false);
|
let project: WorkspaceProject = doc.try_into().context(format!(
|
||||||
|
"workspace member: {} failed to parse",
|
||||||
|
&member.path
|
||||||
|
))?;
|
||||||
|
|
||||||
let root = run::Run::augment_command(root, &context);
|
workspace_members.push((workspace_member_path, project));
|
||||||
|
|
||||||
root.get_matches()
|
|
||||||
} else {
|
|
||||||
matches
|
|
||||||
};
|
|
||||||
|
|
||||||
match matches
|
|
||||||
.subcommand()
|
|
||||||
.expect("forest requires a command to be passed")
|
|
||||||
{
|
|
||||||
("run", args) => {
|
|
||||||
run::Run::execute(args, &project_path, &context).await?;
|
|
||||||
}
|
|
||||||
_ => match Commands::from_arg_matches(&matches).unwrap() {
|
|
||||||
Commands::Init {} => {
|
|
||||||
tracing::info!("initializing project");
|
|
||||||
tracing::trace!("found context: {:?}", context);
|
|
||||||
}
|
}
|
||||||
Commands::Info {} => {
|
|
||||||
let output = serde_json::to_string_pretty(&context)?;
|
// TODO: 1a (optional). Resolve dependencies
|
||||||
println!("{}", output.to_colored_json_auto().unwrap_or(output));
|
// 2. Reconcile plans
|
||||||
}
|
|
||||||
Commands::Template(template) => {
|
let mut member_contexts = Vec::new();
|
||||||
template.execute(&project_path, &context).await?;
|
|
||||||
}
|
for (member_path, member) in &workspace_members {
|
||||||
Commands::Serve {
|
match member {
|
||||||
s3_endpoint,
|
WorkspaceProject::Plan(_plan) => {
|
||||||
s3_bucket,
|
tracing::warn!("skipping reconcile for plans for now")
|
||||||
s3_region,
|
}
|
||||||
s3_user,
|
WorkspaceProject::Project(project) => {
|
||||||
s3_password,
|
let plan = if let Some(plan_file_path) = PlanReconciler::new()
|
||||||
..
|
.reconcile(
|
||||||
} => {
|
project,
|
||||||
tracing::info!("Starting server");
|
member_path,
|
||||||
let creds = Credentials::new(s3_user, s3_password);
|
Some(workspace_members.as_ref()),
|
||||||
let bucket = Bucket::new(
|
Some(&project_path),
|
||||||
url::Url::parse(&s3_endpoint)?,
|
)
|
||||||
rusty_s3::UrlStyle::Path,
|
.await?
|
||||||
s3_bucket,
|
{
|
||||||
s3_region,
|
let plan_file = tokio::fs::read_to_string(&plan_file_path)
|
||||||
)?;
|
.await
|
||||||
let put_object = bucket.put_object(Some(&creds), "some-object");
|
.context(format!(
|
||||||
let _url = put_object.sign(std::time::Duration::from_secs(30));
|
"failed to read file at: {}",
|
||||||
let _state = SharedState::new().await?;
|
project_path.to_string_lossy()
|
||||||
}
|
))?;
|
||||||
Commands::Clean {} => {
|
let plan_doc: KdlDocument = plan_file.parse()?;
|
||||||
let forest_path = project_path.join(".forest");
|
|
||||||
if forest_path.exists() {
|
let plan: Plan = plan_doc.try_into()?;
|
||||||
tokio::fs::remove_dir_all(forest_path).await?;
|
tracing::trace!("found a plan name: {}", project.name);
|
||||||
tracing::info!("removed .forest");
|
|
||||||
|
Some(plan)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let context = Context {
|
||||||
|
project: project.clone(),
|
||||||
|
plan,
|
||||||
|
};
|
||||||
|
member_contexts.push((member_path, context));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
tracing::debug!("run is called, building extra commands, rerunning the parser");
|
||||||
|
let mut run_cmd = clap::Command::new("run").subcommand_required(true);
|
||||||
|
|
||||||
|
// 3. Provide context and aggregated commands for projects
|
||||||
|
for (_, context) in &member_contexts {
|
||||||
|
let commands = run::Run::augment_workspace_command(context, &context.project.name);
|
||||||
|
run_cmd = run_cmd.subcommands(commands);
|
||||||
|
}
|
||||||
|
|
||||||
|
run_cmd =
|
||||||
|
run_cmd.subcommand(clap::Command::new("all").allow_external_subcommands(true));
|
||||||
|
|
||||||
|
let mut root = get_root(false).subcommand(run_cmd);
|
||||||
|
let matches = root.get_matches_mut();
|
||||||
|
|
||||||
|
if matches.subcommand().is_none() {
|
||||||
|
root.print_help()?;
|
||||||
|
anyhow::bail!("failed to find command");
|
||||||
|
}
|
||||||
|
|
||||||
|
match matches
|
||||||
|
.subcommand()
|
||||||
|
.expect("forest requires a command to be passed")
|
||||||
|
{
|
||||||
|
("run", args) => {
|
||||||
|
let (run_args, args) = args.subcommand().expect("run must have subcommands");
|
||||||
|
|
||||||
|
match run_args {
|
||||||
|
"all" => {
|
||||||
|
let (all_cmd, _args) = args
|
||||||
|
.subcommand()
|
||||||
|
.expect("to be able to get a subcommand (todo: might not work)");
|
||||||
|
|
||||||
|
for (member_path, context) in member_contexts {
|
||||||
|
run::Run::execute_command_if_exists(all_cmd, member_path, &context)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let (project_name, command) = run_args
|
||||||
|
.split_once("::")
|
||||||
|
.expect("commands to always be pairs for workspaces");
|
||||||
|
|
||||||
|
let mut found_context = false;
|
||||||
|
for (member_path, context) in &member_contexts {
|
||||||
|
if project_name == context.project.name {
|
||||||
|
run::Run::execute_command(command, member_path, context)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
found_context = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found_context {
|
||||||
|
anyhow::bail!("no matching context was found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => match Commands::from_arg_matches(&matches).unwrap() {
|
||||||
|
Commands::Init {} => {
|
||||||
|
tracing::info!("initializing project");
|
||||||
|
}
|
||||||
|
Commands::Info {} => {
|
||||||
|
let output = serde_json::to_string_pretty(&member_contexts)?;
|
||||||
|
println!("{}", output.to_colored_json_auto().unwrap_or(output));
|
||||||
|
}
|
||||||
|
Commands::Template(template) => {
|
||||||
|
//template.execute(&project_path, &context).await?;
|
||||||
|
}
|
||||||
|
Commands::Serve {
|
||||||
|
s3_endpoint,
|
||||||
|
s3_bucket,
|
||||||
|
s3_region,
|
||||||
|
s3_user,
|
||||||
|
s3_password,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
tracing::info!("Starting server");
|
||||||
|
let creds = Credentials::new(s3_user, s3_password);
|
||||||
|
let bucket = Bucket::new(
|
||||||
|
url::Url::parse(&s3_endpoint)?,
|
||||||
|
rusty_s3::UrlStyle::Path,
|
||||||
|
s3_bucket,
|
||||||
|
s3_region,
|
||||||
|
)?;
|
||||||
|
let put_object = bucket.put_object(Some(&creds), "some-object");
|
||||||
|
let _url = put_object.sign(std::time::Duration::from_secs(30));
|
||||||
|
let _state = SharedState::new().await?;
|
||||||
|
}
|
||||||
|
Commands::Clean {} => {
|
||||||
|
todo!();
|
||||||
|
// let forest_path = project_path.join(".forest");
|
||||||
|
// if forest_path.exists() {
|
||||||
|
// tokio::fs::remove_dir_all(forest_path).await?;
|
||||||
|
// tracing::info!("removed .forest");
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ForestFile::Project(project) => {
|
||||||
|
tracing::trace!("found a project name: {}", project.name);
|
||||||
|
|
||||||
|
let plan = if let Some(plan_file_path) = PlanReconciler::new()
|
||||||
|
.reconcile(&project, &project_path, None, None)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
let plan_file = tokio::fs::read_to_string(&plan_file_path).await?;
|
||||||
|
let plan_doc: KdlDocument = plan_file.parse()?;
|
||||||
|
|
||||||
|
let plan: Plan = plan_doc.try_into()?;
|
||||||
|
tracing::trace!("found a plan name: {}", project.name);
|
||||||
|
|
||||||
|
Some(plan)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let context = Context { project, plan };
|
||||||
|
|
||||||
|
let matches = if matches.subcommand_matches("run").is_some() {
|
||||||
|
tracing::debug!("run is called, building extra commands, rerunning the parser");
|
||||||
|
let root = get_root(false);
|
||||||
|
|
||||||
|
let run_cmd = run::Run::augment_command(&context);
|
||||||
|
root.subcommand(run_cmd).get_matches()
|
||||||
|
} else {
|
||||||
|
matches
|
||||||
|
};
|
||||||
|
|
||||||
|
match matches
|
||||||
|
.subcommand()
|
||||||
|
.expect("forest requires a command to be passed")
|
||||||
|
{
|
||||||
|
("run", args) => {
|
||||||
|
run::Run::execute(args, &project_path, &context).await?;
|
||||||
|
}
|
||||||
|
_ => match Commands::from_arg_matches(&matches).unwrap() {
|
||||||
|
Commands::Init {} => {
|
||||||
|
tracing::info!("initializing project");
|
||||||
|
tracing::trace!("found context: {:?}", context);
|
||||||
|
}
|
||||||
|
Commands::Info {} => {
|
||||||
|
let output = serde_json::to_string_pretty(&context)?;
|
||||||
|
println!("{}", output.to_colored_json_auto().unwrap_or(output));
|
||||||
|
}
|
||||||
|
Commands::Template(template) => {
|
||||||
|
template.execute(&project_path, &context).await?;
|
||||||
|
}
|
||||||
|
Commands::Serve {
|
||||||
|
s3_endpoint,
|
||||||
|
s3_bucket,
|
||||||
|
s3_region,
|
||||||
|
s3_user,
|
||||||
|
s3_password,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
tracing::info!("Starting server");
|
||||||
|
let creds = Credentials::new(s3_user, s3_password);
|
||||||
|
let bucket = Bucket::new(
|
||||||
|
url::Url::parse(&s3_endpoint)?,
|
||||||
|
rusty_s3::UrlStyle::Path,
|
||||||
|
s3_bucket,
|
||||||
|
s3_region,
|
||||||
|
)?;
|
||||||
|
let put_object = bucket.put_object(Some(&creds), "some-object");
|
||||||
|
let _url = put_object.sign(std::time::Duration::from_secs(30));
|
||||||
|
let _state = SharedState::new().await?;
|
||||||
|
}
|
||||||
|
Commands::Clean {} => {
|
||||||
|
let forest_path = project_path.join(".forest");
|
||||||
|
if forest_path.exists() {
|
||||||
|
tokio::fs::remove_dir_all(forest_path).await?;
|
||||||
|
tracing::info!("removed .forest");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
use std::{collections::BTreeMap, path::Path};
|
use std::path::Path;
|
||||||
|
|
||||||
use crate::{model::Context, script::ScriptExecutor};
|
use crate::{model::Context, script::ScriptExecutor};
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@ use crate::{model::Context, script::ScriptExecutor};
|
|||||||
// create a new sub command that encapsulates all the run complexities
|
// create a new sub command that encapsulates all the run complexities
|
||||||
pub struct Run {}
|
pub struct Run {}
|
||||||
impl Run {
|
impl Run {
|
||||||
pub fn augment_command(root: clap::Command, ctx: &Context) -> clap::Command {
|
pub fn augment_command(ctx: &Context) -> clap::Command {
|
||||||
let mut run_cmd = clap::Command::new("run")
|
let mut run_cmd = clap::Command::new("run")
|
||||||
.subcommand_required(true)
|
.subcommand_required(true)
|
||||||
.about("runs any kind of script from either the project or plan");
|
.about("runs any kind of script from either the project or plan");
|
||||||
@@ -37,7 +37,37 @@ impl Run {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
root.subcommand(run_cmd)
|
run_cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn augment_workspace_command(ctx: &Context, prefix: &str) -> Vec<clap::Command> {
|
||||||
|
let mut commands = Vec::new();
|
||||||
|
if let Some(scripts) = &ctx.project.scripts {
|
||||||
|
for name in scripts.items.keys() {
|
||||||
|
let cmd = clap::Command::new(format!("{prefix}::{name}"));
|
||||||
|
commands.push(cmd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(plan) = &ctx.plan {
|
||||||
|
if let Some(scripts) = &plan.scripts {
|
||||||
|
let existing_cmds = commands
|
||||||
|
.iter()
|
||||||
|
.map(|s| format!("{prefix}::{}", s.get_name()))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
for name in scripts.items.keys() {
|
||||||
|
if existing_cmds.contains(name) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cmd = clap::Command::new(format!("{prefix}::{name}"));
|
||||||
|
commands.push(cmd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
commands
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn execute(
|
pub async fn execute(
|
||||||
@@ -73,4 +103,64 @@ impl Run {
|
|||||||
|
|
||||||
anyhow::bail!("no scripts were found for command: {}", name)
|
anyhow::bail!("no scripts were found for command: {}", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn execute_command(
|
||||||
|
command: &str,
|
||||||
|
project_path: &Path,
|
||||||
|
ctx: &Context,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
if let Some(scripts_ctx) = &ctx.project.scripts {
|
||||||
|
if let Some(script_ctx) = scripts_ctx.items.get(command) {
|
||||||
|
ScriptExecutor::new(project_path.into(), ctx.clone())
|
||||||
|
.run(script_ctx, command)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(plan) = &ctx.plan {
|
||||||
|
if let Some(scripts_ctx) = &plan.scripts {
|
||||||
|
if let Some(script_ctx) = scripts_ctx.items.get(command) {
|
||||||
|
ScriptExecutor::new(project_path.into(), ctx.clone())
|
||||||
|
.run(script_ctx, command)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
anyhow::bail!("no scripts were found for command: {}", command)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn execute_command_if_exists(
|
||||||
|
command: &str,
|
||||||
|
project_path: &Path,
|
||||||
|
ctx: &Context,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
if let Some(scripts_ctx) = &ctx.project.scripts {
|
||||||
|
if let Some(script_ctx) = scripts_ctx.items.get(command) {
|
||||||
|
ScriptExecutor::new(project_path.into(), ctx.clone())
|
||||||
|
.run(script_ctx, command)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(plan) = &ctx.plan {
|
||||||
|
if let Some(scripts_ctx) = &plan.scripts {
|
||||||
|
if let Some(script_ctx) = scripts_ctx.items.get(command) {
|
||||||
|
ScriptExecutor::new(project_path.into(), ctx.clone())
|
||||||
|
.run(script_ctx, command)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
use std::{collections::BTreeMap, fmt::Debug, path::PathBuf};
|
use std::{collections::BTreeMap, fmt::Debug, path::PathBuf};
|
||||||
|
|
||||||
use colored_json::Paint;
|
|
||||||
use kdl::{KdlDocument, KdlNode, KdlValue};
|
use kdl::{KdlDocument, KdlNode, KdlValue};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
@@ -13,7 +12,9 @@ pub struct Context {
|
|||||||
#[derive(Debug, Clone, Serialize)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
pub struct Plan {
|
pub struct Plan {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub templates: Option<Templates>,
|
pub templates: Option<Templates>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub scripts: Option<Scripts>,
|
pub scripts: Option<Scripts>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,6 +56,7 @@ impl TryFrom<KdlDocument> for Plan {
|
|||||||
pub enum ProjectPlan {
|
pub enum ProjectPlan {
|
||||||
Local { path: PathBuf },
|
Local { path: PathBuf },
|
||||||
Git { url: String, path: Option<PathBuf> },
|
Git { url: String, path: Option<PathBuf> },
|
||||||
|
Workspace { name: String },
|
||||||
NoPlan,
|
NoPlan,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,6 +96,17 @@ impl TryFrom<&KdlNode> for ProjectPlan {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(workspace) = children.get_arg("workspace") {
|
||||||
|
return Ok(Self::Workspace {
|
||||||
|
name: workspace
|
||||||
|
.as_string()
|
||||||
|
.map(|w| w.to_string())
|
||||||
|
.ok_or(anyhow::anyhow!(
|
||||||
|
"workspace requires a project name in the same project"
|
||||||
|
))?,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Ok(Self::NoPlan)
|
Ok(Self::NoPlan)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -164,6 +177,12 @@ pub struct Global {
|
|||||||
items: BTreeMap<String, GlobalVariable>,
|
items: BTreeMap<String, GlobalVariable>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Global {
|
||||||
|
fn is_empty(&self) -> bool {
|
||||||
|
self.items.is_empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<&Global> for minijinja::Value {
|
impl From<&Global> for minijinja::Value {
|
||||||
fn from(value: &Global) -> Self {
|
fn from(value: &Global) -> Self {
|
||||||
Self::from_serialize(&value.items)
|
Self::from_serialize(&value.items)
|
||||||
@@ -312,10 +331,16 @@ impl TryFrom<&KdlNode> for Scripts {
|
|||||||
#[derive(Debug, Clone, Serialize)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
pub struct Project {
|
pub struct Project {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub plan: Option<ProjectPlan>,
|
pub plan: Option<ProjectPlan>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Global::is_empty")]
|
||||||
pub global: Global,
|
pub global: Global,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub templates: Option<Templates>,
|
pub templates: Option<Templates>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub scripts: Option<Scripts>,
|
pub scripts: Option<Scripts>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -372,3 +397,111 @@ impl TryFrom<KdlDocument> for Project {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
pub struct WorkspaceMember {
|
||||||
|
pub path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&kdl::KdlNode> for WorkspaceMember {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn try_from(value: &kdl::KdlNode) -> Result<Self, Self::Error> {
|
||||||
|
Ok(Self {
|
||||||
|
path: value
|
||||||
|
.entries()
|
||||||
|
.first()
|
||||||
|
.ok_or(anyhow::anyhow!(
|
||||||
|
"is supposed to have a path `member ./some-path`"
|
||||||
|
))?
|
||||||
|
.value()
|
||||||
|
.as_string()
|
||||||
|
.ok_or(anyhow::anyhow!("value is required to be a string"))?
|
||||||
|
.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
pub struct Workspace {
|
||||||
|
pub members: Vec<WorkspaceMember>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<KdlDocument> for Workspace {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn try_from(value: KdlDocument) -> Result<Self, Self::Error> {
|
||||||
|
let workspace = value
|
||||||
|
.get("workspace")
|
||||||
|
.expect("to have a workspace at this point")
|
||||||
|
.children()
|
||||||
|
.ok_or(anyhow::anyhow!("workspace to be a section"))?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
members: workspace
|
||||||
|
.get("members")
|
||||||
|
.ok_or(anyhow::anyhow!(
|
||||||
|
"a members section is required for a workspace"
|
||||||
|
))?
|
||||||
|
.children()
|
||||||
|
.ok_or(anyhow::anyhow!("a members is required to have children"))?
|
||||||
|
.nodes()
|
||||||
|
.iter()
|
||||||
|
.map(|m| m.try_into())
|
||||||
|
.collect::<anyhow::Result<Vec<_>>>()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
pub enum ForestFile {
|
||||||
|
Workspace(Workspace),
|
||||||
|
Project(Project),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<KdlDocument> for ForestFile {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn try_from(value: KdlDocument) -> Result<Self, Self::Error> {
|
||||||
|
if value.get("workspace").is_some() && value.get("project").is_some() {
|
||||||
|
anyhow::bail!("a forest.kdl file cannot contain both a workspace and project")
|
||||||
|
}
|
||||||
|
|
||||||
|
if value.get("project").is_some() {
|
||||||
|
return Ok(Self::Project(value.try_into()?));
|
||||||
|
}
|
||||||
|
|
||||||
|
if value.get("workspace").is_some() {
|
||||||
|
return Ok(Self::Workspace(value.try_into()?));
|
||||||
|
}
|
||||||
|
|
||||||
|
anyhow::bail!("a forest.kdl file must be either a project, workspace or plan")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub enum WorkspaceProject {
|
||||||
|
Plan(Plan),
|
||||||
|
Project(Project),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<KdlDocument> for WorkspaceProject {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn try_from(value: KdlDocument) -> Result<Self, Self::Error> {
|
||||||
|
if value.get("plan").is_some() && value.get("project").is_some() {
|
||||||
|
anyhow::bail!("a forest.kdl file cannot contain both a plan and project")
|
||||||
|
}
|
||||||
|
|
||||||
|
if value.get("project").is_some() {
|
||||||
|
return Ok(Self::Project(value.try_into()?));
|
||||||
|
}
|
||||||
|
|
||||||
|
if value.get("plan").is_some() {
|
||||||
|
return Ok(Self::Plan(value.try_into()?));
|
||||||
|
}
|
||||||
|
|
||||||
|
anyhow::bail!("a forest.kdl file must be either a project, workspace or plan")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -2,7 +2,7 @@ use std::path::{Path, PathBuf};
|
|||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
|
|
||||||
use crate::model::Project;
|
use crate::model::{Project, WorkspaceProject};
|
||||||
|
|
||||||
pub mod git;
|
pub mod git;
|
||||||
pub mod local;
|
pub mod local;
|
||||||
@@ -21,6 +21,8 @@ impl PlanReconciler {
|
|||||||
&self,
|
&self,
|
||||||
project: &Project,
|
project: &Project,
|
||||||
destination: &Path,
|
destination: &Path,
|
||||||
|
workspace_members: Option<&Vec<(PathBuf, WorkspaceProject)>>,
|
||||||
|
workspace_root: Option<&Path>,
|
||||||
) -> anyhow::Result<Option<PathBuf>> {
|
) -> anyhow::Result<Option<PathBuf>> {
|
||||||
tracing::info!("reconciling project");
|
tracing::info!("reconciling project");
|
||||||
if project.plan.is_none() {
|
if project.plan.is_none() {
|
||||||
@@ -55,6 +57,21 @@ impl PlanReconciler {
|
|||||||
crate::model::ProjectPlan::Git { url, path } => {
|
crate::model::ProjectPlan::Git { url, path } => {
|
||||||
git::reconcile(url, path, &plan_dir).await?;
|
git::reconcile(url, path, &plan_dir).await?;
|
||||||
}
|
}
|
||||||
|
crate::model::ProjectPlan::Workspace { name } => {
|
||||||
|
let workspace_root = workspace_root.expect("to have workspace root available");
|
||||||
|
|
||||||
|
if let Some(workspace_members) = workspace_members {
|
||||||
|
for (member_path, member) in workspace_members {
|
||||||
|
if let WorkspaceProject::Plan(plan) = member {
|
||||||
|
if &plan.name == name {
|
||||||
|
tracing::debug!("found workspace project: {}", name);
|
||||||
|
local::reconcile(&workspace_root.join(member_path), &plan_dir)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
crate::model::ProjectPlan::NoPlan => {
|
crate::model::ProjectPlan::NoPlan => {
|
||||||
tracing::debug!("no plan, returning");
|
tracing::debug!("no plan, returning");
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
|
@@ -29,7 +29,7 @@ impl ShellExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut cmd = tokio::process::Command::new(&script_path);
|
let mut cmd = tokio::process::Command::new(&script_path);
|
||||||
let cmd = cmd.current_dir(path);
|
let cmd = cmd.current_dir(&self.root.project_path);
|
||||||
cmd.stdin(Stdio::inherit());
|
cmd.stdin(Stdio::inherit());
|
||||||
cmd.stdout(Stdio::inherit());
|
cmd.stdout(Stdio::inherit());
|
||||||
cmd.stderr(Stdio::inherit());
|
cmd.stderr(Stdio::inherit());
|
||||||
@@ -53,6 +53,7 @@ impl ShellExecutor {
|
|||||||
|
|
||||||
fn get_path(&self) -> PathBuf {
|
fn get_path(&self) -> PathBuf {
|
||||||
match self.ty {
|
match self.ty {
|
||||||
|
//ShellType::Plan => self.root.project_path.join(".forest").join("plan"),
|
||||||
ShellType::Plan => self.root.project_path.join(".forest").join("plan"),
|
ShellType::Plan => self.root.project_path.join(".forest").join("plan"),
|
||||||
ShellType::Project => self.root.project_path.clone(),
|
ShellType::Project => self.root.project_path.clone(),
|
||||||
}
|
}
|
||||||
|
0
examples/workspace/components/flux/forest.kdl
Normal file
0
examples/workspace/components/flux/forest.kdl
Normal file
9
examples/workspace/forest.kdl
Normal file
9
examples/workspace/forest.kdl
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
workspace {
|
||||||
|
members {
|
||||||
|
member "projects/a"
|
||||||
|
member "projects/b"
|
||||||
|
member "plan/a"
|
||||||
|
member "plan/b"
|
||||||
|
// member "components/*"
|
||||||
|
}
|
||||||
|
}
|
7
examples/workspace/plan/a/forest.kdl
Normal file
7
examples/workspace/plan/a/forest.kdl
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
plan {
|
||||||
|
name a
|
||||||
|
|
||||||
|
scripts {
|
||||||
|
hello_plan type=shell {}
|
||||||
|
}
|
||||||
|
}
|
7
examples/workspace/plan/a/scripts/hello_plan.sh
Executable file
7
examples/workspace/plan/a/scripts/hello_plan.sh
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
#!/usr/bin/env zsh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "hello from plan"
|
||||||
|
|
||||||
|
echo "i am here: $PWD"
|
3
examples/workspace/plan/b/forest.kdl
Normal file
3
examples/workspace/plan/b/forest.kdl
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
plan {
|
||||||
|
name b
|
||||||
|
}
|
11
examples/workspace/projects/a/forest.kdl
Normal file
11
examples/workspace/projects/a/forest.kdl
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
project {
|
||||||
|
name a
|
||||||
|
|
||||||
|
plan {
|
||||||
|
workspace a
|
||||||
|
}
|
||||||
|
|
||||||
|
scripts {
|
||||||
|
hello type=shell {}
|
||||||
|
}
|
||||||
|
}
|
7
examples/workspace/projects/a/scripts/hello.sh
Executable file
7
examples/workspace/projects/a/scripts/hello.sh
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
#!/usr/bin/env zsh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "hello from a"
|
||||||
|
|
||||||
|
echo "i am here: $PWD"
|
7
examples/workspace/projects/b/forest.kdl
Normal file
7
examples/workspace/projects/b/forest.kdl
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
project {
|
||||||
|
name b
|
||||||
|
|
||||||
|
scripts {
|
||||||
|
hello type=shell {}
|
||||||
|
}
|
||||||
|
}
|
5
examples/workspace/projects/b/scripts/hello.sh
Executable file
5
examples/workspace/projects/b/scripts/hello.sh
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/usr/bin/env zsh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "hello from b"
|
Reference in New Issue
Block a user