diff --git a/.github/workflows/web.yml b/.github/workflows/web.yml index a76140ab..257530f6 100644 --- a/.github/workflows/web.yml +++ b/.github/workflows/web.yml @@ -19,6 +19,16 @@ jobs: toolchain: stable override: true + - name: Install mdbook + run: | + mkdir mdbook + curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.14/mdbook-v0.4.14-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=./mdbook + echo `pwd`/mdbook >> $GITHUB_PATH + - name: Run mdbook build + run: | + cd book + mdbook build + - name: Install oranda run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/oranda/releases/download/v0.0.1/oranda-v0.0.1-installer.sh | sh" diff --git a/Cargo.lock b/Cargo.lock index f6ce9ade..15f2b257 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1433,6 +1433,20 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64e9829a50b42bb782c1df523f78d332fe371b10c661e78b7a3c34b0198e9fac" +[[package]] +name = "insta" +version = "1.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea5b3894afe466b4bcf0388630fc15e11938a6074af0cd637c825ba2ec8a099" +dependencies = [ + "console", + "lazy_static", + "linked-hash-map", + "serde", + "similar", + "yaml-rust", +] + [[package]] name = "instant" version = "0.1.12" @@ -2002,6 +2016,7 @@ dependencies = [ "embed-resource", "humansize", "indicatif", + "insta", "miette", "nassun", "node-maintainer", @@ -2673,6 +2688,12 @@ dependencies = [ "libc", ] +[[package]] +name = "similar" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf" + [[package]] name = "slab" version = "0.4.8" diff --git a/Cargo.toml b/Cargo.toml index 7873d20d..24f4ae58 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -142,3 +142,12 @@ debug = false # The profile that 'cargo dist' will build with [profile.dist] inherits = "release" + +[dev-dependencies] +insta = { version = "1.28.0", features = ["yaml"] } + +[profile.dev.package.insta] +opt-level = 3 + +[profile.dev.package.similar] +opt-level = 3 diff --git a/book/.gitignore b/book/.gitignore new file mode 100644 index 00000000..7585238e --- /dev/null +++ b/book/.gitignore @@ -0,0 +1 @@ +book diff --git a/book/book.toml b/book/book.toml new file mode 100644 index 00000000..12f04b31 --- /dev/null +++ b/book/book.toml @@ -0,0 +1,6 @@ +[book] +authors = ["Kat Marchán"] +language = "en" +multilingual = false +src = "src" +title = "Orogene" diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md new file mode 100644 index 00000000..5f2ba697 --- /dev/null +++ b/book/src/SUMMARY.md @@ -0,0 +1,17 @@ +# Summary + +[Introduction](./introduction.md) + +--- + +# User Guide + +- [Configuration](./guide/configuration.md) + +--- + +# Commands + +- [ping](./commands/ping.md) +- [restore](./commands/restore.md) +- [view](./commands/view.md) diff --git a/book/src/commands/ping.md b/book/src/commands/ping.md new file mode 100644 index 00000000..116ff966 --- /dev/null +++ b/book/src/commands/ping.md @@ -0,0 +1 @@ +{{#include ../../../tests/snapshots/help__ping.snap:8:}} diff --git a/book/src/commands/restore.md b/book/src/commands/restore.md new file mode 100644 index 00000000..ce3c1c57 --- /dev/null +++ b/book/src/commands/restore.md @@ -0,0 +1 @@ +{{#include ../../../tests/snapshots/help__restore.snap:8:}} diff --git a/book/src/commands/view.md b/book/src/commands/view.md new file mode 100644 index 00000000..afe0094f --- /dev/null +++ b/book/src/commands/view.md @@ -0,0 +1 @@ +{{#include ../../../tests/snapshots/help__view.snap:8:}} diff --git a/book/src/guide/configuration.md b/book/src/guide/configuration.md new file mode 100644 index 00000000..58c79dcf --- /dev/null +++ b/book/src/guide/configuration.md @@ -0,0 +1,6 @@ +# Configuration + +Orogene currently uses toml files for (persistent) configuration. Soon, this +will be replaced with a [kdl](https://kdl.dev)-based configs. + +Until then, this page will stay mostly blank. diff --git a/book/src/introduction.md b/book/src/introduction.md new file mode 100644 index 00000000..4c16b511 --- /dev/null +++ b/book/src/introduction.md @@ -0,0 +1,5 @@ +# Introduction + +Orogene is a package manager for building up and writing `node_modules/` +directories, as well as a general toolkit for working with related packages +and APIs. diff --git a/oranda.json b/oranda.json index 2c63c085..6edaccda 100644 --- a/oranda.json +++ b/oranda.json @@ -1,2 +1,4 @@ { + "additional_pages": ["./CHANGELOG.md", "./CONTRIBUTING.md"], + "md_book": "./book/book" } diff --git a/src/lib.rs b/src/lib.rs index 5b911e9a..54d5bc25 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -94,7 +94,7 @@ use std::path::{Path, PathBuf}; use async_trait::async_trait; -use clap::{ArgMatches, CommandFactory, FromArgMatches as _, Parser, Subcommand}; +use clap::{ArgMatches, Args, CommandFactory, FromArgMatches as _, Parser, Subcommand}; use directories::ProjectDirs; use miette::{IntoDiagnostic, Result}; use oro_config::{OroConfig, OroConfigLayer, OroConfigOptions}; @@ -119,26 +119,26 @@ const MAX_RETAINED_LOGS: usize = 5; #[command(propagate_version = true)] pub struct Orogene { /// Package path to operate on. - #[arg(global = true, long)] + #[arg(help_heading = "Global Options", global = true, long)] root: Option, /// Registry used for unscoped packages. /// /// Defaults to https://registry.npmjs.org. - #[arg(global = true, long)] + #[arg(help_heading = "Global Options", global = true, long)] registry: Option, /// Location of disk cache. /// /// Default location varies by platform. - #[arg(global = true, long)] + #[arg(help_heading = "Global Options", global = true, long)] cache: Option, /// File to read configuration values from. /// /// When specified, global configuration loading is disabled and /// configuration values will only be read from this location. - #[arg(global = true, long)] + #[clap(help_heading = "Global Options", global = true, long)] config: Option, /// Log output level/directive. @@ -146,19 +146,19 @@ pub struct Orogene { /// Supports plain loglevels (off, error, warn, info, debug, trace) as /// well as more advanced directives in the format /// `target[span{field=value}]=level`. - #[clap(global = true, long)] + #[clap(help_heading = "Global Options", global = true, long)] loglevel: Option, /// Disable all output. - #[arg(global = true, long, short)] + #[arg(help_heading = "Global Options", global = true, long, short)] quiet: bool, /// Format output as JSON. - #[arg(global = true, long)] + #[arg(help_heading = "Global Options", global = true, long)] json: bool, /// Disable progress bar display. - #[arg(global = true, long)] + #[arg(help_heading = "Global Options", global = true, long)] no_progress: bool, #[command(subcommand)] @@ -351,6 +351,9 @@ pub enum OroCmd { /// Get information about a package. View(ViewCmd), + + #[clap(hide = true)] + HelpMarkdown(HelpMarkdownCmd), } #[async_trait] @@ -361,6 +364,7 @@ impl OroCommand for Orogene { OroCmd::Ping(cmd) => cmd.execute().await, OroCmd::Restore(cmd) => cmd.execute().await, OroCmd::View(cmd) => cmd.execute().await, + OroCmd::HelpMarkdown(cmd) => cmd.execute().await, } } } @@ -377,6 +381,84 @@ impl OroConfigLayer for Orogene { OroCmd::View(ref mut cmd) => { cmd.layer_config(args.subcommand_matches("view").unwrap(), conf) } + OroCmd::HelpMarkdown(ref mut cmd) => { + cmd.layer_config(args.subcommand_matches("help-markdown").unwrap(), conf) + } + } + } +} + +#[derive(Debug, Args, OroConfigLayer)] +pub struct HelpMarkdownCmd { + #[arg()] + command_name: String, +} + +#[async_trait] +impl OroCommand for HelpMarkdownCmd { + // Based on: + // https://github.com/axodotdev/cargo-dist/blob/b79a12e0942021ec304c5dcbf5e0cfcda3e6a4bb/cargo-dist/src/main.rs#L320 + async fn execute(self) -> Result<()> { + let mut app = Orogene::command(); + + // HACK: This is a hack that forces clap to print global options for + // subcommands when calling `write_long_help` on them. + let mut _help_buf = Vec::new(); + app.write_long_help(&mut _help_buf).into_diagnostic()?; + + for subcmd in app.get_subcommands_mut() { + let name = subcmd.get_name(); + + if name != self.command_name { + continue; + } + + println!("# oro {name}"); + println!(); + + let mut help_buf = Vec::new(); + subcmd.write_long_help(&mut help_buf).into_diagnostic()?; + let help = String::from_utf8(help_buf).into_diagnostic()?; + + for line in help.lines() { + if let Some(usage) = line.strip_prefix("Usage: ") { + println!("### Usage:"); + println!(); + println!("```"); + println!("oro {usage}"); + println!("```"); + continue; + } + + if let Some(heading) = line.strip_suffix(':') { + if !line.starts_with(' ') { + println!("### {heading}"); + println!(); + continue; + } + } + + let line = line.trim(); + + if line.starts_with("- ") { + } else if line.starts_with('-') || line.starts_with('<') { + println!("#### `{line}`"); + println!(); + continue; + } + + if line.starts_with('[') { + println!("\\{line} "); + continue; + } + + println!("{line}"); + } + + println!(); + + return Ok(()); } + Err(miette::miette!("Command not found: {self.command_name}")) } } diff --git a/tests/help.rs b/tests/help.rs new file mode 100644 index 00000000..3a550ec8 --- /dev/null +++ b/tests/help.rs @@ -0,0 +1,37 @@ +use std::process::{Command, Output, Stdio}; + +static BIN: &str = env!("CARGO_BIN_EXE_oro"); + +#[test] +fn ping_markdown() { + insta::assert_snapshot!("ping", sub_md("ping")); +} + +#[test] +fn restore_markdown() { + insta::assert_snapshot!("restore", sub_md("restore")); +} + +#[test] +fn view_markdown() { + insta::assert_snapshot!("view", sub_md("view")); +} + +fn sub_md(subcmd: &str) -> String { + let output = Command::new(BIN) + .arg("help-markdown") + .arg(subcmd) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output() + .expect("Failed to execute process"); + + assert!(output.status.success(), "{}", format_output(&output)); + format_output(&output) +} + +fn format_output(output: &Output) -> String { + let stdout = std::str::from_utf8(&output.stdout).unwrap(); + let stderr = std::str::from_utf8(&output.stderr).unwrap(); + format!("stderr:\n{stderr}\nstdout:\n{stdout}") +} diff --git a/tests/snapshots/help__ping.snap b/tests/snapshots/help__ping.snap new file mode 100644 index 00000000..a70ff490 --- /dev/null +++ b/tests/snapshots/help__ping.snap @@ -0,0 +1,70 @@ +--- +source: tests/help.rs +expression: "sub_md(\"ping\")" +--- +stderr: + +stdout: +# oro ping + +Ping the registry + +### Usage: + +``` +oro ping [OPTIONS] +``` + +### Options + +#### `-h, --help` + +Print help (see a summary with '-h') + +#### `-V, --version` + +Print version + +### Global Options + +#### `--root ` + +Package path to operate on + +#### `--registry ` + +Registry used for unscoped packages. + +Defaults to https://registry.npmjs.org. + +#### `--cache ` + +Location of disk cache. + +Default location varies by platform. + +#### `--config ` + +File to read configuration values from. + +When specified, global configuration loading is disabled and configuration values will only be read from this location. + +#### `--loglevel ` + +Log output level/directive. + +Supports plain loglevels (off, error, warn, info, debug, trace) as well as more advanced directives in the format `target[span{field=value}]=level`. + +#### `-q, --quiet` + +Disable all output + +#### `--json` + +Format output as JSON + +#### `--no-progress` + +Disable progress bar display + + diff --git a/tests/snapshots/help__restore.snap b/tests/snapshots/help__restore.snap new file mode 100644 index 00000000..faaa5793 --- /dev/null +++ b/tests/snapshots/help__restore.snap @@ -0,0 +1,88 @@ +--- +source: tests/help.rs +expression: "sub_md(\"restore\")" +--- +stderr: + +stdout: +# oro restore + +Resolves and extracts a node_modules/ tree + +### Usage: + +``` +oro restore [OPTIONS] +``` + +### Options + +#### `-p, --prefer-copy` + +Prefer copying files over hard linking them. + +On filesystems that don't support copy-on-write/reflinks (usually NTFS or ext4), orogene defaults to hard linking package files from a centralized cache. As such, this can cause global effects if a file inside a node_modules is modified, where other projects that have installed that same file will see those modifications. + +In order to prevent this, you can use this flag to force orogene to always copy files, at a performance cost. + +#### `-v, --validate` + +Validate the integrity of installed files. + +When this is true, orogene will verify all files extracted from the cache, as well as verify that any files in the existing `node_modules` are unmodified. If verification fails, the packages will be reinstalled. + +#### `--lockfile-only` + +Whether to skip restoring packages into `node_modules` and just resolve the tree and write the lockfile + +#### `-h, --help` + +Print help (see a summary with '-h') + +#### `-V, --version` + +Print version + +### Global Options + +#### `--root ` + +Package path to operate on + +#### `--registry ` + +Registry used for unscoped packages. + +Defaults to https://registry.npmjs.org. + +#### `--cache ` + +Location of disk cache. + +Default location varies by platform. + +#### `--config ` + +File to read configuration values from. + +When specified, global configuration loading is disabled and configuration values will only be read from this location. + +#### `--loglevel ` + +Log output level/directive. + +Supports plain loglevels (off, error, warn, info, debug, trace) as well as more advanced directives in the format `target[span{field=value}]=level`. + +#### `-q, --quiet` + +Disable all output + +#### `--json` + +Format output as JSON + +#### `--no-progress` + +Disable progress bar display + + diff --git a/tests/snapshots/help__view.snap b/tests/snapshots/help__view.snap new file mode 100644 index 00000000..cbe00ac0 --- /dev/null +++ b/tests/snapshots/help__view.snap @@ -0,0 +1,76 @@ +--- +source: tests/help.rs +expression: "sub_md(\"view\")" +--- +stderr: + +stdout: +# oro view + +Get information about a package + +### Usage: + +``` +oro view [OPTIONS] +``` + +### Arguments + +#### `` + +Package spec to look up + +### Options + +#### `-h, --help` + +Print help (see a summary with '-h') + +#### `-V, --version` + +Print version + +### Global Options + +#### `--root ` + +Package path to operate on + +#### `--registry ` + +Registry used for unscoped packages. + +Defaults to https://registry.npmjs.org. + +#### `--cache ` + +Location of disk cache. + +Default location varies by platform. + +#### `--config ` + +File to read configuration values from. + +When specified, global configuration loading is disabled and configuration values will only be read from this location. + +#### `--loglevel ` + +Log output level/directive. + +Supports plain loglevels (off, error, warn, info, debug, trace) as well as more advanced directives in the format `target[span{field=value}]=level`. + +#### `-q, --quiet` + +Disable all output + +#### `--json` + +Format output as JSON + +#### `--no-progress` + +Disable progress bar display + +