Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,6 @@ playwright/.cache/
.shopping_list.txt.bak
.shopping-list
.shopping-checked

# insta rejects pending snapshots as .snap.new
*.snap.new
60 changes: 44 additions & 16 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,37 @@ keywords = ["cooklang", "recipes", "cli", "cooking"]
categories = ["command-line-utilities"]

[features]
default = ["self-update", "sync"]
self-update = ["dep:self_update"]
sync = ["dep:cooklang-sync-client", "dep:uuid", "dep:tokio-util", "dep:base64", "dep:libsqlite3-sys", "dep:thiserror"]
default = ["self-update", "sync", "server", "import", "lsp"]

# `cook server` - the axum web UI. Pulls in the whole HTTP server stack.
server = [
"dep:axum",
"dep:askama_axum",
"dep:tower",
"dep:tower-http",
"dep:notify",
"dep:notify-debouncer-full",
"dep:tokio-stream",
"dep:futures-util",
"dep:open",
]
# `cook import` - scrape a recipe from a website.
import = ["dep:cooklang-import", "dep:reqwest"]
# `cook lsp` - language server for editor integrations.
lsp = ["dep:cooklang-language-server", "dep:tower-lsp"]

self-update = ["dep:self_update", "dep:reqwest"]
# The sync loop only runs inside the web server, so sync implies server.
sync = [
"server",
"dep:cooklang-sync-client",
"dep:uuid",
"dep:tokio-util",
"dep:base64",
"dep:libsqlite3-sys",
"dep:thiserror",
"dep:reqwest",
]

[lib]
name = "cookcli"
Expand All @@ -32,8 +60,8 @@ anstyle = "1"
anstyle-yansi = "2"
anyhow = "1"
askama = { version = "0.12", features = ["serde-json"] }
askama_axum = "0.4"
axum = { version = "0.7", features = ["ws"] }
askama_axum = { version = "0.4", optional = true }
axum = { version = "0.7", features = ["ws"], optional = true }
camino = { version = "1", features = ["serde1"] }
chrono = "0.4"
clap = { version = "4.5", features = ["derive"] }
Expand All @@ -44,22 +72,22 @@ base64 = { version = "0.22", optional = true }
# depend on cooklang with default features. Declare it where it is actually used.
cooklang = { version = "0.18.5", default-features = false, features = ["aisle", "bundled_units", "pantry", "shopping_list"] }
cooklang-find = { version = "0.6" }
cooklang-import = "0.9.3"
cooklang-import = { version = "0.9.3", optional = true }
cooklang-sync-client = { version = "0.4.11", optional = true }
libsqlite3-sys = { version = "0.35", features = ["bundled"], optional = true }
cooklang-language-server = "0.2.3"
cooklang-language-server = { version = "0.2.3", optional = true }
cooklang-reports = { version = "0.2.2" }
directories = "6"
fluent = "0.16"
futures-util = "0.3"
futures-util = { version = "0.3", optional = true }
fluent-templates = "0.10"
humantime = "2"
mime_guess = "2.0"
notify = "8"
notify-debouncer-full = "0.7"
open = "5.3"
notify = { version = "8", optional = true }
notify-debouncer-full = { version = "0.7", optional = true }
open = { version = "5.3", optional = true }
regex = "1"
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"], optional = true }
rust-embed = "8"
self_update = { version = "0.41", default-features = false, features = ["archive-tar", "archive-zip", "compression-flate2", "rustls"], optional = true }
serde = "1.0"
Expand All @@ -68,13 +96,13 @@ serde_yaml = "0.9"
tabular = { version = "0.2", features = ["ansi-cell"] }
textwrap = { version = "0.16", features = ["terminal_size"] }
tokio = { version = "1", features = ["full"] }
tokio-stream = { version = "0.1", features = ["sync"] }
tokio-stream = { version = "0.1", features = ["sync"], optional = true }
tokio-util = { version = "0.7", optional = true }
thiserror = { version = "2", optional = true }
toml = "0.9.5"
tower = { version = "0.5", features = ["util"] }
tower-http = { version = "0.5", features = ["fs", "trace", "cors", "normalize-path"] }
tower-lsp = "0.20"
tower = { version = "0.5", features = ["util"], optional = true }
tower-http = { version = "0.5", features = ["fs", "trace", "cors", "normalize-path"], optional = true }
tower-lsp = { version = "0.20", optional = true }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
unic-langid = "0.9"
Expand Down
19 changes: 15 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,17 +213,28 @@ cargo build --release
# Binary will be at target/release/cook
```

#### Building without Self-Update
#### Cargo features

By default, CookCLI includes a self-update feature. To build without this feature (useful for CI/CD pipelines, package managers, or environments where auto-update is not desired):
The optional commands are behind Cargo features, all enabled by default. Turning them off cuts the dependency graph roughly in half (412 crates → 225) and the binary from ~23 MB to ~9 MB — useful for CI/CD, package managers, or small machines where you only want the core recipe tooling.

| Feature | Command | Notes |
|---|---|---|
| `server` | `cook server` | Web UI (axum + tower). `cook build` still works without it. |
| `import` | `cook import` | Scrape a recipe from a website. |
| `lsp` | `cook lsp` | Language server for editor integrations. |
| `sync` | `cook login` / `cook logout` | Recipe sync. Implies `server` — the sync loop runs inside it. |
| `self-update` | `cook update` | In-place binary upgrade. |

```bash
# Build without self-update feature
# Core CLI only: recipe, shopping-list, search, doctor, pantry, report, build, seed
cargo build --release --no-default-features

# This disables the 'self-update' feature flag while keeping all other functionality
# Core plus the web server
cargo build --release --no-default-features --features server
```

Everything not listed above — parsing, scaling, shopping lists, search, pantry, reports and the static site builder — is always compiled in.

### Development Setup

For development with hot-reload of CSS changes:
Expand Down
13 changes: 10 additions & 3 deletions src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,15 @@

use clap::{Parser, Subcommand};

#[cfg(feature = "import")]
use crate::import;
#[cfg(feature = "lsp")]
use crate::lsp;
#[cfg(feature = "server")]
use crate::server;
#[cfg(feature = "self-update")]
use crate::update;
use crate::{
build, doctor, import, lsp, pantry, recipe, report, search, seed, server, shopping_list,
};
use crate::{build, doctor, pantry, recipe, report, search, seed, shopping_list};

#[derive(Parser, Debug)]
#[command(
Expand Down Expand Up @@ -85,6 +89,7 @@ pub enum Command {
alias = "s",
long_about = "Run a web server to browse and interact with your recipe collection"
)]
#[cfg(feature = "server")]
Server(server::ServerArgs),

/// Build artifacts from your recipe collection
Expand Down Expand Up @@ -159,6 +164,7 @@ pub enum Command {
alias = "i",
long_about = "Import recipes from websites and automatically convert them to Cooklang format"
)]
#[cfg(feature = "import")]
Import(import::ImportArgs),

/// Generate custom reports from recipes using templates
Expand Down Expand Up @@ -231,6 +237,7 @@ pub enum Command {
#[command(
long_about = "Start the Language Server Protocol server for Cooklang editor integration"
)]
#[cfg(feature = "lsp")]
Lsp(lsp::LspArgs),

/// Sign in to CookCloud
Expand Down
2 changes: 1 addition & 1 deletion src/build/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ mod renderer;
mod sitemap;
mod writer;

use crate::server::language::{parse_supported_language, EN_US};
use crate::util::resolve_to_absolute_path;
use crate::web::language::{parse_supported_language, EN_US};
use crate::Context;
use anyhow::{bail, Context as _, Result};
use camino::Utf8PathBuf;
Expand Down
4 changes: 2 additions & 2 deletions src/build/renderer.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use crate::build::links::relative_prefix;
use crate::build::writer::write_html;
use crate::server::builders::{
use crate::web::builders::{
build_recipe_template, build_recipes_template, RecipeBuildInput, RecipeBuildOutput,
RecipesBuildInput,
};
use crate::server::language::FeatureFlags;
use crate::web::language::FeatureFlags;
use anyhow::Result;
use askama::Template;
use camino::{Utf8Path, Utf8PathBuf};
Expand Down
4 changes: 2 additions & 2 deletions src/build/writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ pub fn write_bytes(output_root: &Utf8Path, relpath: &Utf8Path, bytes: &[u8]) ->
/// Copy every file in the rust-embed `StaticFiles` to `output_root/static/<path>`.
pub fn copy_static_assets(output_root: &Utf8Path) -> Result<usize> {
let mut count = 0;
for path in crate::server::StaticFiles::iter() {
for path in crate::web::StaticFiles::iter() {
let rel = Utf8Path::new("static").join(path.as_ref());
let file = crate::server::StaticFiles::get(path.as_ref())
let file = crate::web::StaticFiles::get(path.as_ref())
.with_context(|| format!("Embedded file vanished: {path}"))?;
write_bytes(output_root, &rel, &file.data)?;
count += 1;
Expand Down
6 changes: 6 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,27 @@ use camino::{Utf8Path, Utf8PathBuf};
// Commands - make them available as public modules
pub mod build;
pub mod doctor;
#[cfg(feature = "import")]
pub mod import;
#[cfg(feature = "sync")]
pub mod login;
#[cfg(feature = "sync")]
pub mod logout;
#[cfg(feature = "lsp")]
pub mod lsp;
pub mod pantry;
pub mod recipe;
pub mod report;
pub mod search;
pub mod seed;
#[cfg(feature = "server")]
pub mod server;
pub mod shopping_list;
#[cfg(feature = "sync")]
pub mod sync;
#[cfg(feature = "self-update")]
pub mod update;
pub mod web;

// Other modules
pub mod args;
Expand Down
2 changes: 1 addition & 1 deletion src/login.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use anyhow::Result;
use clap::Args;
use tokio_util::sync::CancellationToken;

use crate::server::sync::{device_flow, SyncSession};
use crate::sync::{device_flow, SyncSession};
use crate::Context;

#[derive(Debug, Args)]
Expand Down
2 changes: 1 addition & 1 deletion src/logout.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use anyhow::Result;
use clap::Args;

use crate::server::sync::SyncSession;
use crate::sync::SyncSession;
use crate::Context;

#[derive(Debug, Args)]
Expand Down
10 changes: 10 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,21 +37,27 @@ use clap::Parser;
// commands
mod build;
mod doctor;
#[cfg(feature = "import")]
mod import;
#[cfg(feature = "sync")]
mod login;
#[cfg(feature = "sync")]
mod logout;
#[cfg(feature = "lsp")]
mod lsp;
mod pantry;
mod recipe;
mod report;
mod search;
mod seed;
#[cfg(feature = "server")]
mod server;
mod shopping_list;
#[cfg(feature = "sync")]
mod sync;
#[cfg(feature = "self-update")]
mod update;
mod web;

// other modules
mod args;
Expand All @@ -71,15 +77,18 @@ pub fn main() -> Result<()> {

match args.command {
Command::Recipe(args) => recipe::run(&ctx, args),
#[cfg(feature = "server")]
Command::Server(args) => server::run(ctx, args),
Command::Build(args) => build::run(&ctx, args),
Command::ShoppingList(args) => shopping_list::run(&ctx, args),
Command::Seed(args) => seed::run(&ctx, args),
Command::Search(args) => search::run(&ctx, args),
#[cfg(feature = "import")]
Command::Import(args) => import::run(&ctx, args),
Command::Report(args) => report::run(&ctx, args),
Command::Doctor(args) => doctor::run(&ctx, args),
Command::Pantry(args) => pantry::run(&ctx, args),
#[cfg(feature = "lsp")]
Command::Lsp(args) => lsp::run(&ctx, args),
#[cfg(feature = "sync")]
Command::Login(args) => login::run(&ctx, args),
Expand Down Expand Up @@ -131,6 +140,7 @@ impl Context {
fn configure_context() -> Result<Context> {
let args = CliArgs::parse();
let base_path = match args.command {
#[cfg(feature = "server")]
Command::Server(ref server_args) => server_args
.get_base_path()
.unwrap_or_else(|| Utf8PathBuf::from(".")),
Expand Down
Loading
Loading