diff --git a/.github/workflows/publish-to-pages.yaml b/.github/workflows/publish-to-pages.yaml new file mode 100644 index 000000000..0e30a5040 --- /dev/null +++ b/.github/workflows/publish-to-pages.yaml @@ -0,0 +1,41 @@ +name: publish to github pages + +on: + push: + branches: [ main ] + +# Cancel already running jobs +concurrency: + group: publish_to_pages_${{ github.head_ref }} + cancel-in-progress: true + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +jobs: + build: + strategy: + matrix: + include: + - name: Publish website to Github Pages + runner: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.setup_pages.outputs.base_url }} + name: ${{ matrix.name }} + runs-on: ${{ matrix.runner }} + steps: + - uses: actions/checkout@v4 + - name: Build website + run: cargo run -p website + - name: Setup Pages + uses: actions/configure-pages@v4 + - name: Upload pages + uses: actions/upload-pages-artifact@v3 + with: + path: 'website/root' + - name: Deploy pages + uses: actions/deploy-pages@v4 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7b69c6476..ba7ca1f2f 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,8 @@ /.project /docs/book /docs/mdbook_bin +/website/root +/website/shotover_repo_for_docs /shotover-proxy/build/packages /some_local_file /test-helpers/src/connection/kafka/node/node_modules \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 038791f70..4a38d639f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -679,6 +679,15 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "basic-toml" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "823388e228f614e9558c6804262db37960ec8821856535f5c3f59913140558f8" +dependencies = [ + "serde", +] + [[package]] name = "bcrypt-pbkdf" version = "0.10.0" @@ -2344,6 +2353,15 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "humansize" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" +dependencies = [ + "libm", +] + [[package]] name = "hyper" version = "0.14.31" @@ -2958,6 +2976,16 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -3978,6 +4006,47 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rinja" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dc4940d00595430b3d7d5a01f6222b5e5b51395d1120bdb28d854bb8abb17a5" +dependencies = [ + "humansize", + "itoa", + "percent-encoding", + "rinja_derive", +] + +[[package]] +name = "rinja_derive" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d9ed0146aef6e2825f1b1515f074510549efba38d71f4554eec32eb36ba18b" +dependencies = [ + "basic-toml", + "memchr", + "mime", + "mime_guess", + "proc-macro2", + "quote", + "rinja_parser", + "rustc-hash", + "serde", + "syn 2.0.87", +] + +[[package]] +name = "rinja_parser" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f9a866e2e00a7a1fb27e46e9e324a6f7c0e7edc4543cae1d38f4e4a100c610" +dependencies = [ + "memchr", + "nom", + "serde", +] + [[package]] name = "rsa" version = "0.9.6" @@ -5550,6 +5619,12 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "unicase" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" + [[package]] name = "unicode-ident" version = "1.0.13" @@ -5804,6 +5879,16 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "website" +version = "0.1.0" +dependencies = [ + "anyhow", + "rinja", + "semver", + "subprocess", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 366e90819..f355d903e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ members = [ "custom-transforms-example", "ec2-cargo", "windsock-cloud-docker", + "website", ] resolver = "2" diff --git a/docs/book.toml b/docs/book.toml index ab99a4289..49bcb55f2 100644 --- a/docs/book.toml +++ b/docs/book.toml @@ -7,9 +7,9 @@ title = "Shotover" [output.html] -[output.linkcheck] +#[output.linkcheck] # Should we check links on the internet? Enabling this option adds a # non-negligible performance impact -follow-web-links = false +#follow-web-links = false warning-policy = "error" diff --git a/docs/mdbook.sh b/docs/mdbook.sh index 0593744e9..60b12782c 100755 --- a/docs/mdbook.sh +++ b/docs/mdbook.sh @@ -6,8 +6,10 @@ set -e; set -u if [ ! -d "mdbook_bin" ]; then mkdir -p mdbook_bin pushd mdbook_bin - curl -L https://github.com/rust-lang/mdBook/releases/download/v0.4.13/mdbook-v0.4.13-x86_64-unknown-linux-gnu.tar.gz | tar xvz - wget https://github.com/Michael-F-Bryan/mdbook-linkcheck/releases/download/v0.7.6/mdbook-linkcheck.x86_64-unknown-linux-gnu.zip -O linkcheck.zip + #curl -L https://github.com/rust-lang/mdBook/releases/download/v0.4.13/mdbook-v0.4.13-x86_64-unknown-linux-gnu.tar.gz | tar xvz + curl -L https://github.com/rust-lang/mdBook/releases/download/v0.4.13/mdbook-v0.4.13-x86_64-apple-darwin-gnu.tar.gz | tar -xvz + chmod +x mdbook + curl https://github.com/Michael-F-Bryan/mdbook-linkcheck/releases/download/v0.7.6/mdbook-linkcheck.x86_64-unknown-linux-gnu.zip -o linkcheck.zip unzip linkcheck.zip chmod +x mdbook-linkcheck popd diff --git a/website/Cargo.toml b/website/Cargo.toml new file mode 100644 index 000000000..e456eea42 --- /dev/null +++ b/website/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "website" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" +publish = false + +[dependencies] +subprocess.workspace = true +anyhow.workspace = true +rinja = "0.3.5" +semver = "1.0.23" diff --git a/website/src/main.rs b/website/src/main.rs new file mode 100644 index 000000000..21973bc20 --- /dev/null +++ b/website/src/main.rs @@ -0,0 +1,123 @@ +use anyhow::{anyhow, Result}; +use rinja::Template; +use std::{path::Path, process::Command}; +use subprocess::{Exec, Redirection}; + +mod version_tags; + +fn main() { + // Set standard path to root of repo so this always runs in the same directory, regardless of where the user ran it from. + let current_dir = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap(); + std::env::set_current_dir(current_dir).unwrap(); + + println!("Ensuring mdbook is installed"); + // TODO: Once mdbook starts doing macos aarch64 binary releases we should download the release directly instead of compiling. + // https://github.com/rust-lang/mdBook/pull/2500 + if !Command::new("cargo") + .args(["install", "mdbook", "--version", "0.4.43"]) + .status() + .unwrap() + .success() + { + return; + } + + if let Err(err) = run(current_dir) { + println!("{err}"); + return; + } + + let out = current_dir + .join("website") + .join("root") + .join("docs") + .join("index.html"); + println!( + "Succesfully generated website at: file://{}", + out.to_str().unwrap() + ); +} + +fn run(current_dir: &Path) -> Result<()> { + let root = current_dir.join("website").join("root"); + run_command("docs", "mdbook", &["test"])?; + run_command( + "docs", + "mdbook", + &[ + "build", + "--dest-dir", + root.join("docs").join("main").to_str().unwrap(), + ], + )?; + + // ensure repo exists and is up to date + let repo_path = Path::new("website").join("shotover_repo_for_docs"); + if repo_path.exists() { + run_command(&repo_path, "git", &["fetch"])?; + } else { + run_command( + ".", + "git", + &[ + "clone", + "https://github.com/shotover/shotover-proxy", + repo_path.to_str().unwrap(), + ], + )?; + } + + let versions = version_tags::get_versions_of_repo(&repo_path); + + let docs = DocsTemplate { + versions: versions.iter().map(|x| x.semver_range.clone()).collect(), + }; + std::fs::write(root.join("docs").join("index.html"), docs.render().unwrap()).unwrap(); + + for version in &versions { + println!("Generating {}", version.tag); + run_command(&repo_path, "git", &["checkout", &version.tag])?; + + let temp_docs_dir = root.join("temp_docs_build"); + std::fs::remove_dir_all(&temp_docs_dir).ok(); + run_command( + repo_path.join("docs"), + "mdbook", + &["build", "--dest-dir", temp_docs_dir.to_str().unwrap()], + ) + .ok(); + + let version_docs_dest = root.join("docs").join(&version.semver_range); + std::fs::remove_dir_all(&version_docs_dest).ok(); + std::fs::rename(temp_docs_dir.join("html"), version_docs_dest).unwrap(); + } + + Ok(()) +} + +pub fn run_command(dir: impl AsRef, command: &str, args: &[&str]) -> Result { + let data = Exec::cmd(command) + .args(args) + .cwd(dir) + .stdout(Redirection::Pipe) + .stderr(Redirection::Merge) + .capture()?; + + if data.exit_status.success() { + Ok(data.stdout_str()) + } else { + Err(anyhow!( + "command {} {:?} exited with {:?} and output:\n{}", + command, + args, + data.exit_status, + data.stdout_str() + )) + } +} + +#[derive(Template)] +#[template(path = "docs.html")] +struct DocsTemplate { + versions: Vec, +} diff --git a/website/src/version_tags.rs b/website/src/version_tags.rs new file mode 100644 index 000000000..c7a668da5 --- /dev/null +++ b/website/src/version_tags.rs @@ -0,0 +1,63 @@ +use crate::run_command; +use semver::Version; +use std::path::Path; + +pub struct VersionTag { + /// e.g. "v0.1.1" + pub tag: String, + /// e.g. "0.1.x" + pub semver_range: String, + /// e.g. 0.1.1 + pub version: Version, +} + +impl VersionTag { + fn new(tag: &str) -> Option { + let version = Version::parse(tag.strip_prefix("v")?).ok()?; + + // ignore any prerelease or otherwise unusual tags + if !version.pre.is_empty() || !version.build.is_empty() { + return None; + } + + let semver_range = if version.major != 0 { + format!("{}.Y.Z", version.major) + } else { + format!("0.{}.Z", version.minor) + }; + Some(VersionTag { + tag: tag.to_owned(), + version, + semver_range, + }) + } +} + +pub fn get_versions_of_repo(repo_path: &Path) -> Vec { + let mut versions: Vec = run_command(repo_path, "git", &["tag"]) + .unwrap() + .lines() + .filter_map(VersionTag::new) + .filter(|x| x.version >= Version::new(0, 1, 0)) + .collect(); + + // reverse sort the list of versions + versions.sort_by_key(|x| x.version.clone()); + versions.reverse(); + + // Filter out any versions with duplicate semver range, keeping the first item. + // Keeping the first items leaves us with the most recent item due to the previous reverse sort. + let mut known = vec![]; + versions.retain(|version| { + let any_known = known + .iter() + .any(|known_range| &version.semver_range == known_range); + known.push(version.semver_range.clone()); + !any_known + }); + + // reverse order so that we are in the correct order again + versions.reverse(); + + versions +} diff --git a/website/templates/docs.html b/website/templates/docs.html new file mode 100644 index 000000000..5823c21b1 --- /dev/null +++ b/website/templates/docs.html @@ -0,0 +1,4 @@ +latest +{% for version in versions %} +{{ version }} +{% endfor %} \ No newline at end of file