From 062054f45071a19265fbe7d1ba22da8a9da59e17 Mon Sep 17 00:00:00 2001 From: cupcakearmy Date: Sun, 12 Jul 2026 11:36:01 +0200 Subject: [PATCH 01/46] first v3 --- CONTRIBUTING.md | 4 +- Dockerfile | 2 +- README.md | 18 +- docker-compose.dev.yaml | 8 +- docker-compose.yaml | 8 +- package.json | 17 +- packages/backend/Cargo.lock | 22 +- packages/backend/Cargo.toml | 5 +- packages/backend/src/config.rs | 8 +- packages/backend/src/health/mod.rs | 4 +- packages/backend/src/lock.rs | 10 - packages/backend/src/main.rs | 32 +- packages/backend/src/note/mod.rs | 2 +- packages/backend/src/note/model.rs | 37 +- packages/backend/src/note/routes.rs | 216 ++- packages/backend/src/store.rs | 113 +- packages/cli/build.js | 9 +- packages/cli/package.json | 33 +- packages/cli/src/actions/download.ts | 79 +- packages/cli/src/actions/upload.ts | 63 +- packages/cli/src/cli.ts | 23 +- packages/cli/src/index.ts | 4 +- packages/cli/src/shared/adapters.ts | 61 - packages/cli/src/shared/api.ts | 141 -- packages/cli/src/shared/shared.ts | 2 - packages/cli/src/utils/utils.ts | 18 +- packages/frontend/package.json | 3 +- packages/frontend/src/lib/stores/status.ts | 25 +- .../src/lib/ui/AdvancedParameters.svelte | 3 +- .../frontend/src/lib/ui/FileUpload.svelte | 6 +- .../src/lib/ui/PastedFilesPreview.svelte | 8 +- packages/frontend/src/lib/ui/ShowNote.svelte | 20 +- packages/frontend/src/lib/ui/TextInput.svelte | 4 +- packages/frontend/src/lib/views/Create.svelte | 79 +- .../src/routes/note/[id]/+page.svelte | 64 +- packages/shared/package.json | 22 + packages/shared/src/api.ts | 58 + packages/shared/src/crypto.test.ts | 26 + packages/shared/src/crypto.ts | 41 + packages/shared/src/index.ts | 4 + packages/shared/src/types.ts | 21 + packages/shared/tsconfig.json | 10 + packages/shared/vitest.config.ts | 7 + pnpm-lock.yaml | 1304 ++++------------- test/utils.ts | 7 +- version.mjs | 16 +- 46 files changed, 976 insertions(+), 1691 deletions(-) delete mode 100644 packages/backend/src/lock.rs delete mode 100644 packages/cli/src/shared/adapters.ts delete mode 100644 packages/cli/src/shared/api.ts delete mode 100644 packages/cli/src/shared/shared.ts create mode 100644 packages/shared/package.json create mode 100644 packages/shared/src/api.ts create mode 100644 packages/shared/src/crypto.test.ts create mode 100644 packages/shared/src/crypto.ts create mode 100644 packages/shared/src/index.ts create mode 100644 packages/shared/src/types.ts create mode 100644 packages/shared/tsconfig.json create mode 100644 packages/shared/vitest.config.ts diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 65ba5f6..eada928 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,7 +3,7 @@ ## Requirements - [mise](https://mise.jdx.dev) — manages pnpm, rust, node (see `mise.toml`) -- docker or [colima](https://github.com/abiosoft/colima) (for redis) +- docker or [colima](https://github.com/abiosoft/colima) (for cache) ## Setup @@ -18,7 +18,7 @@ pnpm install pnpm run dev ``` -Make sure docker/colima is running. This starts redis, the rust backend, the web client, and the CLI. The app is at [localhost:3000](http://localhost:3000). +Make sure docker/colima is running. This starts the cache (valkey/redis), the rust backend, the web client, and the CLI. The app is at [localhost:3000](http://localhost:3000). ## Tests diff --git a/Dockerfile b/Dockerfile index 1c5738c..ab68bbb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,6 +25,6 @@ RUN apk add --no-cache curl libgcc COPY --from=backend /tmp/target/release/cryptgeon . COPY --from=client /tmp/packages/frontend/build ./frontend ENV FRONTEND_PATH="./frontend" -ENV REDIS="redis://redis/" +ENV CACHE="redis://cache/" EXPOSE 8000 ENTRYPOINT [ "/app/cryptgeon" ] diff --git a/README.md b/README.md index 31668f5..f3c2bc6 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ client side with the key and then sent to the server. data is store never persisted to disk. the server never sees the encryption key and cannot decrypt the contents of the notes even if it tried to. -> View counts are guaranteed with one running instance of cryptgeon. Multiple instances connected to the same Redis instance can run into race conditions, where a note might be retrieved more than the view count allows. +> View counts are guaranteed with one running instance of cryptgeon. Multiple instances connected to the same cache instance can run into race conditions, where a note might be retrieved more than the view count allows. ## Screenshot @@ -73,14 +73,14 @@ of the notes even if it tried to. | Variable | Default | Description | | ----------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `REDIS` | `redis://redis/` | Redis URL to connect to. [According to format](https://docs.rs/redis/latest/redis/#connection-parameters) | +| `CACHE` | `redis://cache/` | Cache URL (valkey or redis) to connect to. [According to format](https://docs.rs/redis/latest/redis/#connection-parameters) | | `SIZE_LIMIT` | `1 KiB` | Max size for body. Accepted values according to [byte-unit](https://docs.rs/byte-unit/).
`512 MiB` is the maximum allowed.
The frontend will show that number including the ~35% encoding overhead. | | `MAX_VIEWS` | `100` | Maximal number of views. | | `MAX_EXPIRATION` | `360` | Maximal expiration in minutes. | | `ALLOW_ADVANCED` | `true` | Allow custom configuration. If set to `false` all notes will be one view only. | | `ALLOW_FILES` | `true` | Allow uploading files. If set to `false`, users will only be allowed to create text notes. | | `ID_LENGTH` | `32` | Set the size of the note `id` in bytes. By default this is `32` bytes. This is useful for reducing link size. _This setting does not affect encryption strength_. | -| `REDIS_PREFIX` | `""` | Optional prefix for all Redis keys. Useful when sharing a Redis instance with other apps via ACL namespaces. | +| `CACHE_PREFIX` | `""` | Optional prefix for all cache keys. Useful when sharing a cache instance with other apps via ACL namespaces. | | `VERBOSITY` | `warn` | Verbosity level for the backend. [Possible values](https://docs.rs/env_logger/latest/env_logger/#enabling-logging) are: `error`, `warn`, `info`, `debug`, `trace` | | `THEME_IMAGE` | `""` | Custom image for replacing the logo. Must be publicly reachable | | `THEME_TEXT` | `""` | Custom text for replacing the description below the logo | @@ -107,12 +107,12 @@ Docker is the easiest way. There is the [official image here](https://hub.docker version: "3.8" services: - redis: - image: redis:7-alpine + cache: + image: valkey/valkey:7-alpine # This is required to stay in RAM only. - command: redis-server --save "" --appendonly no + command: valkey-server --save "" --appendonly no # Set a size limit. See link below on how to customise. - # https://redis.io/docs/latest/operate/rs/databases/memory-performance/eviction-policy/ + # https://valkey.io/docs/latest/operate/rs/databases/memory-performance/eviction-policy/ # --maxmemory 1gb --maxmemory-policy allkeys-lrulpine # This prevents the creation of an anonymous volume. tmpfs: @@ -121,7 +121,7 @@ services: app: image: cupcakearmy/cryptgeon:latest depends_on: - - redis + - cache environment: # Size limit for a single note. SIZE_LIMIT: 4 MiB @@ -130,7 +130,7 @@ services: # Optional health checks # healthcheck: - # test: ["CMD", "curl", "--fail", "http://127.0.0.1:8000/api/live/"] + # test: ["CMD", "curl", "--fail", "http://127.0.0.1:8000/healthz"] # interval: 1m # timeout: 3s # retries: 2 diff --git a/docker-compose.dev.yaml b/docker-compose.dev.yaml index c651a8e..4f21c0d 100644 --- a/docker-compose.dev.yaml +++ b/docker-compose.dev.yaml @@ -2,7 +2,7 @@ # For a production file see: README.md services: - redis: + cache: image: valkey/valkey:7-alpine # This is required to stay in RAM only. command: valkey-server --save "" --appendonly no @@ -19,14 +19,14 @@ services: build: . env_file: .env.dev depends_on: - - redis + - cache restart: unless-stopped ports: - 3000:8000 healthcheck: - test: ['CMD', 'curl', '--fail', 'http://127.0.0.1:8000/api/live/'] + test: ['CMD', 'curl', '--fail', 'http://127.0.0.1:8000/healthz'] interval: 1m timeout: 3s retries: 2 - start_period: 5s + start_period: 5s \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index 3a0b25f..73ef450 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,5 +1,5 @@ services: - redis: + cache: image: valkey/valkey:7-alpine # This is required to stay in RAM only. command: valkey-server --save "" --appendonly no @@ -13,7 +13,7 @@ services: app: image: cupcakearmy/cryptgeon:latest depends_on: - - redis + - cache environment: # Size limit for a single note. SIZE_LIMIT: 4 MiB @@ -22,8 +22,8 @@ services: # Optional health checks # healthcheck: - # test: ["CMD", "curl", "--fail", "http://127.0.0.1:8000/api/live/"] + # test: ["CMD", "curl", "--fail", "http://127.0.0.1:8000/healthz"] # interval: 1m # timeout: 3s # retries: 2 - # start_period: 5s + # start_period: 5s \ No newline at end of file diff --git a/package.json b/package.json index 115e11a..a33f46e 100644 --- a/package.json +++ b/package.json @@ -1,22 +1,23 @@ { "scripts": { - "dev:docker": "docker compose -f docker-compose.dev.yaml up redis", + "dev:docker": "docker compose -f docker-compose.dev.yaml up cache", "dev:packages": "pnpm --parallel run dev", - "dev": "run-p dev:*", + "dev": "pnpm --parallel run dev:*", "docker:up": "docker compose -f docker-compose.dev.yaml up", "docker:build": "docker compose -f docker-compose.dev.yaml build", "test": "playwright test --project=chrome --project=firefox --project=safari", "test:local": "playwright test --project=chrome", - "test:server": "run-s docker:up", + "test:server": "docker compose -f docker-compose.dev.yaml up", "test:dl-browsers": "playwright install", - "test:prepare": "run-p test:dl-browsers build docker:build", + "test:prepare": "pnpm --parallel run test:dl-browsers build docker:build", "build": "pnpm run --recursive --filter=!@cryptgeon/backend build" }, "devDependencies": { "@playwright/test": "^1.60.0", - "@types/node": "^24.12.4", - "npm-run-all": "^4.1.5", - "shelljs": "^0.8.5" + "@types/node": "^24.12.4" }, - "packageManager": "pnpm@11.5.0" + "packageManager": "pnpm@11.5.0", + "engines": { + "node": ">=22" + } } diff --git a/packages/backend/Cargo.lock b/packages/backend/Cargo.lock index 7f5c0bd..dba5e3c 100644 --- a/packages/backend/Cargo.lock +++ b/packages/backend/Cargo.lock @@ -252,7 +252,7 @@ dependencies = [ [[package]] name = "cryptgeon" -version = "2.9.3" +version = "3.0.0" dependencies = [ "axum", "bs62", @@ -261,6 +261,7 @@ dependencies = [ "lazy_static", "redis", "ring", + "rmp-serde", "serde", "serde_json", "tokio", @@ -1004,6 +1005,25 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rmp" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "rmp-serde" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155" +dependencies = [ + "rmp", + "serde", +] + [[package]] name = "rustix" version = "1.1.4" diff --git a/packages/backend/Cargo.toml b/packages/backend/Cargo.toml index 9cb08db..2646a8f 100644 --- a/packages/backend/Cargo.toml +++ b/packages/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cryptgeon" -version = "2.9.3" +version = "3.0.0" authors = ["cupcakearmy "] edition = "2024" rust-version = "1.95" @@ -20,8 +20,9 @@ redis = { version = "1", features = ["tls-native-tls"] } # Utility serde_json = "1" +rmp-serde = "1" lazy_static = "1" ring = "0.17" bs62 = "0.1" byte-unit = "4" -dotenv = "0.15" +dotenv = "0.15" \ No newline at end of file diff --git a/packages/backend/src/config.rs b/packages/backend/src/config.rs index 9b1d1ec..b9b4468 100644 --- a/packages/backend/src/config.rs +++ b/packages/backend/src/config.rs @@ -34,7 +34,7 @@ pub static ref ID_LENGTH: u32 = std::env::var("ID_LENGTH") .unwrap_or("32".to_string()) .parse() .unwrap(); -pub static ref REDIS_PREFIX: String = std::env::var("REDIS_PREFIX") +pub static ref CACHE_PREFIX: String = std::env::var("CACHE_PREFIX") .unwrap_or("".to_string()) .parse() .unwrap(); @@ -50,6 +50,10 @@ pub static ref IMPRINT_HTML: String = std::env::var("IMPRINT_HTML") .unwrap_or("".to_string()) .parse() .unwrap(); +pub static ref EXTRA_SIZE_LIMIT: usize = std::env::var("EXTRA_SIZE_LIMIT") + .unwrap_or("512".to_string()) + .parse() + .unwrap(); } // THEME @@ -78,4 +82,4 @@ lazy_static! { .unwrap_or("true".to_string()) .parse() .unwrap(); -} +} \ No newline at end of file diff --git a/packages/backend/src/health/mod.rs b/packages/backend/src/health/mod.rs index e54f8ac..cec0760 100644 --- a/packages/backend/src/health/mod.rs +++ b/packages/backend/src/health/mod.rs @@ -2,9 +2,9 @@ use crate::store; use axum::http::StatusCode; pub async fn report_health() -> (StatusCode,) { - if store::can_reach_redis() { + if store::can_reach_cache() { return (StatusCode::OK,); } else { return (StatusCode::SERVICE_UNAVAILABLE,); } -} +} \ No newline at end of file diff --git a/packages/backend/src/lock.rs b/packages/backend/src/lock.rs deleted file mode 100644 index 42deccf..0000000 --- a/packages/backend/src/lock.rs +++ /dev/null @@ -1,10 +0,0 @@ -use std::collections::HashMap; -use std::sync::Arc; -use tokio::sync::Mutex; - -#[derive(Clone)] -pub struct SharedState { - pub locks: LockMap, -} - -pub type LockMap = Arc>>>>; diff --git a/packages/backend/src/main.rs b/packages/backend/src/main.rs index e738eea..f51a59f 100644 --- a/packages/backend/src/main.rs +++ b/packages/backend/src/main.rs @@ -1,13 +1,9 @@ -use std::{collections::HashMap, sync::Arc}; - use axum::{ Router, ServiceExt, extract::{DefaultBodyLimit, Request}, routing::{delete, get, post}, }; use dotenv::dotenv; -use lock::SharedState; -use tokio::sync::Mutex; use tower::Layer; use tower_http::{ compression::CompressionLayer, @@ -21,7 +17,6 @@ extern crate lazy_static; mod config; mod csp; mod health; -mod lock; mod note; mod status; mod store; @@ -30,34 +25,30 @@ mod store; async fn main() { dotenv().ok(); - let shared_state = SharedState { - locks: Arc::new(Mutex::new(HashMap::new())), - }; - - if !store::can_reach_redis() { - println!("cannot reach redis"); - panic!("cannot reach redis"); + if !store::can_reach_cache() { + println!("cannot reach cache"); + panic!("cannot reach cache"); } let notes_routes = Router::new() .route("/", post(note::create)) - .route("/{id}", delete(note::delete)) + .route("/{id}", delete(note::view)) .route("/{id}", get(note::preview)); - let health_routes = Router::new().route("/live", get(health::report_health)); + let health_routes = Router::new().route("/healthz", get(health::report_health)); let status_routes = Router::new().route("/status", get(status::get_status)); - let api_routes = Router::new() + let v3_routes = Router::new() .nest("/notes", notes_routes) - .merge(health_routes) .merge(status_routes); + let api_routes = Router::new().nest("/v3", v3_routes); + let index = format!("{}{}", config::FRONTEND_PATH.to_string(), "/index.html"); let serve_dir = ServeDir::new(config::FRONTEND_PATH.to_string()).not_found_service(ServeFile::new(index)); let app = Router::new() .nest("/api", api_routes) + .merge(health_routes) .fallback_service(serve_dir) - // Disabled for now, as svelte inlines scripts - // .layer(middleware::from_fn(csp::add_csp_header)) .layer(DefaultBodyLimit::max(*config::LIMIT)) .layer( CompressionLayer::new() @@ -65,8 +56,7 @@ async fn main() { .deflate(true) .gzip(true) .zstd(true), - ) - .with_state(shared_state); + ); let app = NormalizePathLayer::trim_trailing_slash().layer(app); @@ -77,4 +67,4 @@ async fn main() { axum::serve(listener, ServiceExt::::into_make_service(app)) .await .unwrap(); -} +} \ No newline at end of file diff --git a/packages/backend/src/note/mod.rs b/packages/backend/src/note/mod.rs index 13bc006..9efd49d 100644 --- a/packages/backend/src/note/mod.rs +++ b/packages/backend/src/note/mod.rs @@ -2,4 +2,4 @@ mod model; mod routes; pub use model::*; -pub use routes::*; +pub use routes::*; \ No newline at end of file diff --git a/packages/backend/src/note/model.rs b/packages/backend/src/note/model.rs index 8200b19..e522f98 100644 --- a/packages/backend/src/note/model.rs +++ b/packages/backend/src/note/model.rs @@ -5,22 +5,35 @@ use serde::{Deserialize, Serialize}; use crate::config; #[derive(Serialize, Deserialize, Clone)] -pub struct Note { - pub meta: String, - pub contents: String, +pub struct NoteMeta { + #[serde(skip_serializing_if = "Option::is_none")] pub views: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub expiration: Option, + #[serde(default)] + pub extra: Vec, } -#[derive(Serialize)] -pub struct NoteInfo { - pub meta: String, +#[derive(Serialize, Deserialize, Clone)] +pub struct CreateRequest { + pub meta: NoteMeta, + pub data: Vec, } -#[derive(Serialize)] -pub struct NotePublic { - pub meta: String, - pub contents: String, +#[derive(Serialize, Deserialize)] +pub struct CreateResponse { + pub id: String, +} + +#[derive(Serialize, Deserialize)] +pub struct MetaResponse { + pub meta: NoteMeta, +} + +#[derive(Serialize, Deserialize)] +pub struct NoteResponse { + pub meta: NoteMeta, + pub data: Vec, } pub fn generate_id() -> String { @@ -32,5 +45,5 @@ pub fn generate_id() -> String { let _ = sr.fill(&mut id); result.push_str(&bs62::encode_data(&id)); } - return result; -} + result +} \ No newline at end of file diff --git a/packages/backend/src/note/routes.rs b/packages/backend/src/note/routes.rs index bff47ef..de6ce11 100644 --- a/packages/backend/src/note/routes.rs +++ b/packages/backend/src/note/routes.rs @@ -2,155 +2,143 @@ use axum::{ extract::Path, http::StatusCode, response::{IntoResponse, Response}, - Json, + body::Bytes, }; -use serde::{Deserialize, Serialize}; -use std::{sync::Arc, time::SystemTime}; -use tokio::sync::Mutex; +use serde::Deserialize; +use std::time::SystemTime; -use crate::note::{generate_id, Note, NoteInfo}; +use crate::note::{CreateRequest, generate_id}; use crate::store; -use crate::{config, lock::SharedState}; +use crate::config; -use super::NotePublic; +use super::{CreateResponse, MetaResponse, NoteResponse, NoteMeta}; -pub fn now() -> u32 { +pub fn now() -> u64 { SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .unwrap() - .as_secs() as u32 + .as_secs() } #[derive(Deserialize)] -pub struct OneNoteParams { +pub struct NoteParams { id: String, } -pub async fn preview(Path(OneNoteParams { id }): Path) -> Response { - let note = store::get(&id); +pub async fn create(body: Bytes) -> Response { + let req: CreateRequest = match rmp_serde::from_slice(&body) { + Ok(r) => r, + Err(_) => return (StatusCode::BAD_REQUEST, "Invalid msgpack").into_response(), + }; - match note { - Ok(Some(n)) => (StatusCode::OK, Json(NoteInfo { meta: n.meta })).into_response(), - Ok(None) => (StatusCode::NOT_FOUND).into_response(), - Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(), + if req.meta.views.is_none() && req.meta.expiration.is_none() { + return (StatusCode::BAD_REQUEST, "At least views or expiration must be set").into_response(); } -} -#[derive(Serialize, Deserialize)] -struct CreateResponse { - id: String, -} - -pub async fn create(Json(mut n): Json) -> Response { - // let mut n = note.into_inner(); - let id = generate_id(); - // let bad_req = HttpResponse::BadRequest().finish(); - if n.views == None && n.expiration == None { - return ( - StatusCode::BAD_REQUEST, - "At least views or expiration must be set", - ) - .into_response(); + if req.meta.extra.len() > *config::EXTRA_SIZE_LIMIT { + return (StatusCode::BAD_REQUEST, "Extra data too large").into_response(); } + + let mut meta = req.meta; + if !*config::ALLOW_ADVANCED { - n.views = Some(1); - n.expiration = None; + meta.views = Some(1); + meta.expiration = None; } - match n.views { + + match meta.views { Some(v) => { if v > *config::MAX_VIEWS || v < 1 { return (StatusCode::BAD_REQUEST, "Invalid views").into_response(); } - n.expiration = None; // views overrides expiration } - _ => {} + None => {} } - match n.expiration { + + let expiration_ts = match meta.expiration { Some(e) => { if e > *config::MAX_EXPIRATION || e < 1 { return (StatusCode::BAD_REQUEST, "Invalid expiration").into_response(); } - let expiration = now() + (e * 60); - n.expiration = Some(expiration); + Some(now() + (e as u64 * 60)) + } + None => None, + }; + + let id = generate_id(); + let views = meta.views.map(|v| v as i64); + + match store::set(&id, &req.data, views, expiration_ts, &meta.extra) { + Ok(_) => { + let resp = CreateResponse { id }; + let bytes = rmp_serde::to_vec_named(&resp).unwrap(); + (StatusCode::OK, Bytes::from(bytes)).into_response() } - _ => {} - } - match store::set(&id.clone(), &n.clone()) { - Ok(_) => (StatusCode::OK, Json(CreateResponse { id })).into_response(), Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(), } } -pub async fn delete( - Path(OneNoteParams { id }): Path, - state: axum::extract::State, -) -> Response { - let mut locks_map = state.locks.lock().await; - let lock = locks_map - .entry(id.clone()) - .or_insert_with(|| Arc::new(Mutex::new(()))) - .clone(); - drop(locks_map); - let _guard = lock.lock().await; - - let note = store::get(&id); - match note { - Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(), +pub async fn preview(Path(NoteParams { id }): Path) -> Response { + match store::get_meta(&id) { + Ok(Some((views, expiration, extra))) => { + let meta = NoteMeta { + views: views.map(|v| v as u32), + expiration: expiration.map(|e| e as u32), + extra, + }; + let resp = MetaResponse { meta }; + let bytes = rmp_serde::to_vec_named(&resp).unwrap(); + (StatusCode::OK, Bytes::from(bytes)).into_response() + } Ok(None) => (StatusCode::NOT_FOUND).into_response(), - Ok(Some(note)) => { - let mut changed = note.clone(); - if changed.views == None && changed.expiration == None { - return (StatusCode::BAD_REQUEST).into_response(); - } - match changed.views { - Some(v) => { - changed.views = Some(v - 1); - let id = id.clone(); - if v <= 1 { - match store::del(&id) { - Err(e) => { - return (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()) - .into_response(); - } - _ => {} - } - } else { - match store::set(&id, &changed.clone()) { - Err(e) => { - return (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()) - .into_response(); - } - _ => {} - } - } - } - _ => {} - } - - let n = now(); - match changed.expiration { - Some(e) => { - if e < n { - match store::del(&id.clone()) { - Ok(_) => return (StatusCode::BAD_REQUEST).into_response(), - Err(e) => { - return (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()) - .into_response() - } - } - } - } - _ => {} - } - - return ( - StatusCode::OK, - Json(NotePublic { - contents: changed.contents, - meta: changed.meta, - }), - ) - .into_response(); - } + Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(), } } + +pub async fn view(Path(NoteParams { id }): Path) -> Response { + let (views, expiration, extra) = match store::get_meta(&id) { + Ok(Some(v)) => v, + _ => return (StatusCode::NOT_FOUND).into_response(), + }; + + let has_views = views.is_some(); + + if has_views { + let remaining = match store::decrement_views(&id) { + Ok(r) => r, + Err(_) => return (StatusCode::NOT_FOUND).into_response(), + }; + + let data = match store::get_data(&id) { + Ok(Some(d)) => d, + _ => return (StatusCode::NOT_FOUND).into_response(), + }; + + if remaining <= 0 { + let _ = store::del(&id); + } + + let meta = NoteMeta { + views: Some(if remaining > 0 { remaining as u32 } else { 0 }), + expiration: expiration.map(|e| e as u32), + extra, + }; + let resp = NoteResponse { meta, data }; + let bytes = rmp_serde::to_vec_named(&resp).unwrap(); + (StatusCode::OK, Bytes::from(bytes)).into_response() + } else { + let data = match store::get_data(&id) { + Ok(Some(d)) => d, + _ => return (StatusCode::NOT_FOUND).into_response(), + }; + + let meta = NoteMeta { + views: None, + expiration: expiration.map(|e| e as u32), + extra, + }; + let resp = NoteResponse { meta, data }; + let bytes = rmp_serde::to_vec_named(&resp).unwrap(); + (StatusCode::OK, Bytes::from(bytes)).into_response() + } +} \ No newline at end of file diff --git a/packages/backend/src/store.rs b/packages/backend/src/store.rs index af437eb..7dbe098 100644 --- a/packages/backend/src/store.rs +++ b/packages/backend/src/store.rs @@ -1,71 +1,90 @@ -use redis; use redis::Commands; use crate::config; -use crate::note::now; -use crate::note::Note; lazy_static! { - static ref REDIS_CLIENT: String = std::env::var("REDIS") + static ref CACHE_URL: String = std::env::var("CACHE") .unwrap_or("redis://127.0.0.1/".to_string()) .parse() .unwrap(); } -fn prefixed(id: &String) -> String { - format!("{}{}", config::REDIS_PREFIX.as_str(), id) +fn prefixed(id: &str) -> String { + format!("{}{}", config::CACHE_PREFIX.as_str(), id) } -fn get_connection() -> Result { +fn conn() -> Result { let client = - redis::Client::open(REDIS_CLIENT.to_string()).map_err(|_| "Unable to connect to redis")?; - client - .get_connection() - .map_err(|_| "Unable to connect to redis") + redis::Client::open(CACHE_URL.to_string()).map_err(|_| "Unable to connect to cache")?; + client.get_connection().map_err(|_| "Unable to connect to cache") } -pub fn can_reach_redis() -> bool { - let conn = get_connection(); - return match conn { - Ok(_) => true, - Err(_) => false, - }; +pub fn can_reach_cache() -> bool { + conn().is_ok() } -pub fn set(id: &String, note: &Note) -> Result<(), &'static str> { +pub fn set(id: &str, data: &[u8], views: Option, expiration: Option, extra: &[u8]) -> Result<(), &'static str> { let key = prefixed(id); - let serialized = serde_json::to_string(¬e.clone()).unwrap(); - let mut conn = get_connection()?; + let mut c = conn()?; - conn.set::<_, _, ()>(key.as_str(), serialized) - .map_err(|_| "Unable to set note in redis")?; - match note.expiration { - Some(e) => { - let seconds = e - now(); - conn.expire::<_, ()>(key.as_str(), seconds as i64) - .map_err(|_| "Unable to set expiration on note")? - } - None => {} - }; - Ok(()) -} + c.hset::<_, _, _, ()>(&key, "data", data).map_err(|_| "Unable to set note")?; + c.hset::<_, _, _, ()>(&key, "extra", extra).map_err(|_| "Unable to set note")?; -pub fn get(id: &String) -> Result, &'static str> { - let key = prefixed(id); - let mut conn = get_connection()?; - let value: Option = conn.get(key.as_str()).map_err(|_| "Could not load note in redis")?; - match value { - None => return Ok(None), - Some(s) => { - let deserialize: Note = serde_json::from_str(&s).unwrap(); - return Ok(Some(deserialize)); - } + if let Some(v) = views { + c.hset::<_, _, _, ()>(&key, "views", v).map_err(|_| "Unable to set note")?; + } + if let Some(e) = expiration { + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs(); + let ttl = e.saturating_sub(now); + c.expire::<_, ()>(&key, ttl as i64).map_err(|_| "Unable to set expiration")?; } -} -pub fn del(id: &String) -> Result<(), &'static str> { - let key = prefixed(id); - let mut conn = get_connection()?; - conn.del::<_, ()>(key.as_str()).map_err(|_| "Unable to delete note in redis")?; Ok(()) } + +pub fn get_meta(id: &str) -> Result, Option, Vec)>, &'static str> { + let key = prefixed(id); + let mut c = conn()?; + + let exists: bool = c.exists::<_, bool>(&key).map_err(|_| "Cache error")?; + if !exists { + return Ok(None); + } + + let views: Option = c.hget::<_, _, Option>(&key, "views").map_err(|_| "Cache error")?; + let expiration: Option = c.hget::<_, _, Option>(&key, "expiration").map_err(|_| "Cache error")?; + let extra: Vec = c.hget::<_, _, Vec>(&key, "extra").unwrap_or_default(); + + Ok(Some((views, expiration, extra))) +} + +pub fn get_data(id: &str) -> Result>, &'static str> { + let key = prefixed(id); + let mut c = conn()?; + let data: Option> = c.hget::<_, _, Option>>(&key, "data").map_err(|_| "Cache error")?; + Ok(data) +} + +pub fn has_views(id: &str) -> Result { + let key = prefixed(id); + let mut c = conn()?; + let has: bool = c.hexists::<_, _, bool>(&key, "views").map_err(|_| "Cache error")?; + Ok(has) +} + +pub fn decrement_views(id: &str) -> Result { + let key = prefixed(id); + let mut c = conn()?; + let result: i64 = c.hincr::<_, _, _, i64>(&key, "views", -1).map_err(|_| "Cache error")?; + Ok(result) +} + +pub fn del(id: &str) -> Result<(), &'static str> { + let key = prefixed(id); + let mut c = conn()?; + c.del::<_, ()>(&key).map_err(|_| "Unable to delete note")?; + Ok(()) +} \ No newline at end of file diff --git a/packages/cli/build.js b/packages/cli/build.js index a977c85..14f66c0 100644 --- a/packages/cli/build.js +++ b/packages/cli/build.js @@ -4,12 +4,13 @@ import pkg from './package.json' with { type: 'json' } const watch = process.argv.slice(2)[0] === '--watch' await build({ - entry: ['src/index.ts', 'src/cli.ts', 'src/shared/shared.ts'], + entry: ['src/index.ts', 'src/cli.ts'], dts: true, minify: true, - format: ['esm', 'cjs'], - target: 'es2020', + format: ['esm'], + target: 'es2022', clean: true, + noExternal: ['@cryptgeon/shared'], define: { VERSION: `"${pkg.version}"` }, watch, -}) +}) \ No newline at end of file diff --git a/packages/cli/package.json b/packages/cli/package.json index 71dbeb3..f07a3bd 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "cryptgeon", - "version": "2.9.3", + "version": "3.0.0", "homepage": "https://github.com/cupcakearmy/cryptgeon", "repository": { "type": "git", @@ -9,39 +9,36 @@ }, "type": "module", "exports": { - ".": "./dist/index.js", - "./shared": { - "import": "./dist/shared/shared.js", - "types": "./dist/shared/shared.d.ts" - } + ".": "./dist/index.js" }, "types": "./dist/index.d.ts", "bin": { - "cryptgeon": "./dist/cli.cjs" + "cryptgeon": "./dist/cli.js" }, "files": [ "dist" ], "scripts": { - "bin": "run-s build package", - "build": "tsc && node build.js", + "build": "node build.js", "dev": "node build.js --watch", "prepublishOnly": "run-s build" }, - "devDependencies": { + "dependencies": { + "@cryptgeon/shared": "workspace:*", + "@msgpack/msgpack": "^3.1.3", "@commander-js/extra-typings": "^12.1.0", - "@types/inquirer": "^9.0.9", - "@types/mime": "^4.0.0", - "@types/node": "^20.19.41", - "commander": "^12.1.0", "inquirer": "^9.3.8", "mime": "^4.1.0", - "occulto": "^2.0.6", - "pretty-bytes": "^6.1.1", + "pretty-bytes": "^6.1.1" + }, + "devDependencies": { + "@types/inquirer": "^9.0.9", + "@types/node": "^22.15.3", + "commander": "^12.1.0", "tsup": "^8.5.1", "typescript": "^5.9.3" }, "engines": { - "node": ">=18" + "node": ">=22" } -} +} \ No newline at end of file diff --git a/packages/cli/src/actions/download.ts b/packages/cli/src/actions/download.ts index 7aa04f1..c842104 100644 --- a/packages/cli/src/actions/download.ts +++ b/packages/cli/src/actions/download.ts @@ -1,51 +1,42 @@ import inquirer from 'inquirer' import { access, constants, writeFile } from 'node:fs/promises' import { basename, resolve } from 'node:path' -import { AES, Hex } from 'occulto' +import { decode } from '@msgpack/msgpack' import pretty from 'pretty-bytes' -import { Adapters } from '../shared/adapters.js' -import { API } from '../shared/api.js' +import { decrypt, deriveKey, setServer, getServer, info, get } from '@cryptgeon/shared' export async function download(url: URL, all: boolean, suggestedPassword?: string) { - API.setOptions({ server: url.origin }) + setServer(url.origin) const id = url.pathname.split('/')[2] - const preview = await API.info(id).catch(() => { - throw new Error('Note does not exist or is expired') - }) + const meta = await info(id) + if (!meta) throw new Error('Note does not exist or is expired') - // Password - let password: string - const derivation = preview?.meta.derivation - if (derivation) { + let key: Uint8Array + if (meta.extra && meta.extra.length > 0) { if (suggestedPassword) { - password = suggestedPassword + const derivation = decode(meta.extra) as any + key = deriveKey(suggestedPassword, new Uint8Array(derivation.salt)) } else { const response = await inquirer.prompt([ - { - type: 'password', - message: 'Note password', - name: 'password', - }, + { type: 'password', message: 'Note password', name: 'password' }, ]) - password = response.password + const derivation = decode(meta.extra) as any + key = deriveKey(response.password, new Uint8Array(derivation.salt)) } } else { - password = url.hash.slice(1) + const hex = url.hash.slice(1) + key = new Uint8Array(Buffer.from(hex, 'hex')) } - const key = derivation ? (await AES.derive(password, derivation))[0] : Hex.decode(password) - const note = await API.get(id) + const note = await get(id) + if (!note) throw new Error('Could not load note') - const couldNotDecrypt = new Error('Could not decrypt note. Probably an invalid password') - switch (note.meta.type) { - case 'file': - const files = await Adapters.Files.decrypt(note.contents, key).catch(() => { - throw couldNotDecrypt - }) - if (!files) { - throw new Error('No files found in note') - } + const decrypted = decrypt(note.data, key) + const content = decode(decrypted) as any + switch (content.type) { + case 'files': + const files: { name: string; data: Uint8Array }[] = content.data let selected: typeof files if (all) { selected = files @@ -55,36 +46,32 @@ export async function download(url: URL, all: boolean, suggestedPassword?: strin type: 'checkbox', message: 'What files should be saved?', name: 'names', - choices: files.map((file) => ({ - value: file.name, - name: `${file.name} - ${file.type} - ${pretty(file.size, { binary: true })}`, + choices: files.map((f) => ({ + value: f.name, + name: `${f.name} - ${pretty(f.data.length, { binary: true })}`, checked: true, })), }, ]) - selected = files.filter((file) => names.includes(file.name)) + selected = files.filter((f) => names.includes(f.name)) } - if (!selected.length) throw new Error('No files selected') await Promise.all( - selected.map(async (file) => { - let filename = resolve(file.name) + selected.map(async (f) => { + let filename = resolve(f.name) try { - // If exists -> prepend timestamp to not overwrite the current file await access(filename, constants.R_OK) - filename = resolve(`${Date.now()}-${file.name}`) + filename = resolve(`${Date.now()}-${f.name}`) } catch {} - await writeFile(filename, file.contents) + await writeFile(filename, f.data) console.log(`Saved: ${basename(filename)}`) }) ) - break case 'text': - const plaintext = await Adapters.Text.decrypt(note.contents, key).catch(() => { - throw couldNotDecrypt - }) - console.log(plaintext) + console.log(content.data) break + default: + throw new Error('Unknown content type') } -} +} \ No newline at end of file diff --git a/packages/cli/src/actions/upload.ts b/packages/cli/src/actions/upload.ts index 3bb1258..a4e352f 100644 --- a/packages/cli/src/actions/upload.ts +++ b/packages/cli/src/actions/upload.ts @@ -1,46 +1,43 @@ import { readFile, stat } from 'node:fs/promises' import { basename } from 'node:path' +import { encode } from '@msgpack/msgpack' import mime from 'mime' -import { AES, Hex } from 'occulto' -import { Adapters } from '../shared/adapters.js' -import { API, FileDTO, Note, NoteMeta } from '../shared/api.js' +import { encrypt, generateKey, deriveKey, randomBytes, setServer, getServer, create, utf8ToBytes } from '@cryptgeon/shared' -export type UploadOptions = Pick & { password?: string } +export type UploadOptions = { views?: number; expiration?: number; password?: string } export async function upload(input: string | string[], options: UploadOptions): Promise { const { password, ...noteOptions } = options - const derived = options.password ? await AES.derive(options.password) : undefined - const key = derived ? derived[0] : await AES.generateKey() - let contents: string - let type: NoteMeta['type'] - if (typeof input === 'string') { - contents = await Adapters.Text.encrypt(input, key) - type = 'text' + let key: Uint8Array + let extra = new Uint8Array() + if (password) { + const salt = randomBytes(16) + key = deriveKey(password, salt) + extra = encode({ salt, N: 32768, r: 8, p: 1 }) } else { - const files: FileDTO[] = await Promise.all( - input.map(async (path) => { - const data = new Uint8Array(await readFile(path)) - const stats = await stat(path) - const extension = path.substring(path.indexOf('.') + 1) - const type = mime.getType(extension) ?? 'application/octet-stream' - return { - name: basename(path), - size: stats.size, - contents: data, - type, - } satisfies FileDTO - }) - ) - contents = await Adapters.Files.encrypt(files, key) - type = 'file' + key = generateKey() } - // Create the actual note and upload it. - const note: Note = { ...noteOptions, contents, meta: { type, derivation: derived?.[1] } } - const result = await API.create(note) - let url = `${API.getOptions().server}/note/${result.id}` - if (!derived) url += `#${Hex.encode(key)}` + let inner: Uint8Array + if (typeof input === 'string') { + inner = encode({ type: 'text', data: input }) + } else { + const files = await Promise.all( + input.map(async (path) => { + const data = new Uint8Array(await readFile(path)) + const extension = path.substring(path.indexOf('.') + 1) + const type = mime.getType(extension) ?? 'application/octet-stream' + return { name: basename(path), mime: type, size: data.length, data } + }) + ) + inner = encode({ type: 'files', data: files }) + } + + const data = encrypt(inner, key) + const result = await create({ meta: { ...noteOptions, extra }, data }) + let url = `${getServer()}/note/${result.id}` + if (!password) url += `#${Buffer.from(key).toString('hex')}` return url -} +} \ No newline at end of file diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index e505df3..835fd78 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -5,7 +5,7 @@ import prettyBytes from 'pretty-bytes' import { download } from './actions/download.js' import { upload } from './actions/upload.js' -import { API } from './shared/api.js' +import { setServer, status } from '@cryptgeon/shared' import { parseFile, parseNumber } from './utils/parsers.js' import { getStdin } from './utils/stdin.js' import { checkConstrains, exit } from './utils/utils.js' @@ -33,15 +33,12 @@ program .description('show information about the server') .addOption(server) .action(async (options) => { - API.setOptions({ server: options.server }) - const response = await API.status() - const formatted = { - ...response, - max_size: prettyBytes(response.max_size), - } - for (const key of Object.keys(formatted)) { - if (key.startsWith('theme_')) delete formatted[key as keyof typeof formatted] - } + setServer(options.server) + const response = await status() + const formatted = Object.fromEntries( + Object.entries({ ...response, max_size: prettyBytes(response.max_size as number) }) + .filter(([key]) => !key.startsWith('theme_')) + ) console.table(formatted) }) @@ -54,7 +51,7 @@ send .addOption(minutes) .addOption(password) .action(async (files, options) => { - API.setOptions({ server: options.server }) + setServer(options.server) await checkConstrains(options) options.password ||= await getStdin() try { @@ -72,7 +69,7 @@ send .addOption(minutes) .addOption(password) .action(async (text, options) => { - API.setOptions({ server: options.server }) + setServer(options.server) await checkConstrains(options) options.password ||= await getStdin() try { @@ -103,4 +100,4 @@ program } }) -program.parse() +program.parse() \ No newline at end of file diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index a22bcf6..78d73ce 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -1,4 +1,2 @@ export * from './actions/download.js' -export * from './actions/upload.js' -export * from './shared/adapters.js' -export * from './shared/api.js' +export * from './actions/upload.js' \ No newline at end of file diff --git a/packages/cli/src/shared/adapters.ts b/packages/cli/src/shared/adapters.ts deleted file mode 100644 index e35b972..0000000 --- a/packages/cli/src/shared/adapters.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { AES, Bytes, type TypedArray } from 'occulto' -import type { EncryptedFileDTO, FileDTO } from './api' - -abstract class CryptAdapter { - abstract encrypt(plaintext: T, key: TypedArray): Promise - abstract decrypt(ciphertext: string, key: TypedArray): Promise -} - -class CryptTextAdapter implements CryptAdapter { - async encrypt(plaintext: string, key: TypedArray) { - return await AES.encrypt(Bytes.encode(plaintext), key) - } - async decrypt(ciphertext: string, key: TypedArray) { - return Bytes.decode(await AES.decrypt(ciphertext, key)) - } -} - -class CryptBlobAdapter implements CryptAdapter { - async encrypt(plaintext: TypedArray, key: TypedArray) { - return await AES.encrypt(plaintext, key) - } - - async decrypt(ciphertext: string, key: TypedArray) { - return await AES.decrypt(ciphertext, key) - // const plaintext = await AES.decrypt(ciphertext, key) - // return new Blob([plaintext], { type: 'application/octet-stream' }) - } -} - -class CryptFilesAdapter implements CryptAdapter { - async encrypt(plaintext: FileDTO[], key: TypedArray) { - const adapter = new CryptBlobAdapter() - const data: Promise[] = plaintext.map(async (file) => ({ - name: file.name, - size: file.size, - type: file.type, - contents: await adapter.encrypt(file.contents, key), - })) - return JSON.stringify(await Promise.all(data)) - } - - async decrypt(ciphertext: string, key: TypedArray) { - const adapter = new CryptBlobAdapter() - const data: EncryptedFileDTO[] = JSON.parse(ciphertext) - const files: FileDTO[] = await Promise.all( - data.map(async (file) => ({ - name: file.name, - size: file.size, - type: file.type, - contents: await adapter.decrypt(file.contents, key), - })) - ) - return files - } -} - -export const Adapters = { - Text: new CryptTextAdapter(), - Blob: new CryptBlobAdapter(), - Files: new CryptFilesAdapter(), -} diff --git a/packages/cli/src/shared/api.ts b/packages/cli/src/shared/api.ts deleted file mode 100644 index f8a9936..0000000 --- a/packages/cli/src/shared/api.ts +++ /dev/null @@ -1,141 +0,0 @@ -import type { KeyData, TypedArray } from 'occulto' - -export type NoteMeta = { - type: 'text' | 'file' - derivation?: KeyData -} - -export type Note = { - contents: string - meta: NoteMeta - views?: number - expiration?: number -} -export type NoteInfo = Pick -export type NotePublic = Pick -export type NoteCreate = Omit & { meta: string } - -export type FileDTO = Pick & { - contents: TypedArray -} - -export type EncryptedFileDTO = Omit & { - contents: string -} - -type ClientOptions = { - server: string -} - -type CallOptions = { - url: string - method: string - body?: any -} - -export class PayloadToLargeError extends Error {} - -export let client: ClientOptions = { - server: '', -} - -function setOptions(options: Partial) { - client = { ...client, ...options } -} - -function getOptions(): ClientOptions { - return client -} - -async function call(options: CallOptions) { - const url = client.server + '/api/' + options.url - const response = await fetch(url, { - method: options.method, - body: options.body === undefined ? undefined : JSON.stringify(options.body), - mode: 'cors', - headers: { - 'Content-Type': 'application/json', - }, - }) - - if (!response.ok) { - if (response.status === 413) throw new PayloadToLargeError() - else throw new Error('API call failed') - } - return response.json() -} - -async function create(note: Note) { - const { meta, ...rest } = note - const body: NoteCreate = { - ...rest, - meta: JSON.stringify(meta), - } - const data = await call({ - url: 'notes/', - method: 'post', - body, - }) - return data as { id: string } -} - -async function get(id: string): Promise { - const data = await call({ - url: `notes/${id}`, - method: 'delete', - }) - const { contents, meta } = data - const note = { - contents, - meta: JSON.parse(meta), - } satisfies NotePublic - if (note.meta.derivation) note.meta.derivation.salt = new Uint8Array(Object.values(note.meta.derivation.salt)) - return note -} - -async function info(id: string): Promise { - const data = await call({ - url: `notes/${id}`, - method: 'get', - }) - const { meta } = data - const note = { - meta: JSON.parse(meta), - } satisfies NoteInfo - if (note.meta.derivation) note.meta.derivation.salt = new Uint8Array(Object.values(note.meta.derivation.salt)) - return note -} - -export type Status = { - version: string - max_size: number - max_views: number - max_expiration: number - allow_advanced: boolean - allow_files: boolean - imprint_url: string - imprint_html: string - theme_image: string - theme_text: string - theme_favicon: string - theme_page_title: string - theme_new_note_notice: boolean - theme_home_link: boolean -} - -async function status() { - const data = await call({ - url: 'status/', - method: 'get', - }) - return data as Status -} - -export const API = { - setOptions, - getOptions, - create, - get, - info, - status, -} diff --git a/packages/cli/src/shared/shared.ts b/packages/cli/src/shared/shared.ts deleted file mode 100644 index 3aaabc9..0000000 --- a/packages/cli/src/shared/shared.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './adapters.js' -export * from './api.js' diff --git a/packages/cli/src/utils/utils.ts b/packages/cli/src/utils/utils.ts index 18da6a1..9b919c7 100644 --- a/packages/cli/src/utils/utils.ts +++ b/packages/cli/src/utils/utils.ts @@ -1,5 +1,5 @@ import { exit as exitNode } from 'node:process' -import { API } from '../shared/api.js' +import { status } from '@cryptgeon/shared' export function exit(message: string) { console.error(message) @@ -7,13 +7,11 @@ export function exit(message: string) { } export async function checkConstrains(constrains: { views?: number; minutes?: number }) { - const { views, minutes } = constrains - if (views && minutes) exit('cannot set view and minutes constrains simultaneously') - if (!views && !minutes) constrains.views = 1 + if (!constrains.views && !constrains.minutes) constrains.views = 1 - const response = await API.status() - if (views && views > response.max_views) - exit(`Only a maximum of ${response.max_views} views allowed. ${views} given.`) - if (minutes && minutes > response.max_expiration) - exit(`Only a maximum of ${response.max_expiration} minutes allowed. ${minutes} given.`) -} + const response = await status() + if (constrains.views && constrains.views > (response.max_views as number)) + exit(`Only a maximum of ${response.max_views} views allowed. ${constrains.views} given.`) + if (constrains.minutes && constrains.minutes > (response.max_expiration as number)) + exit(`Only a maximum of ${response.max_expiration} minutes allowed. ${constrains.minutes} given.`) +} \ No newline at end of file diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 29a0a45..50a1362 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -29,9 +29,8 @@ "vite": "^8.0.14" }, "dependencies": { + "@cryptgeon/shared": "workspace:*", "@fontsource/fira-mono": "^5.2.7", - "cryptgeon": "workspace:*", - "occulto": "^2.0.6", "pretty-bytes": "^7.1.0", "uqr": "^0.1.3" } diff --git a/packages/frontend/src/lib/stores/status.ts b/packages/frontend/src/lib/stores/status.ts index b020413..047cfc3 100644 --- a/packages/frontend/src/lib/stores/status.ts +++ b/packages/frontend/src/lib/stores/status.ts @@ -1,8 +1,25 @@ -import { API, type Status } from 'cryptgeon/shared' +import { status as apiStatus } from '@cryptgeon/shared' import { writable } from 'svelte/store' -export const status = writable(null) +export type StatusInfo = { + version: string + max_size: number + max_views: number + max_expiration: number + allow_advanced: boolean + allow_files: boolean + imprint_url: string + imprint_html: string + theme_image: string + theme_text: string + theme_page_title: string + theme_favicon: string + theme_new_note_notice: boolean + theme_home_link: boolean +} + +export const status = writable(null) export async function init() { - status.set(await API.status()) -} + status.set((await apiStatus()) as StatusInfo) +} \ No newline at end of file diff --git a/packages/frontend/src/lib/ui/AdvancedParameters.svelte b/packages/frontend/src/lib/ui/AdvancedParameters.svelte index ba755d8..a1e8b77 100644 --- a/packages/frontend/src/lib/ui/AdvancedParameters.svelte +++ b/packages/frontend/src/lib/ui/AdvancedParameters.svelte @@ -4,10 +4,9 @@ import { status } from '$lib/stores/status' import Switch from '$lib/ui/Switch.svelte' import TextInput from '$lib/ui/TextInput.svelte' - import type { Note } from 'cryptgeon/shared' interface Props { - note: Note + note: { views: number; expiration: number } timeExpiration?: boolean customPassword?: string | null } diff --git a/packages/frontend/src/lib/ui/FileUpload.svelte b/packages/frontend/src/lib/ui/FileUpload.svelte index 6ba7135..7a37a68 100644 --- a/packages/frontend/src/lib/ui/FileUpload.svelte +++ b/packages/frontend/src/lib/ui/FileUpload.svelte @@ -3,7 +3,7 @@ import Button from '$lib/ui/Button.svelte' import MaxSize from '$lib/ui/MaxSize.svelte' - import type { FileDTO } from 'cryptgeon/shared' + import type { FileDTO } from '@cryptgeon/shared' interface Props { label?: string @@ -16,9 +16,9 @@ async function fileToDTO(file: File): Promise { return { name: file.name, + mime: file.type, size: file.size, - type: file.type, - contents: new Uint8Array(await file.arrayBuffer()), + data: new Uint8Array(await file.arrayBuffer()), } } diff --git a/packages/frontend/src/lib/ui/PastedFilesPreview.svelte b/packages/frontend/src/lib/ui/PastedFilesPreview.svelte index ba72838..46c8f19 100644 --- a/packages/frontend/src/lib/ui/PastedFilesPreview.svelte +++ b/packages/frontend/src/lib/ui/PastedFilesPreview.svelte @@ -1,7 +1,7 @@ diff --git a/packages/frontend/src/lib/views/Create.svelte b/packages/frontend/src/lib/views/Create.svelte index a299a1e..6f5610e 100644 --- a/packages/frontend/src/lib/views/Create.svelte +++ b/packages/frontend/src/lib/views/Create.svelte @@ -1,5 +1,10 @@ {#if $status !== null} - {prettyBytes($status.max_size * overhead, { binary: true })} + {prettyBytes($status.max_size, { binary: true })} {:else} {$_('common.loading')} {/if} From 74f387f920d5375748b6ade72576203f30f70b5f Mon Sep 17 00:00:00 2001 From: cupcakearmy Date: Sat, 12 Sep 2026 15:00:53 +0200 Subject: [PATCH 21/46] docs: align ES and zh readmes with v3 (cache, healthz, msgpack, env) --- README.md | 38 ++++++++++++++++++------------------ README_ES.md | 48 +++++++++++++++++++++++++-------------------- README_zh-CN.md | 52 +++++++++++++++++++++++++++++-------------------- 3 files changed, 77 insertions(+), 61 deletions(-) diff --git a/README.md b/README.md index c98ef54..09bc4b6 100644 --- a/README.md +++ b/README.md @@ -68,26 +68,26 @@ of the notes even if it tried to. ## Environment Variables -| Variable | Default | Description | -| ----------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `CACHE` | `redis://cache/` | Cache URL (valkey or redis) to connect to. [According to format](https://docs.rs/redis/latest/redis/#connection-parameters) | +| Variable | Default | Description | +| ----------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `CACHE` | `redis://cache/` | Cache URL (valkey or redis) to connect to. [According to format](https://docs.rs/redis/latest/redis/#connection-parameters) | | `SIZE_LIMIT` | `1 KiB` | Max size for body. Accepted values according to [byte-unit](https://docs.rs/byte-unit/).
`512 MiB` is the maximum allowed.
Payloads are raw bytes (msgpack + cipher), so the frontend shows the full limit. | -| `MAX_VIEWS` | `100` | Maximal number of views. | -| `MAX_EXPIRATION` | `360` | Maximal expiration in minutes. | -| `ALLOW_ADVANCED` | `true` | Allow custom configuration. If set to `false` all notes will be one view only. | -| `ALLOW_FILES` | `true` | Allow uploading files. If set to `false`, users will only be allowed to create text notes. | -| `ID_LENGTH` | `32` | Set the size of the note `id` in bytes. By default this is `32` bytes. This is useful for reducing link size. _This setting does not affect encryption strength_. | -| `CACHE_PREFIX` | `""` | Optional prefix for all cache keys. Useful when sharing a cache instance with other apps via ACL namespaces. | -| `EXTRA_SIZE_LIMIT` | `512` | Maximum size in bytes of the opaque `extra` payload (e.g. key derivation params) stored on the note metadata. | -| `VERBOSITY` | `warn` | Verbosity level for the backend. [Possible values](https://docs.rs/env_logger/latest/env_logger/#enabling-logging) are: `error`, `warn`, `info`, `debug`, `trace` | -| `THEME_IMAGE` | `""` | Custom image for replacing the logo. Must be publicly reachable | -| `THEME_TEXT` | `""` | Custom text for replacing the description below the logo | -| `THEME_PAGE_TITLE` | `""` | Custom text the page title | -| `THEME_FAVICON` | `""` | Custom url for the favicon. Must be publicly reachable | -| `THEME_NEW_NOTE_NOTICE` | `true` | Show the message about how notes are stored in the memory and may be evicted after creating a new note. Defaults to `true`. | -| `THEME_HOME_LINK` | `true` | Show the `/home` link in the footer. Defaults to `true`. | -| `IMPRINT_URL` | `""` | Custom url for an Imprint hosted somewhere else. Must be publicly reachable. Takes precedence above `IMPRINT_HTML`. | -| `IMPRINT_HTML` | `""` | Alternative to `IMPRINT_URL`, this can be used to specify the HTML code to show on `/imprint`. Only `IMPRINT_HTML` or `IMPRINT_URL` should be specified, not both. | +| `MAX_VIEWS` | `100` | Maximal number of views. | +| `MAX_EXPIRATION` | `360` | Maximal expiration in minutes. | +| `ALLOW_ADVANCED` | `true` | Allow custom configuration. If set to `false` all notes will be one view only. | +| `ALLOW_FILES` | `true` | Allow uploading files. If set to `false`, users will only be allowed to create text notes. | +| `ID_LENGTH` | `32` | Set the size of the note `id` in bytes. By default this is `32` bytes. This is useful for reducing link size. _This setting does not affect encryption strength_. | +| `CACHE_PREFIX` | `""` | Optional prefix for all cache keys. Useful when sharing a cache instance with other apps via ACL namespaces. | +| `EXTRA_SIZE_LIMIT` | `512` | Maximum size in bytes of the opaque `extra` payload (e.g. key derivation params) stored on the note metadata. | +| `VERBOSITY` | `warn` | Verbosity level for the backend. [Possible values](https://docs.rs/env_logger/latest/env_logger/#enabling-logging) are: `error`, `warn`, `info`, `debug`, `trace` | +| `THEME_IMAGE` | `""` | Custom image for replacing the logo. Must be publicly reachable | +| `THEME_TEXT` | `""` | Custom text for replacing the description below the logo | +| `THEME_PAGE_TITLE` | `""` | Custom text the page title | +| `THEME_FAVICON` | `""` | Custom url for the favicon. Must be publicly reachable | +| `THEME_NEW_NOTE_NOTICE` | `true` | Show the message about how notes are stored in the memory and may be evicted after creating a new note. Defaults to `true`. | +| `THEME_HOME_LINK` | `true` | Show the `/home` link in the footer. Defaults to `true`. | +| `IMPRINT_URL` | `""` | Custom url for an Imprint hosted somewhere else. Must be publicly reachable. Takes precedence above `IMPRINT_HTML`. | +| `IMPRINT_HTML` | `""` | Alternative to `IMPRINT_URL`, this can be used to specify the HTML code to show on `/imprint`. Only `IMPRINT_HTML` or `IMPRINT_URL` should be specified, not both. | ## Deployment diff --git a/README_ES.md b/README_ES.md index 9dd1412..2035ef2 100644 --- a/README_ES.md +++ b/README_ES.md @@ -48,7 +48,7 @@ Puedes revisar la documentación sobre el CLI en este [readme](./packages/cli/RE Se genera una id (256bit) y una llave 256(bit) para cada nota. La id -se usa para guardar y recuperar la nota. Después la nota es encriptada con la llave y con aes en modo gcm del lado del cliente y por último se envía al servidor. La información es almacenada en memoria y nunca persiste en el disco. El servidor nunca ve la llave de encriptación por lo que no puede desencriptar el contenido de las notas aunque lo intentara. +se usa para guardar y recuperar la nota. Después la nota es encriptada con XChaCha20-Poly1305 del lado del cliente y por último se envía al servidor. La información es almacenada en memoria y nunca persiste en el disco. El servidor nunca ve la llave de encriptación por lo que no puede desencriptar el contenido de las notas aunque lo intentara. ## Capturas de pantalla @@ -58,24 +58,30 @@ se usa para guardar y recuperar la nota. Después la nota es encriptada con la < | Variable | Default | Descripción | | ------------------ | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `REDIS` | `redis://redis/` | Redis URL a la que conectarse. [Según el formato](https://docs.rs/redis/latest/redis/#connection-parameters) | -| `SIZE_LIMIT` | `1 KiB` | Tamaño máximo. Valores aceptados según la [unidad byte](https://docs.rs/byte-unit/).
`512 MiB` es el máximo permitido.
El frontend mostrará ese número, incluyendo el ~35% de sobrecarga de codificación. | -| `MAX_VIEWS` | `100` | Número máximo de vistas. | -| `MAX_EXPIRATION` | `360` | Tiempo máximo de expiración en minutos. | -| `ALLOW_ADVANCED` | `true` | Permitir configuración personalizada. Si se establece en `false` todas las notas serán de una sola vista. | -| `ID_LENGTH` | `32` | Establece el tamaño en bytes de la `id` de la nota. Por defecto es de `32` bytes. Esto es útil para reducir el tamaño del link. _Esta configuración no afecta el nivel de encriptación_. | -| `VERBOSITY` | `warn` | Nivel de verbosidad del backend. [Posibles valores](https://docs.rs/env_logger/latest/env_logger/#enabling-logging): `error`, `warn`, `info`, `debug`, `trace` | -| `THEME_IMAGE` | `""` | Imagen personalizada para reemplazar el logo. Debe ser accesible públicamente. | -| `THEME_TEXT` | `""` | Texto personalizado para reemplazar la descripción bajo el logo. | -| `THEME_PAGE_TITLE` | `""` | Texto personalizado para el título | -| `THEME_FAVICON` | `""` | Url personalizada para el favicon. Debe ser accesible públicamente. | -| `THEME_HOME_LINK` | `true` | Mostrar el enlace `/home` en el pie de página. El valor predeterminado es `true`. | +| `CACHE` | `redis://cache/` | URL de caché (valkey o redis) a la que conectarse. [Según el formato](https://docs.rs/redis/latest/redis/#connection-parameters) | +| `SIZE_LIMIT` | `1 KiB` | Tamaño máximo del cuerpo. Valores aceptados según [byte-unit](https://docs.rs/byte-unit/).
`512 MiB` es el máximo permitido.
Los payloads son bytes crudos (msgpack + cifrado), por lo que el frontend muestra el límite completo. | +| `MAX_VIEWS` | `100` | Número máximo de vistas. | +| `MAX_EXPIRATION` | `360` | Tiempo máximo de expiración en minutos. | +| `ALLOW_ADVANCED` | `true` | Permitir configuración personalizada. Si se establece en `false` todas las notas serán de una sola vista. | +| `ALLOW_FILES` | `true` | Permitir subir archivos. Si es `false`, los usuarios solo podrán crear notas de texto. | +| `ID_LENGTH` | `32` | Establece el tamaño en bytes de la `id` de la nota. Por defecto es de `32` bytes. Útil para reducir el tamaño del link. _No afecta el nivel de encriptación_. | +| `CACHE_PREFIX` | `""` | Prefijo opcional para las claves de caché. Útil al compartir una instancia con otras apps vía namespaces ACL. | +| `EXTRA_SIZE_LIMIT` | `512` | Tamaño máximo en bytes del payload `extra` opaco (p. ej. parámetros de derivación de clave) guardado en los metadatos de la nota. | +| `VERBOSITY` | `warn` | Nivel de verbosidad del backend. [Posibles valores](https://docs.rs/env_logger/latest/env_logger/#enabling-logging): `error`, `warn`, `info`, `debug`, `trace` | +| `THEME_IMAGE` | `""` | Imagen personalizada para reemplazar el logo. Debe ser accesible públicamente. | +| `THEME_TEXT` | `""` | Texto personalizado para reemplazar la descripción bajo el logo. | +| `THEME_PAGE_TITLE` | `""` | Texto personalizado para el título. | +| `THEME_FAVICON` | `""` | Url personalizada para el favicon. Debe ser accesible públicamente. | +| `THEME_NEW_NOTE_NOTICE` | `true` | Mostrar el mensaje sobre cómo se almacenan las notas en memoria (pueden ser expulsadas) al crear una nueva nota. | +| `THEME_HOME_LINK` | `true` | Mostrar el enlace `/home` en el pie de página. El valor predeterminado es `true`. | +| `IMPRINT_URL` | `""` | URL personalizada para un imprint alojado en otro sitio. Debe ser accesible públicamente. Tiene prioridad sobre `IMPRINT_HTML`. | +| `IMPRINT_HTML` | `""` | Alternativa a `IMPRINT_URL` para especificar el HTML a mostrar en `/imprint`. Usa solo `IMPRINT_HTML` o `IMPRINT_URL`, no ambos. | ## Despliegue > ℹ️ Se requiere `https` de lo contrario el navegador no soportará las funciones de encriptación. -> ℹ️ Hay un endpoint para verificar el estado, lo encontramos en `/api/health/`. Regresa un código 200 o 503. +> ℹ️ Hay un endpoint para verificar el estado, lo encontramos en `/healthz`. Regresa un código 200 o 503. ### Docker @@ -87,13 +93,13 @@ Docker es la manera más fácil. Aquí encontramos [la imagen oficial](https://h version: "3.8" services: - redis: - image: redis:7-alpine + cache: + image: valkey/valkey:7-alpine # This is required to stay in RAM only. - command: redis-server --save "" --appendonly no + command: valkey-server --save "" --appendonly no # Set a size limit. See link below on how to customise. - # https://redis.io/docs/latest/operate/rs/databases/memory-performance/eviction-policy/ - # --maxmemory 1gb --maxmemory-policy allkeys-lrulpine + # https://valkey.io/docs/latest/operate/rs/databases/memory-performance/eviction-policy/ + # --maxmemory 1g --maxmemory-policy allkeys-lrulpine # This prevents the creation of an anonymous volume. tmpfs: - /data @@ -101,7 +107,7 @@ services: app: image: cupcakearmy/cryptgeon:latest depends_on: - - redis + - cache environment: # Size limit for a single note. SIZE_LIMIT: 4 MiB @@ -110,7 +116,7 @@ services: # Optional health checks # healthcheck: - # test: ["CMD", "curl", "--fail", "http://127.0.0.1:8000/api/live/"] + # test: ["CMD", "curl", "--fail", "http://127.0.0.1:8000/healthz"] # interval: 1m # timeout: 3s # retries: 2 diff --git a/README_zh-CN.md b/README_zh-CN.md index 12fd6c1..71cf389 100644 --- a/README_zh-CN.md +++ b/README_zh-CN.md @@ -36,7 +36,7 @@ _加密鸽_ 是一个受 [_PrivNote_](https://privnote.com)项目启发的安全 加密鸽会为每条笔记都生成一个独立的 id (256bit) 和 key 256(bit)。 -其中id用于保存和提取密信, 在这之后这封密信将会被客户端使用 AES 算法的 GCM 模式和`key`进行加密然后发送至服务器,数据将会保存在服务器的内存中且永远不会被持久化到硬盘上,服务端永远不会得到密钥并且无法解读密信的内容。 +其中id用于保存和提取密信, 在这之后这封密信将会被客户端使用 XChaCha20-Poly1305 加密算法和`key`进行加密然后发送至服务器,数据将会保存在服务器的内存中且永远不会被持久化到硬盘上,服务端永远不会得到密钥并且无法解读密信的内容。 ## 屏幕截图 @@ -44,16 +44,26 @@ _加密鸽_ 是一个受 [_PrivNote_](https://privnote.com)项目启发的安全 ## 环境变量 -| 变量名称 | 默认值 | 描述 | -| ---------------- | ---------------- | --------------------------------------------------------------------------------- | -| `REDIS` | `redis://redis/` | Redis 连接 URL。 | -| `SIZE_LIMIT` | `1 KiB` | 最大请求体(body)限制。有关支持的数值请查看 [字节单位](https://docs.rs/byte-unit/) | -| `MAX_VIEWS` | `100` | 密信最多查看次数限制 | -| `MAX_EXPIRATION` | `360` | 密信最长过期时间限制(分钟) | -| `ALLOW_ADVANCED` | `true` | 是否允许自定义设置,该项如果设为`false`,则不会显示自定义设置模块 | -| `THEME_IMAGE` | `""` | 自定义 Logo 图片,你在这里填写的的图片链接必须是可以公开访问的。 | -| `THEME_TEXT` | `""` | 自定义在 Logo 下方的文本。 | -| `THEME_HOME_LINK` | `true` | 是否在页脚显示 `/home` 链接。默认为 `true`。 | +| 变量名称 | 默认值 | 描述 | +| ---------------- | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------- | +| `CACHE` | `redis://cache/` | 缓存(valkey 或 redis)连接 URL。[连接参数](https://docs.rs/redis/latest/redis/#connection-parameters) | +| `SIZE_LIMIT` | `1 KiB` | 最大请求体(body)限制。可通过 [字节单位](https://docs.rs/byte-unit/) 查看支持的值。负载是原始字节(msgpack + 加密),因此前端显示完整限制。| +| `MAX_VIEWS` | `100` | 密信最多查看次数限制。 | +| `MAX_EXPIRATION` | `360` | 密信最长过期时间限制(分钟)。 | +| `ALLOW_ADVANCED` | `true` | 是否允许自定义设置,该项如果设为`false`,则不会显示自定义设置模块。 | +| `ALLOW_FILES` | `true` | 是否允许上传文件。为 `false` 时用户只能创建文本密信。 | +| `ID_LENGTH` | `32` | 设置密信 `id` 的字节大小。默认 `32` 字节,可用于缩短链接长度。_不影响加密强度_。 | +| `CACHE_PREFIX` | `""` | 缓存键可选前缀。与其它应用通过 ACL namespace 共享缓存实例时有用。 | +| `EXTRA_SIZE_LIMIT` | `512` | 不透明 `extra` 负载(如密钥派生参数)的最大字节数,存于密信元数据。 | +| `VERBOSITY` | `warn` | 后端日志级别。可能值见 [env_logger](https://docs.rs/env_logger/latest/env_logger/#enabling-logging)。 | +| `THEME_IMAGE` | `""` | 自定义 Logo 图片,需可公开访问。 | +| `THEME_TEXT` | `""` | 自定义在 Logo 下方的文本。 | +| `THEME_PAGE_TITLE` | `""` | 自定义页面标题。 | +| `THEME_FAVICON` | `""` | 自定义 favicon 地址,需可公开访问。 | +| `THEME_NEW_NOTE_NOTICE` | `true` | 创建新笔记后显示“笔记存于内存可能被清除”的提示。 | +| `THEME_HOME_LINK` | `true` | 是否在页脚显示 `/home` 链接。默认为 `true`。 | +| `IMPRINT_URL` | `""` | 托管在其它位置的印页 URL,需可公开访问。优先于 `IMPRINT_HTML`。 | +| `IMPRINT_HTML` | `""` | `IMPRINT_URL` 的替代:指定 `/imprint` 展示的 HTML。`IMPRINT_HTML` 与 `IMPRINT_URL` 只应指定其一。 | | ## 部署 @@ -70,12 +80,12 @@ Docker 是最简单的部署方式。这里是[官方镜像的地址](https://hu version: "3.8" services: - redis: - image: redis:7-alpine + cache: + image: valkey/valkey:7-alpine # This is required to stay in RAM only. - command: redis-server --save "" --appendonly no + command: valkey-server --save "" --appendonly no # Set a size limit. See link below on how to customise. - # https://redis.io/docs/latest/operate/rs/databases/memory-performance/eviction-policy/ + # https://valkey.io/docs/latest/operate/rs/databases/memory-performance/eviction-policy/ # --maxmemory 1gb --maxmemory-policy allkeys-lrulpine # This prevents the creation of an anonymous volume. tmpfs: @@ -84,7 +94,7 @@ services: app: image: cupcakearmy/cryptgeon:latest depends_on: - - redis + - cache environment: SIZE_LIMIT: 4 MiB ports: @@ -112,12 +122,12 @@ networks: external: true services: - redis: - image: redis:7-alpine + cache: + image: valkey/valkey:7-alpine # This is required to stay in RAM only. - command: redis-server --save "" --appendonly no + command: valkey-server --save "" --appendonly no # Set a size limit. See link below on how to customise. - # https://redis.io/docs/latest/operate/rs/databases/memory-performance/eviction-policy/ + # https://valkey.io/docs/latest/operate/rs/databases/memory-performance/eviction-policy/ # --maxmemory 1gb --maxmemory-policy allkeys-lrulpine # This prevents the creation of an anonymous volume. tmpfs: @@ -127,7 +137,7 @@ services: image: cupcakearmy/cryptgeon:latest restart: unless-stopped depends_on: - - redis + - cache networks: - default - proxy From b5c5629ccaa155593bf9c6a08d3407884428310f Mon Sep 17 00:00:00 2001 From: cupcakearmy Date: Sat, 12 Sep 2026 15:01:47 +0200 Subject: [PATCH 22/46] chore: formatting --- README_ES.md | 40 ++++++++++++++++++++-------------------- README_zh-CN.md | 40 ++++++++++++++++++++-------------------- 2 files changed, 40 insertions(+), 40 deletions(-) diff --git a/README_ES.md b/README_ES.md index 2035ef2..f70f1fa 100644 --- a/README_ES.md +++ b/README_ES.md @@ -56,26 +56,26 @@ se usa para guardar y recuperar la nota. Después la nota es encriptada con XCha ## Variables de entorno -| Variable | Default | Descripción | -| ------------------ | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `CACHE` | `redis://cache/` | URL de caché (valkey o redis) a la que conectarse. [Según el formato](https://docs.rs/redis/latest/redis/#connection-parameters) | -| `SIZE_LIMIT` | `1 KiB` | Tamaño máximo del cuerpo. Valores aceptados según [byte-unit](https://docs.rs/byte-unit/).
`512 MiB` es el máximo permitido.
Los payloads son bytes crudos (msgpack + cifrado), por lo que el frontend muestra el límite completo. | -| `MAX_VIEWS` | `100` | Número máximo de vistas. | -| `MAX_EXPIRATION` | `360` | Tiempo máximo de expiración en minutos. | -| `ALLOW_ADVANCED` | `true` | Permitir configuración personalizada. Si se establece en `false` todas las notas serán de una sola vista. | -| `ALLOW_FILES` | `true` | Permitir subir archivos. Si es `false`, los usuarios solo podrán crear notas de texto. | -| `ID_LENGTH` | `32` | Establece el tamaño en bytes de la `id` de la nota. Por defecto es de `32` bytes. Útil para reducir el tamaño del link. _No afecta el nivel de encriptación_. | -| `CACHE_PREFIX` | `""` | Prefijo opcional para las claves de caché. Útil al compartir una instancia con otras apps vía namespaces ACL. | -| `EXTRA_SIZE_LIMIT` | `512` | Tamaño máximo en bytes del payload `extra` opaco (p. ej. parámetros de derivación de clave) guardado en los metadatos de la nota. | -| `VERBOSITY` | `warn` | Nivel de verbosidad del backend. [Posibles valores](https://docs.rs/env_logger/latest/env_logger/#enabling-logging): `error`, `warn`, `info`, `debug`, `trace` | -| `THEME_IMAGE` | `""` | Imagen personalizada para reemplazar el logo. Debe ser accesible públicamente. | -| `THEME_TEXT` | `""` | Texto personalizado para reemplazar la descripción bajo el logo. | -| `THEME_PAGE_TITLE` | `""` | Texto personalizado para el título. | -| `THEME_FAVICON` | `""` | Url personalizada para el favicon. Debe ser accesible públicamente. | -| `THEME_NEW_NOTE_NOTICE` | `true` | Mostrar el mensaje sobre cómo se almacenan las notas en memoria (pueden ser expulsadas) al crear una nueva nota. | -| `THEME_HOME_LINK` | `true` | Mostrar el enlace `/home` en el pie de página. El valor predeterminado es `true`. | -| `IMPRINT_URL` | `""` | URL personalizada para un imprint alojado en otro sitio. Debe ser accesible públicamente. Tiene prioridad sobre `IMPRINT_HTML`. | -| `IMPRINT_HTML` | `""` | Alternativa a `IMPRINT_URL` para especificar el HTML a mostrar en `/imprint`. Usa solo `IMPRINT_HTML` o `IMPRINT_URL`, no ambos. | +| Variable | Default | Descripción | +| ----------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `CACHE` | `redis://cache/` | URL de caché (valkey o redis) a la que conectarse. [Según el formato](https://docs.rs/redis/latest/redis/#connection-parameters) | +| `SIZE_LIMIT` | `1 KiB` | Tamaño máximo del cuerpo. Valores aceptados según [byte-unit](https://docs.rs/byte-unit/).
`512 MiB` es el máximo permitido.
Los payloads son bytes crudos (msgpack + cifrado), por lo que el frontend muestra el límite completo. | +| `MAX_VIEWS` | `100` | Número máximo de vistas. | +| `MAX_EXPIRATION` | `360` | Tiempo máximo de expiración en minutos. | +| `ALLOW_ADVANCED` | `true` | Permitir configuración personalizada. Si se establece en `false` todas las notas serán de una sola vista. | +| `ALLOW_FILES` | `true` | Permitir subir archivos. Si es `false`, los usuarios solo podrán crear notas de texto. | +| `ID_LENGTH` | `32` | Establece el tamaño en bytes de la `id` de la nota. Por defecto es de `32` bytes. Útil para reducir el tamaño del link. _No afecta el nivel de encriptación_. | +| `CACHE_PREFIX` | `""` | Prefijo opcional para las claves de caché. Útil al compartir una instancia con otras apps vía namespaces ACL. | +| `EXTRA_SIZE_LIMIT` | `512` | Tamaño máximo en bytes del payload `extra` opaco (p. ej. parámetros de derivación de clave) guardado en los metadatos de la nota. | +| `VERBOSITY` | `warn` | Nivel de verbosidad del backend. [Posibles valores](https://docs.rs/env_logger/latest/env_logger/#enabling-logging): `error`, `warn`, `info`, `debug`, `trace` | +| `THEME_IMAGE` | `""` | Imagen personalizada para reemplazar el logo. Debe ser accesible públicamente. | +| `THEME_TEXT` | `""` | Texto personalizado para reemplazar la descripción bajo el logo. | +| `THEME_PAGE_TITLE` | `""` | Texto personalizado para el título. | +| `THEME_FAVICON` | `""` | Url personalizada para el favicon. Debe ser accesible públicamente. | +| `THEME_NEW_NOTE_NOTICE` | `true` | Mostrar el mensaje sobre cómo se almacenan las notas en memoria (pueden ser expulsadas) al crear una nueva nota. | +| `THEME_HOME_LINK` | `true` | Mostrar el enlace `/home` en el pie de página. El valor predeterminado es `true`. | +| `IMPRINT_URL` | `""` | URL personalizada para un imprint alojado en otro sitio. Debe ser accesible públicamente. Tiene prioridad sobre `IMPRINT_HTML`. | +| `IMPRINT_HTML` | `""` | Alternativa a `IMPRINT_URL` para especificar el HTML a mostrar en `/imprint`. Usa solo `IMPRINT_HTML` o `IMPRINT_URL`, no ambos. | ## Despliegue diff --git a/README_zh-CN.md b/README_zh-CN.md index 71cf389..bd86e70 100644 --- a/README_zh-CN.md +++ b/README_zh-CN.md @@ -44,26 +44,26 @@ _加密鸽_ 是一个受 [_PrivNote_](https://privnote.com)项目启发的安全 ## 环境变量 -| 变量名称 | 默认值 | 描述 | -| ---------------- | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------- | -| `CACHE` | `redis://cache/` | 缓存(valkey 或 redis)连接 URL。[连接参数](https://docs.rs/redis/latest/redis/#connection-parameters) | -| `SIZE_LIMIT` | `1 KiB` | 最大请求体(body)限制。可通过 [字节单位](https://docs.rs/byte-unit/) 查看支持的值。负载是原始字节(msgpack + 加密),因此前端显示完整限制。| -| `MAX_VIEWS` | `100` | 密信最多查看次数限制。 | -| `MAX_EXPIRATION` | `360` | 密信最长过期时间限制(分钟)。 | -| `ALLOW_ADVANCED` | `true` | 是否允许自定义设置,该项如果设为`false`,则不会显示自定义设置模块。 | -| `ALLOW_FILES` | `true` | 是否允许上传文件。为 `false` 时用户只能创建文本密信。 | -| `ID_LENGTH` | `32` | 设置密信 `id` 的字节大小。默认 `32` 字节,可用于缩短链接长度。_不影响加密强度_。 | -| `CACHE_PREFIX` | `""` | 缓存键可选前缀。与其它应用通过 ACL namespace 共享缓存实例时有用。 | -| `EXTRA_SIZE_LIMIT` | `512` | 不透明 `extra` 负载(如密钥派生参数)的最大字节数,存于密信元数据。 | -| `VERBOSITY` | `warn` | 后端日志级别。可能值见 [env_logger](https://docs.rs/env_logger/latest/env_logger/#enabling-logging)。 | -| `THEME_IMAGE` | `""` | 自定义 Logo 图片,需可公开访问。 | -| `THEME_TEXT` | `""` | 自定义在 Logo 下方的文本。 | -| `THEME_PAGE_TITLE` | `""` | 自定义页面标题。 | -| `THEME_FAVICON` | `""` | 自定义 favicon 地址,需可公开访问。 | -| `THEME_NEW_NOTE_NOTICE` | `true` | 创建新笔记后显示“笔记存于内存可能被清除”的提示。 | -| `THEME_HOME_LINK` | `true` | 是否在页脚显示 `/home` 链接。默认为 `true`。 | -| `IMPRINT_URL` | `""` | 托管在其它位置的印页 URL,需可公开访问。优先于 `IMPRINT_HTML`。 | -| `IMPRINT_HTML` | `""` | `IMPRINT_URL` 的替代:指定 `/imprint` 展示的 HTML。`IMPRINT_HTML` 与 `IMPRINT_URL` 只应指定其一。 | | +| 变量名称 | 默认值 | 描述 | +| ----------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | +| `CACHE` | `redis://cache/` | 缓存(valkey 或 redis)连接 URL。[连接参数](https://docs.rs/redis/latest/redis/#connection-parameters) | +| `SIZE_LIMIT` | `1 KiB` | 最大请求体(body)限制。可通过 [字节单位](https://docs.rs/byte-unit/) 查看支持的值。负载是原始字节(msgpack + 加密),因此前端显示完整限制。 | +| `MAX_VIEWS` | `100` | 密信最多查看次数限制。 | +| `MAX_EXPIRATION` | `360` | 密信最长过期时间限制(分钟)。 | +| `ALLOW_ADVANCED` | `true` | 是否允许自定义设置,该项如果设为`false`,则不会显示自定义设置模块。 | +| `ALLOW_FILES` | `true` | 是否允许上传文件。为 `false` 时用户只能创建文本密信。 | +| `ID_LENGTH` | `32` | 设置密信 `id` 的字节大小。默认 `32` 字节,可用于缩短链接长度。_不影响加密强度_。 | +| `CACHE_PREFIX` | `""` | 缓存键可选前缀。与其它应用通过 ACL namespace 共享缓存实例时有用。 | +| `EXTRA_SIZE_LIMIT` | `512` | 不透明 `extra` 负载(如密钥派生参数)的最大字节数,存于密信元数据。 | +| `VERBOSITY` | `warn` | 后端日志级别。可能值见 [env_logger](https://docs.rs/env_logger/latest/env_logger/#enabling-logging)。 | +| `THEME_IMAGE` | `""` | 自定义 Logo 图片,需可公开访问。 | +| `THEME_TEXT` | `""` | 自定义在 Logo 下方的文本。 | +| `THEME_PAGE_TITLE` | `""` | 自定义页面标题。 | +| `THEME_FAVICON` | `""` | 自定义 favicon 地址,需可公开访问。 | +| `THEME_NEW_NOTE_NOTICE` | `true` | 创建新笔记后显示“笔记存于内存可能被清除”的提示。 | +| `THEME_HOME_LINK` | `true` | 是否在页脚显示 `/home` 链接。默认为 `true`。 | +| `IMPRINT_URL` | `""` | 托管在其它位置的印页 URL,需可公开访问。优先于 `IMPRINT_HTML`。 | +| `IMPRINT_HTML` | `""` | `IMPRINT_URL` 的替代:指定 `/imprint` 展示的 HTML。`IMPRINT_HTML` 与 `IMPRINT_URL` 只应指定其一。 | | ## 部署 From c3c6e9677402c21e175a0796e5002daa3632fd62 Mon Sep 17 00:00:00 2001 From: cupcakearmy Date: Sat, 12 Sep 2026 15:02:20 +0200 Subject: [PATCH 23/46] docs(examples): rename cache service in compose examples to match CACHE env --- examples/nginx/docker-compose.yaml | 4 ++-- examples/scratch/README.md | 4 ++-- examples/traefik/README.md | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/nginx/docker-compose.yaml b/examples/nginx/docker-compose.yaml index 16cd0b0..b877d59 100644 --- a/examples/nginx/docker-compose.yaml +++ b/examples/nginx/docker-compose.yaml @@ -1,7 +1,7 @@ version: '3.8' services: - redis: + cache: image: valkey/valkey:7-alpine # This is required to stay in RAM only. command: valkey-server --save "" --appendonly no @@ -15,7 +15,7 @@ services: app: image: cupcakearmy/cryptgeon:latest depends_on: - - redis + - cache proxy: image: nginx:alpine diff --git a/examples/scratch/README.md b/examples/scratch/README.md index f403ef9..bad1eed 100644 --- a/examples/scratch/README.md +++ b/examples/scratch/README.md @@ -107,7 +107,7 @@ networks: external: true services: - redis: + cache: image: valkey/valkey:7-alpine # This is required to stay in RAM only. command: valkey-server --save "" --appendonly no @@ -122,7 +122,7 @@ services: image: cupcakearmy/cryptgeon:latest restart: unless-stopped depends_on: - - redis + - cache environment: SIZE_LIMIT: 4 MiB networks: diff --git a/examples/traefik/README.md b/examples/traefik/README.md index c087dde..4eca487 100644 --- a/examples/traefik/README.md +++ b/examples/traefik/README.md @@ -16,7 +16,7 @@ networks: external: true services: - redis: + cache: image: valkey/valkey:7-alpine # This is required to stay in RAM only. command: valkey-server --save "" --appendonly no @@ -31,7 +31,7 @@ services: image: cupcakearmy/cryptgeon:latest restart: unless-stopped depends_on: - - redis + - cache networks: - default - proxy @@ -60,7 +60,7 @@ services: volumes: - "/var/run/docker.sock:/var/run/docker.sock:ro" - redis: + cache: image: valkey/valkey:7-alpine # This is required to stay in RAM only. command: valkey-server --save "" --appendonly no @@ -74,7 +74,7 @@ services: cryptgeon: image: cupcakearmy/cryptgeon depends_on: - - redis + - cache labels: - "traefik.enable=true" - "traefik.http.routers.cryptgeon.rule=Host(`cryptgeon.localhost`)" From 56f8498530b8300c2fda7f771918b1d5ab1041ac Mon Sep 17 00:00:00 2001 From: cupcakearmy Date: Sat, 12 Sep 2026 15:03:15 +0200 Subject: [PATCH 24/46] docs: drop deprecated compose version tag --- README.md | 1 - README_ES.md | 1 - README_zh-CN.md | 2 -- examples/nginx/docker-compose.yaml | 1 - examples/scratch/README.md | 3 --- examples/traefik/README.md | 1 - 6 files changed, 9 deletions(-) diff --git a/README.md b/README.md index 09bc4b6..ab0c304 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,6 @@ Docker is the easiest way. There is the [official image here](https://hub.docker ```yaml # docker-compose.yml -version: "3.8" services: cache: diff --git a/README_ES.md b/README_ES.md index f70f1fa..4541ca4 100644 --- a/README_ES.md +++ b/README_ES.md @@ -90,7 +90,6 @@ Docker es la manera más fácil. Aquí encontramos [la imagen oficial](https://h ```yaml # docker-compose.yml -version: "3.8" services: cache: diff --git a/README_zh-CN.md b/README_zh-CN.md index bd86e70..5871de5 100644 --- a/README_zh-CN.md +++ b/README_zh-CN.md @@ -77,7 +77,6 @@ Docker 是最简单的部署方式。这里是[官方镜像的地址](https://hu ```yaml # docker-compose.yml -version: "3.8" services: cache: @@ -115,7 +114,6 @@ services: - 域名 `example.org` ```yaml -version: "3.8" networks: proxy: diff --git a/examples/nginx/docker-compose.yaml b/examples/nginx/docker-compose.yaml index b877d59..7b932f5 100644 --- a/examples/nginx/docker-compose.yaml +++ b/examples/nginx/docker-compose.yaml @@ -1,4 +1,3 @@ -version: '3.8' services: cache: diff --git a/examples/scratch/README.md b/examples/scratch/README.md index bad1eed..b2af4b2 100644 --- a/examples/scratch/README.md +++ b/examples/scratch/README.md @@ -25,7 +25,6 @@ This is a tiny guide to install cryptgeon on (probably) any unix system (and may ```yaml # docker-compose.yaml -version: '3.8' services: traefik: image: traefik:2.6 @@ -100,7 +99,6 @@ Create another docker-compose.yaml file in another folder. We will assume that t ``` ```yaml -version: '3.8' networks: proxy: @@ -155,7 +153,6 @@ docker-compose up -d ```yaml # docker-compose.yaml -version: '3.8' services: watchtower: diff --git a/examples/traefik/README.md b/examples/traefik/README.md index 4eca487..eeb952a 100644 --- a/examples/traefik/README.md +++ b/examples/traefik/README.md @@ -9,7 +9,6 @@ Assumptions: - Domain name `example.org`. ```yaml -version: '3.8' networks: proxy: From 8a3831fd05de8ecb3c443825ecbfe4f2dd7d3b2e Mon Sep 17 00:00:00 2001 From: cupcakearmy Date: Sat, 12 Sep 2026 15:04:04 +0200 Subject: [PATCH 25/46] chore: formatting --- README.md | 1 - README_ES.md | 1 - README_zh-CN.md | 1 - examples/scratch/README.md | 26 ++++++++++++-------------- examples/traefik/README.md | 1 - 5 files changed, 12 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index ab0c304..7d11cde 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,6 @@ Docker is the easiest way. There is the [official image here](https://hub.docker ```yaml # docker-compose.yml - services: cache: image: valkey/valkey:7-alpine diff --git a/README_ES.md b/README_ES.md index 4541ca4..f5cbfc0 100644 --- a/README_ES.md +++ b/README_ES.md @@ -90,7 +90,6 @@ Docker es la manera más fácil. Aquí encontramos [la imagen oficial](https://h ```yaml # docker-compose.yml - services: cache: image: valkey/valkey:7-alpine diff --git a/README_zh-CN.md b/README_zh-CN.md index 5871de5..68e61c3 100644 --- a/README_zh-CN.md +++ b/README_zh-CN.md @@ -114,7 +114,6 @@ services: - 域名 `example.org` ```yaml - networks: proxy: external: true diff --git a/examples/scratch/README.md b/examples/scratch/README.md index b2af4b2..87560e4 100644 --- a/examples/scratch/README.md +++ b/examples/scratch/README.md @@ -30,21 +30,21 @@ services: image: traefik:2.6 restart: unless-stopped ports: - - '80:80' - - '443:443' + - "80:80" + - "443:443" volumes: - /var/run/docker.sock:/var/run/docker.sock - ./traefik.yaml:/etc/traefik/traefik.yaml:ro - ./data:/data labels: - - 'traefik.enable=true' + - "traefik.enable=true" # HTTP to HTTPS redirection - - 'traefik.http.routers.http_catchall.rule=HostRegexp(`{any:.+}`)' - - 'traefik.http.routers.http_catchall.entrypoints=insecure' - - 'traefik.http.routers.http_catchall.middlewares=https_redirect' - - 'traefik.http.middlewares.https_redirect.redirectscheme.scheme=https' - - 'traefik.http.middlewares.https_redirect.redirectscheme.permanent=true' + - "traefik.http.routers.http_catchall.rule=HostRegexp(`{any:.+}`)" + - "traefik.http.routers.http_catchall.entrypoints=insecure" + - "traefik.http.routers.http_catchall.middlewares=https_redirect" + - "traefik.http.middlewares.https_redirect.redirectscheme.scheme=https" + - "traefik.http.middlewares.https_redirect.redirectscheme.permanent=true" networks: default: @@ -61,15 +61,15 @@ api: # Define HTTP and HTTPS entrypoint entryPoints: insecure: - address: ':80' + address: ":80" secure: - address: ':443' + address: ":443" # Dynamic configuration will come from docker labels providers: docker: - endpoint: 'unix:///var/run/docker.sock' - network: 'proxy' + endpoint: "unix:///var/run/docker.sock" + network: "proxy" exposedByDefault: false # Enable acme with http file challenge @@ -99,7 +99,6 @@ Create another docker-compose.yaml file in another folder. We will assume that t ``` ```yaml - networks: proxy: external: true @@ -153,7 +152,6 @@ docker-compose up -d ```yaml # docker-compose.yaml - services: watchtower: image: containrrr/watchtower diff --git a/examples/traefik/README.md b/examples/traefik/README.md index eeb952a..e863fc7 100644 --- a/examples/traefik/README.md +++ b/examples/traefik/README.md @@ -9,7 +9,6 @@ Assumptions: - Domain name `example.org`. ```yaml - networks: proxy: external: true From ba7669514e2f523dfffa20ac245038ac60610c75 Mon Sep 17 00:00:00 2001 From: cupcakearmy Date: Sat, 12 Sep 2026 15:05:36 +0200 Subject: [PATCH 26/46] Delete Cryptgeon.postman_collection.json --- Cryptgeon.postman_collection.json | 614 ------------------------------ 1 file changed, 614 deletions(-) delete mode 100644 Cryptgeon.postman_collection.json diff --git a/Cryptgeon.postman_collection.json b/Cryptgeon.postman_collection.json deleted file mode 100644 index 655151f..0000000 --- a/Cryptgeon.postman_collection.json +++ /dev/null @@ -1,614 +0,0 @@ -{ - "info": { - "_postman_id": "3aaeac19-4eac-4911-b3c8-912b17a48634", - "name": "Cryptgeon", - "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" - }, - "item": [ - { - "name": "Notes", - "item": [ - { - "name": "Preview", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "{{BASE}}/notes/:id", - "host": ["{{BASE}}"], - "path": ["notes", ":id"], - "variable": [ - { - "key": "id", - "value": "{{NOTE_ID}}", - "description": "Id of the Note" - } - ] - }, - "description": "This endpoint is to query wether a note exists, without actually opening it. No view limits are used here, as contents of the note are not available, only the `meta` field is returned, which is public." - }, - "response": [ - { - "name": "200", - "originalRequest": { - "method": "GET", - "header": [], - "url": { - "raw": "{{BASE}}/notes/:id", - "host": ["{{BASE}}"], - "path": ["notes", ":id"], - "variable": [ - { - "key": "id", - "value": "{{NOTE_ID}}", - "description": "Id of the Note" - } - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "transfer-encoding", - "value": "chunked" - }, - { - "key": "connection", - "value": "close" - }, - { - "key": "content-type", - "value": "application/json" - }, - { - "key": "content-encoding", - "value": "gzip" - }, - { - "key": "vary", - "value": "accept-encoding" - }, - { - "key": "date", - "value": "Tue, 23 May 2023 05:24:29 GMT" - } - ], - "cookie": [], - "body": "{}" - }, - { - "name": "404", - "originalRequest": { - "method": "GET", - "header": [], - "url": { - "raw": "{{BASE}}/notes/:id", - "host": ["{{BASE}}"], - "path": ["notes", ":id"], - "variable": [ - { - "key": "id", - "value": "{{NOTE_ID}}", - "description": "Id of the Note" - } - ] - } - }, - "status": "Not Found", - "code": 404, - "_postman_previewlanguage": "plain", - "header": [ - { - "key": "transfer-encoding", - "value": "chunked" - }, - { - "key": "connection", - "value": "close" - }, - { - "key": "vary", - "value": "accept-encoding" - }, - { - "key": "content-encoding", - "value": "gzip" - }, - { - "key": "date", - "value": "Tue, 23 May 2023 05:25:26 GMT" - } - ], - "cookie": [], - "body": "" - } - ] - }, - { - "name": "Create", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "const jsonData = pm.response.json();", - "pm.collectionVariables.set('NOTE_ID', jsonData.id)", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"contents\": \"Some encrypted content\",\n \"views\": 1,\n \"meta\": \"{\\\"type\\\":\\\"text\\\"}\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{BASE}}/notes/", - "host": ["{{BASE}}"], - "path": ["notes", ""] - } - }, - "response": [ - { - "name": "Simple", - "originalRequest": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"contents\": \"Some encrypted content\",\n \"views\": 1,\n \"meta\": \"{\\\"type\\\":\\\"text\\\"}\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{BASE}}/notes/", - "host": ["{{BASE}}"], - "path": ["notes", ""] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "transfer-encoding", - "value": "chunked" - }, - { - "key": "connection", - "value": "close" - }, - { - "key": "content-encoding", - "value": "gzip" - }, - { - "key": "content-type", - "value": "application/json" - }, - { - "key": "vary", - "value": "accept-encoding" - }, - { - "key": "date", - "value": "Tue, 23 May 2023 05:31:54 GMT" - } - ], - "cookie": [], - "body": "{\n \"id\": \"1QeEWDQbQY9dOo8cDDQjykaEjouqugTR6A78sjgn4VMv\"\n}" - }, - { - "name": "5 Minutes", - "originalRequest": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"contents\": \"Some encrypted content\",\n \"expiration\": 5,\n \"meta\": \"{\\\"type\\\":\\\"text\\\"}\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{BASE}}/notes/", - "host": ["{{BASE}}"], - "path": ["notes", ""] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "transfer-encoding", - "value": "chunked" - }, - { - "key": "connection", - "value": "close" - }, - { - "key": "content-encoding", - "value": "gzip" - }, - { - "key": "content-type", - "value": "application/json" - }, - { - "key": "vary", - "value": "accept-encoding" - }, - { - "key": "date", - "value": "Tue, 23 May 2023 05:31:54 GMT" - } - ], - "cookie": [], - "body": "{\n \"id\": \"1QeEWDQbQY9dOo8cDDQjykaEjouqugTR6A78sjgn4VMv\"\n}" - }, - { - "name": "3 Views", - "originalRequest": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"contents\": \"Some encrypted content\",\n \"views\": 3,\n \"meta\": \"{\\\"type\\\":\\\"text\\\"}\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{BASE}}/notes/", - "host": ["{{BASE}}"], - "path": ["notes", ""] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "transfer-encoding", - "value": "chunked" - }, - { - "key": "connection", - "value": "close" - }, - { - "key": "content-encoding", - "value": "gzip" - }, - { - "key": "content-type", - "value": "application/json" - }, - { - "key": "vary", - "value": "accept-encoding" - }, - { - "key": "date", - "value": "Tue, 23 May 2023 05:31:54 GMT" - } - ], - "cookie": [], - "body": "{\n \"id\": \"1QeEWDQbQY9dOo8cDDQjykaEjouqugTR6A78sjgn4VMv\"\n}" - } - ] - }, - { - "name": "Read", - "request": { - "method": "DELETE", - "header": [], - "url": { - "raw": "{{BASE}}/notes/:id", - "host": ["{{BASE}}"], - "path": ["notes", ":id"], - "variable": [ - { - "key": "id", - "value": "{{NOTE_ID}}" - } - ] - }, - "description": "This endpoint gets the actual contents of a note. It's a `DELETE` endpoint, es it decreases the `view` counter, and deletes the note if `0` is reached." - }, - "response": [ - { - "name": "200", - "originalRequest": { - "method": "DELETE", - "header": [], - "url": { - "raw": "{{BASE}}/notes/:id", - "host": ["{{BASE}}"], - "path": ["notes", ":id"], - "variable": [ - { - "key": "id", - "value": "{{NOTE_ID}}" - } - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "transfer-encoding", - "value": "chunked" - }, - { - "key": "connection", - "value": "close" - }, - { - "key": "content-type", - "value": "application/json" - }, - { - "key": "vary", - "value": "accept-encoding" - }, - { - "key": "content-encoding", - "value": "gzip" - }, - { - "key": "date", - "value": "Tue, 23 May 2023 05:59:07 GMT" - } - ], - "cookie": [], - "body": "{\n \"meta\": \"{\\\"type\\\":\\\"text\\\"}\",\n \"contents\": \"Some encrypted content\"\n}" - }, - { - "name": "404", - "originalRequest": { - "method": "DELETE", - "header": [], - "url": { - "raw": "{{BASE}}/notes/:id", - "host": ["{{BASE}}"], - "path": ["notes", ":id"], - "variable": [ - { - "key": "id", - "value": "{{NOTE_ID}}" - } - ] - } - }, - "status": "Not Found", - "code": 404, - "_postman_previewlanguage": "plain", - "header": [ - { - "key": "transfer-encoding", - "value": "chunked" - }, - { - "key": "connection", - "value": "close" - }, - { - "key": "vary", - "value": "accept-encoding" - }, - { - "key": "content-encoding", - "value": "gzip" - }, - { - "key": "date", - "value": "Tue, 23 May 2023 05:59:15 GMT" - } - ], - "cookie": [], - "body": "" - } - ] - } - ] - }, - { - "name": "Status", - "item": [ - { - "name": "Get server status", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "{{BASE}}/status/", - "host": ["{{BASE}}"], - "path": ["status", ""] - } - }, - "response": [ - { - "name": "200", - "originalRequest": { - "method": "GET", - "header": [], - "url": { - "raw": "{{BASE}}/status/", - "host": ["{{BASE}}"], - "path": ["status", ""] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "transfer-encoding", - "value": "chunked" - }, - { - "key": "connection", - "value": "close" - }, - { - "key": "content-encoding", - "value": "gzip" - }, - { - "key": "vary", - "value": "accept-encoding" - }, - { - "key": "content-type", - "value": "application/json" - }, - { - "key": "date", - "value": "Tue, 23 May 2023 05:56:45 GMT" - } - ], - "cookie": [], - "body": "{\n \"version\": \"2.3.0-beta.4\",\n \"max_size\": 10485760,\n \"max_views\": 100,\n \"max_expiration\": 360,\n \"allow_advanced\": true,\n \"theme_image\": \"\",\n \"theme_text\": \"\",\n \"theme_page_title\": \"\",\n \"theme_favicon\": \"\"\n}" - } - ] - }, - { - "name": "Health Check", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "{{BASE}}/live/", - "host": ["{{BASE}}"], - "path": ["live", ""] - }, - "description": "Return `200` for healthy service. `503` if service is unavailable." - }, - "response": [ - { - "name": "Healthy", - "originalRequest": { - "method": "GET", - "header": [], - "url": { - "raw": "{{BASE}}/live/", - "host": ["{{BASE}}"], - "path": ["live", ""] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "plain", - "header": [ - { - "key": "transfer-encoding", - "value": "chunked" - }, - { - "key": "vary", - "value": "accept-encoding" - }, - { - "key": "content-encoding", - "value": "gzip" - }, - { - "key": "date", - "value": "Thu, 22 Jun 2023 20:17:58 GMT" - } - ], - "cookie": [], - "body": null - }, - { - "name": "Service Unavilable", - "originalRequest": { - "method": "GET", - "header": [], - "url": { - "raw": "{{BASE}}/live/", - "host": ["{{BASE}}"], - "path": ["live", ""] - } - }, - "status": "Service Unavailable", - "code": 503, - "_postman_previewlanguage": "plain", - "header": [ - { - "key": "transfer-encoding", - "value": "chunked" - }, - { - "key": "content-encoding", - "value": "gzip" - }, - { - "key": "vary", - "value": "accept-encoding" - }, - { - "key": "date", - "value": "Thu, 22 Jun 2023 20:18:55 GMT" - } - ], - "cookie": [], - "body": null - } - ] - } - ] - } - ], - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [""] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [""] - } - } - ], - "variable": [ - { - "key": "BASE", - "value": "http://localhost:3000/api", - "type": "default" - }, - { - "key": "NOTE_ID", - "value": "", - "type": "default" - } - ] -} From 41d0f0bfa2232cc4e6984b28d21b06ed8f2295f6 Mon Sep 17 00:00:00 2001 From: cupcakearmy Date: Sat, 12 Sep 2026 15:10:51 +0200 Subject: [PATCH 27/46] chore: bump to 3.0.0-rc.0 and publish npm prereleases under rc tag --- .github/workflows/release.yml | 8 +++++++- packages/backend/Cargo.lock | 2 +- packages/backend/Cargo.toml | 2 +- packages/cli/package.json | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2d0b1fb..cf10611 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,7 +23,13 @@ jobs: pnpm install pnpm --filter cryptgeon build - - run: pnpm publish --filter cryptgeon + - name: Publish to npm + run: | + DIST_TAG=latest + if [[ "${GITHUB_REF_NAME}" == *"-"* ]]; then + DIST_TAG=rc + fi + pnpm publish --filter cryptgeon --tag "${DIST_TAG}" env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/packages/backend/Cargo.lock b/packages/backend/Cargo.lock index dba5e3c..2e29d31 100644 --- a/packages/backend/Cargo.lock +++ b/packages/backend/Cargo.lock @@ -252,7 +252,7 @@ dependencies = [ [[package]] name = "cryptgeon" -version = "3.0.0" +version = "3.0.0-rc.0" dependencies = [ "axum", "bs62", diff --git a/packages/backend/Cargo.toml b/packages/backend/Cargo.toml index 2646a8f..793d5ab 100644 --- a/packages/backend/Cargo.toml +++ b/packages/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cryptgeon" -version = "3.0.0" +version = "3.0.0-rc.0" authors = ["cupcakearmy "] edition = "2024" rust-version = "1.95" diff --git a/packages/cli/package.json b/packages/cli/package.json index b6d377d..f5bd9b0 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "cryptgeon", - "version": "3.0.0", + "version": "3.0.0-rc.0", "homepage": "https://github.com/cupcakearmy/cryptgeon", "repository": { "type": "git", From a0bca0a1a7ab32eb01113c1f9205c83975828b6d Mon Sep 17 00:00:00 2001 From: cupcakearmy Date: Sat, 12 Sep 2026 15:17:56 +0200 Subject: [PATCH 28/46] chore(cli): move all deps to devDependencies (bundled into single output) --- packages/cli/package.json | 10 ++++------ pnpm-lock.yaml | 21 ++++++++++----------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index f5bd9b0..9c97021 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -23,19 +23,17 @@ "dev": "vp pack --watch", "prepublishOnly": "run-s build" }, - "dependencies": { + "devDependencies": { "@cryptgeon/shared": "workspace:*", "@msgpack/msgpack": "^3.1.3", "@commander-js/extra-typings": "^12.1.0", - "inquirer": "^9.3.8", - "mime": "^4.1.0", - "pretty-bytes": "^6.1.1" - }, - "devDependencies": { "@tsconfig/strictest": "catalog:", "@types/inquirer": "^9.0.9", "@types/node": "^22.15.3", "commander": "^12.1.0", + "inquirer": "^9.3.8", + "mime": "^4.1.0", + "pretty-bytes": "^6.1.1", "typescript": "catalog:", "vite-plus": "catalog:" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f6fe5f1..03c6d41 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,7 +30,7 @@ importers: packages/backend: {} packages/cli: - dependencies: + devDependencies: '@commander-js/extra-typings': specifier: ^12.1.0 version: 12.1.0(commander@12.1.0) @@ -40,16 +40,6 @@ importers: '@msgpack/msgpack': specifier: ^3.1.3 version: 3.1.3 - inquirer: - specifier: ^9.3.8 - version: 9.3.8(@types/node@22.20.0) - mime: - specifier: ^4.1.0 - version: 4.1.0 - pretty-bytes: - specifier: ^6.1.1 - version: 6.1.1 - devDependencies: '@tsconfig/strictest': specifier: 'catalog:' version: 2.0.8 @@ -62,6 +52,15 @@ importers: commander: specifier: ^12.1.0 version: 12.1.0 + inquirer: + specifier: ^9.3.8 + version: 9.3.8(@types/node@22.20.0) + mime: + specifier: ^4.1.0 + version: 4.1.0 + pretty-bytes: + specifier: ^6.1.1 + version: 6.1.1 typescript: specifier: 'catalog:' version: 7.0.2 From 6de97039d348d3e3496a70a9e3fb2f381697d326 Mon Sep 17 00:00:00 2001 From: cupcakearmy Date: Sat, 12 Sep 2026 15:27:29 +0200 Subject: [PATCH 29/46] ci: publish with --no-git-checks and drop done cli-deps todo --- .github/workflows/release.yml | 2 +- docs/roadmap.md | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cf10611..94377b9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,7 +29,7 @@ jobs: if [[ "${GITHUB_REF_NAME}" == *"-"* ]]; then DIST_TAG=rc fi - pnpm publish --filter cryptgeon --tag "${DIST_TAG}" + pnpm publish --filter cryptgeon --tag "${DIST_TAG}" --no-git-checks env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/docs/roadmap.md b/docs/roadmap.md index e76b8fc..60a1a9f 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -6,7 +6,6 @@ - Add remaining shared tooling to the pnpm catalog (`vite`, `tsdown`). - Move formatting, linting and type-checking + git hooks onto `vite-plus` (oxlint, oxfmt, vitest). - Re-add CSP (`Content-Security-Policy`) wired into the axum router (was in `csp.rs`, removed as unused). -- Move all CLI deps to `devDependencies` — they bundle into the single output file anyway. ## Unified payload (drop the text/file union) From 3f7bb6f32efef17f0589d015a46c3923f6381a41 Mon Sep 17 00:00:00 2001 From: cupcakearmy Date: Sat, 12 Sep 2026 15:29:49 +0200 Subject: [PATCH 30/46] chore: bump to 3.0.0-rc.1 --- packages/backend/Cargo.lock | 2 +- packages/backend/Cargo.toml | 2 +- packages/cli/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/backend/Cargo.lock b/packages/backend/Cargo.lock index 2e29d31..5c24ef2 100644 --- a/packages/backend/Cargo.lock +++ b/packages/backend/Cargo.lock @@ -252,7 +252,7 @@ dependencies = [ [[package]] name = "cryptgeon" -version = "3.0.0-rc.0" +version = "3.0.0-rc.1" dependencies = [ "axum", "bs62", diff --git a/packages/backend/Cargo.toml b/packages/backend/Cargo.toml index 793d5ab..616f9de 100644 --- a/packages/backend/Cargo.toml +++ b/packages/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cryptgeon" -version = "3.0.0-rc.0" +version = "3.0.0-rc.1" authors = ["cupcakearmy "] edition = "2024" rust-version = "1.95" diff --git a/packages/cli/package.json b/packages/cli/package.json index 9c97021..87a39a0 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "cryptgeon", - "version": "3.0.0-rc.0", + "version": "3.0.0-rc.1", "homepage": "https://github.com/cupcakearmy/cryptgeon", "repository": { "type": "git", From 98c56f6fb97b8b03c72b62939de9f62abb26969b Mon Sep 17 00:00:00 2001 From: cupcakearmy Date: Sat, 12 Sep 2026 15:32:02 +0200 Subject: [PATCH 31/46] fix(cli): replace removed run-s in prepublishOnly --- packages/cli/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 87a39a0..d75b1d4 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -21,7 +21,7 @@ "scripts": { "build": "vp pack", "dev": "vp pack --watch", - "prepublishOnly": "run-s build" + "prepublishOnly": "pnpm run build" }, "devDependencies": { "@cryptgeon/shared": "workspace:*", From 2dc9d6aa16a2810c75fbec5c2888afb90161961a Mon Sep 17 00:00:00 2001 From: cupcakearmy Date: Sat, 12 Sep 2026 15:32:32 +0200 Subject: [PATCH 32/46] chore: bump to 3.0.0-rc.2 --- packages/backend/Cargo.lock | 2 +- packages/backend/Cargo.toml | 2 +- packages/cli/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/backend/Cargo.lock b/packages/backend/Cargo.lock index 5c24ef2..adebd06 100644 --- a/packages/backend/Cargo.lock +++ b/packages/backend/Cargo.lock @@ -252,7 +252,7 @@ dependencies = [ [[package]] name = "cryptgeon" -version = "3.0.0-rc.1" +version = "3.0.0-rc.2" dependencies = [ "axum", "bs62", diff --git a/packages/backend/Cargo.toml b/packages/backend/Cargo.toml index 616f9de..b363e6d 100644 --- a/packages/backend/Cargo.toml +++ b/packages/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cryptgeon" -version = "3.0.0-rc.1" +version = "3.0.0-rc.2" authors = ["cupcakearmy "] edition = "2024" rust-version = "1.95" diff --git a/packages/cli/package.json b/packages/cli/package.json index d75b1d4..90bb2f8 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "cryptgeon", - "version": "3.0.0-rc.1", + "version": "3.0.0-rc.2", "homepage": "https://github.com/cupcakearmy/cryptgeon", "repository": { "type": "git", From 8df6b6c7b5c4c43f9a9200a43c2b5e291372fcd4 Mon Sep 17 00:00:00 2001 From: cupcakearmy Date: Sat, 12 Sep 2026 15:56:00 +0200 Subject: [PATCH 33/46] docs: use v3 image tag instead of latest in compose --- README.md | 2 +- README_ES.md | 2 +- README_zh-CN.md | 4 ++-- docker-compose.yaml | 4 ++-- examples/nginx/docker-compose.yaml | 2 +- examples/scratch/README.md | 2 +- examples/traefik/README.md | 4 ++-- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 7d11cde..949242a 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,7 @@ services: - /data app: - image: cupcakearmy/cryptgeon:latest + image: cupcakearmy/cryptgeon:v3 depends_on: - cache environment: diff --git a/README_ES.md b/README_ES.md index f5cbfc0..5d27498 100644 --- a/README_ES.md +++ b/README_ES.md @@ -103,7 +103,7 @@ services: - /data app: - image: cupcakearmy/cryptgeon:latest + image: cupcakearmy/cryptgeon:v3 depends_on: - cache environment: diff --git a/README_zh-CN.md b/README_zh-CN.md index 68e61c3..eb1fc8e 100644 --- a/README_zh-CN.md +++ b/README_zh-CN.md @@ -91,7 +91,7 @@ services: - /data app: - image: cupcakearmy/cryptgeon:latest + image: cupcakearmy/cryptgeon:v3 depends_on: - cache environment: @@ -131,7 +131,7 @@ services: - /data app: - image: cupcakearmy/cryptgeon:latest + image: cupcakearmy/cryptgeon:v3 restart: unless-stopped depends_on: - cache diff --git a/docker-compose.yaml b/docker-compose.yaml index 73ef450..bce7c40 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -11,7 +11,7 @@ services: - /data app: - image: cupcakearmy/cryptgeon:latest + image: cupcakearmy/cryptgeon:v3 depends_on: - cache environment: @@ -26,4 +26,4 @@ services: # interval: 1m # timeout: 3s # retries: 2 - # start_period: 5s \ No newline at end of file + # start_period: 5s diff --git a/examples/nginx/docker-compose.yaml b/examples/nginx/docker-compose.yaml index 7b932f5..afbdcbd 100644 --- a/examples/nginx/docker-compose.yaml +++ b/examples/nginx/docker-compose.yaml @@ -12,7 +12,7 @@ services: - /data app: - image: cupcakearmy/cryptgeon:latest + image: cupcakearmy/cryptgeon:v3 depends_on: - cache diff --git a/examples/scratch/README.md b/examples/scratch/README.md index 87560e4..2739e90 100644 --- a/examples/scratch/README.md +++ b/examples/scratch/README.md @@ -116,7 +116,7 @@ services: - /data app: - image: cupcakearmy/cryptgeon:latest + image: cupcakearmy/cryptgeon:v3 restart: unless-stopped depends_on: - cache diff --git a/examples/traefik/README.md b/examples/traefik/README.md index e863fc7..192eb14 100644 --- a/examples/traefik/README.md +++ b/examples/traefik/README.md @@ -26,7 +26,7 @@ services: - /data app: - image: cupcakearmy/cryptgeon:latest + image: cupcakearmy/cryptgeon:v3 restart: unless-stopped depends_on: - cache @@ -70,7 +70,7 @@ services: - /data cryptgeon: - image: cupcakearmy/cryptgeon + image: cupcakearmy/cryptgeon:v3 depends_on: - cache labels: From f05707e033956a797b386eecf136b799c1e276a0 Mon Sep 17 00:00:00 2001 From: cupcakearmy Date: Sat, 12 Sep 2026 16:13:02 +0200 Subject: [PATCH 34/46] update deps --- docs/roadmap.md | 1 - packages/cli/package.json | 12 +- pnpm-lock.yaml | 777 +++++++++++++++++++------------------- 3 files changed, 400 insertions(+), 390 deletions(-) diff --git a/docs/roadmap.md b/docs/roadmap.md index 60a1a9f..305c45d 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -2,7 +2,6 @@ ## Todo -- Localize i18n readmes: `README_ES.md`, `README_zh-CN.md` still reference `REDIS` / `/api/v1`-era endpoint names — align to `CACHE` / `/healthz` (also sweep `CONTRIBUTING.md`, `examples/*`, postman collection). - Add remaining shared tooling to the pnpm catalog (`vite`, `tsdown`). - Move formatting, linting and type-checking + git hooks onto `vite-plus` (oxlint, oxfmt, vitest). - Re-add CSP (`Content-Security-Policy`) wired into the axum router (was in `csp.rs`, removed as unused). diff --git a/packages/cli/package.json b/packages/cli/package.json index 90bb2f8..c1a1d3e 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -24,16 +24,16 @@ "prepublishOnly": "pnpm run build" }, "devDependencies": { + "@commander-js/extra-typings": "^15.0.0", "@cryptgeon/shared": "workspace:*", "@msgpack/msgpack": "^3.1.3", - "@commander-js/extra-typings": "^12.1.0", "@tsconfig/strictest": "catalog:", - "@types/inquirer": "^9.0.9", - "@types/node": "^22.15.3", - "commander": "^12.1.0", - "inquirer": "^9.3.8", + "@types/inquirer": "^9.0.10", + "@types/node": "^22.20.1", + "commander": "^15.0.0", + "inquirer": "^14.2.1", "mime": "^4.1.0", - "pretty-bytes": "^6.1.1", + "pretty-bytes": "^7.1.3", "typescript": "catalog:", "vite-plus": "catalog:" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 03c6d41..be8a2cd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,8 +32,8 @@ importers: packages/cli: devDependencies: '@commander-js/extra-typings': - specifier: ^12.1.0 - version: 12.1.0(commander@12.1.0) + specifier: ^15.0.0 + version: 15.0.0(commander@15.0.0) '@cryptgeon/shared': specifier: workspace:* version: link:../shared @@ -44,29 +44,29 @@ importers: specifier: 'catalog:' version: 2.0.8 '@types/inquirer': - specifier: ^9.0.9 + specifier: ^9.0.10 version: 9.0.10 '@types/node': - specifier: ^22.15.3 - version: 22.20.0 + specifier: ^22.20.1 + version: 22.20.1 commander: - specifier: ^12.1.0 - version: 12.1.0 + specifier: ^15.0.0 + version: 15.0.0 inquirer: - specifier: ^9.3.8 - version: 9.3.8(@types/node@22.20.0) + specifier: ^14.2.1 + version: 14.2.1(@types/node@22.20.1) mime: specifier: ^4.1.0 version: 4.1.0 pretty-bytes: - specifier: ^6.1.1 - version: 6.1.1 + specifier: ^7.1.3 + version: 7.1.3 typescript: specifier: 'catalog:' version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 0.3.0(@types/node@22.20.0)(svelte@5.57.0)(typescript@7.0.2)(vite@8.2.2(@types/node@22.20.0)) + version: 0.3.0(@types/node@22.20.1)(svelte@5.57.0)(typescript@7.0.2)(vite@8.2.2(@types/node@22.20.1)) packages/frontend: dependencies: @@ -151,10 +151,6 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} - '@babel/code-frame@7.26.2': - resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} - engines: {node: '>=6.9.0'} - '@babel/code-frame@7.29.7': resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} engines: {node: '>=6.9.0'} @@ -197,10 +193,6 @@ packages: resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.25.9': - resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} - engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.29.7': resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} engines: {node: '>=6.9.0'} @@ -237,10 +229,10 @@ packages: '@blazediff/core@1.9.1': resolution: {integrity: sha512-ehg3jIkYKulZh+8om/O25vkvSsXXwC+skXmyA87FFx6A/45eqOkZsBltMw/TVteb0mloiGT8oGRTcjRAz66zaA==} - '@commander-js/extra-typings@12.1.0': - resolution: {integrity: sha512-wf/lwQvWAA0goIghcb91dQYpkLBcyhOhQNqG/VgWhnKzgt+UOMvra7EX/2fv70arm5RW+PUHoQHHDa6/p77Eqg==} + '@commander-js/extra-typings@15.0.0': + resolution: {integrity: sha512-yeJlba62xqmkgELUsn7356MEnzLLu/fw2x4lofFqGnXh6YysRdEs2BaLeLtg1+KU0AXvMeqQvTTp+3hBEBK+EA==} peerDependencies: - commander: ~12.1.0 + commander: ~15.0.0 '@fontsource/fira-mono@5.3.0': resolution: {integrity: sha512-FZzRwKhpoXQU1uhEGKUg5AHUUdfiaTXxGCdBBeGHUmcZaqEDEmndSVvpXHkSViH25f/UiWfhcWTib+6A8o4qeQ==} @@ -264,18 +256,139 @@ packages: resolution: {integrity: sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA==} engines: {node: ^20.17.0 || >=22.9.0} - '@inquirer/external-editor@1.0.3': - resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} - engines: {node: '>=18'} + '@inquirer/ansi@2.0.8': + resolution: {integrity: sha512-WpQM+Ti6Z40EFwwt+uL2p4UabT+W179zHp6HhLVOzfbwnVn05IPO/eXIZXGNqcT1jbQ15SujNLzQ39k4QPPxBQ==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + + '@inquirer/checkbox@5.2.4': + resolution: {integrity: sha512-oKT5RNeO9oKYizSyOxKb66IrKUsYVMQD2tnjllYW4wmSOe9WawfL3OE2p82JuvAyo+/nDIEem5P4Bm/ZIVYVew==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/figures@1.0.9': - resolution: {integrity: sha512-BXvGj0ehzrngHTPTDqUoDT3NXL8U0RxUk2zJm2A66RhCEIWdtU1v6GuUqNAgArW4PQ9CinqIWyHdQgdwOj06zQ==} - engines: {node: '>=18'} + '@inquirer/confirm@6.3.1': + resolution: {integrity: sha512-HvnOHal39DTenOVoRpIy8Z+n4YYfNa3Qhi/P8zFqn9/d1crpmQG0DPx34rwOeOFvotgKr9Vov/14OmKR/Iwfjw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/core@12.0.2': + resolution: {integrity: sha512-9pBhkxE14uUlnHhs8lOt7qVPtS4caRY6CjADl8COejl9Mf7w8i6Uoe3DrljCqYtmYM3Iv2Q6EmPxx/oTYbvTAQ==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/editor@5.3.2': + resolution: {integrity: sha512-7ruKO5d/wxhGHxdCQ3eyP/aBNrPTZ+Ju7ERtnAwuIsu0RSLs6kotlTRkOxHatbD6sDaEEdwsD82pzFiT9ESIyQ==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/expand@5.1.4': + resolution: {integrity: sha512-12o4aYnjWouoq0fyVhYIL+hwXAaga+kntA/wrijFmVvn2heinkdvkbEXRt1Ob/9IOJ285US5tntjfI/Ukt2YkA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/external-editor@3.0.5': + resolution: {integrity: sha512-f3QQJRIX5ZEneBHNUIuPjmbdzHnmRFJA8r2dkcb8q+OM5Uv5KtnuAttQumnrjcBVBM3mcTX1CkmtAkU58VRZxg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/figures@2.0.9': + resolution: {integrity: sha512-EAWgUTGQ/Umgga51dE3B2PUHbufuXarDfg86uVgoSgNHNNQnyFKcOrQLWVqYMghuSyHh8+2HUH0Js9cTC1WAdg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + + '@inquirer/input@5.1.5': + resolution: {integrity: sha512-2MkYYFD1Owt1eRBwHhsLSutysRodWasA4DVpl42KJKGmzwBTXRYAIXujisrt0UW0UohUlMjNjLfMDbw4emRbfw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/number@4.2.2': + resolution: {integrity: sha512-tjW1gMIQsFb/9Ie11i8SpuPARBdyCVfC5XOeVugWovrdm8CpjzN+A1CBX5QwCCN2mtNmpgDDCnuLCCT94cQ5zA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/password@5.2.1': + resolution: {integrity: sha512-InA6nJxMCXPavPBiTKYHIsYiWz/mkIsk1rkI6pKp5w18gVawqrZOj7eSzSmPJa3j0pVxXm6RlrFP1BE9YeOixA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/prompts@8.7.1': + resolution: {integrity: sha512-xJIKWyrFNUKj3R5VEub/iKodG9Sh/MbCeApZUbATJgHTw8YoAJ2gOT78HlvO6NMFiRvjm6ySM7aiAew/zVY0Sw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/rawlist@5.3.4': + resolution: {integrity: sha512-c4fDwOpQsjJDHjXdHJE1/xXH6w9/BAiyu3QKzH9Ww5Xm1q37mzyUNa20L7qdtM1ECi4e+oUEKjhK8hR37OMAMQ==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/search@4.3.2': + resolution: {integrity: sha512-YIaEhWmfkdHEWOKwhF/oJar35FFTTrAdzNbJDo9lSsRT6E/obRPx9oVm4rkTMtlcJSqGYttNtLbcHQef9Qw2yw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/select@5.2.4': + resolution: {integrity: sha512-L9ubNaJdoBsiom/AfPKq0QLHTkhhCFCtszzdVidGremrlJjPZfGlmvp+IULKIbnYjtVFWnvb4mhiEdNTZ12ugA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/type@4.1.1': + resolution: {integrity: sha512-yJoHYrMnxIsJZCY+0Vb66Dy3he3kL3e2wOBKhoSwWWAzZAY82emlxwgprCtp6yRixvNRNq9ztfRWQYPNr3Go7A==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true '@isaacs/fs-minipass@4.0.1': resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} @@ -833,8 +946,8 @@ packages: resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} engines: {node: '>=18'} - '@testing-library/user-event@14.6.6': - resolution: {integrity: sha512-Jbs9FpkkIDw8FgSc6kOVsOv8JuuqGAL7J4X1oot77JxAoDlkNn2GRkd0aYRVuQ+pVQAiHWVkE4rX/dkF5fBiCw==} + '@testing-library/user-event@14.6.7': + resolution: {integrity: sha512-MPCpX8bxe8zS+JmmTwLp8jd0dy1rAm60Te/SL8JrQM3qvQJcBOs1d7IefJMyZzqM3EWBrDn/LWDt1BCGu4ASfg==} engines: {node: '>=12', npm: '>=6'} peerDependencies: '@testing-library/dom': '>=7.21.4' @@ -871,8 +984,8 @@ packages: '@types/lz4js@0.2.2': resolution: {integrity: sha512-pqXfoJ2AwllcLTf1Nia0+HT1KHj8z4wWo3bBP6vn7Aen5ySywBqrh/u1TyBwWxuKDS+mPzVHkPbHivqZEfk6pA==} - '@types/node@22.20.0': - resolution: {integrity: sha512-QWlFW2wf3nTjC13/DqRnBpR4ZO36VJH/JVBkA/vcnmbTBNQIlnObqyqZE1tUR7+Ni23Lda8R1BxMfbXRpCUx5g==} + '@types/node@22.20.1': + resolution: {integrity: sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==} '@types/node@24.13.3': resolution: {integrity: sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==} @@ -1295,10 +1408,6 @@ packages: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} - ansi-escapes@4.3.2: - resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} - engines: {node: '>=8'} - ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -1342,11 +1451,8 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - - baseline-browser-mapping@2.11.20: - resolution: {integrity: sha512-H0ulySigv6icDJ1F7SjtdCD6PrhTpdYCmP0CactWy1+ekh0AFd0o1Wn5T8b+hnTmdBx19u9yhL6wvCylXMY7zw==} + baseline-browser-mapping@2.11.21: + resolution: {integrity: sha512-uh8vpY/1/YyFkunIDFH/12p7/7VdPKA1hejMVEbdkEaWnUz0Hesvx5EbiU6XxjyHZIOju+ZMbQJkRh+es3/spQ==} engines: {node: '>=6.0.0'} hasBin: true @@ -1354,21 +1460,15 @@ packages: resolution: {integrity: sha512-frE1t78WOwJ45PKV2cF2tNPjTcs9L1J9s6VkrV59wanRP4GlaomuxYPVma7BwthMg8WnfSory4w5PTE6FZZ81w==} engines: {node: ^20.17.0 || >=22.9.0} - bl@4.1.0: - resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} - brace-expansion@5.0.9: resolution: {integrity: sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==} engines: {node: 20 || >=22} - browserslist@4.28.8: - resolution: {integrity: sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==} + browserslist@4.28.9: + resolution: {integrity: sha512-EWazOblFYUvlGZcfGhPUPmYh3nikUxBVb+y9MJun5f3hBi812X+8MSQTujLBtgK3cf51fJWbWfOjyeO954d+Eg==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true - buffer@5.7.1: - resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - cacache@20.0.4: resolution: {integrity: sha512-M3Lab8NPYlZU2exsL3bMVvMrMqgwCnMWfdZbK28bn3pK6APT/Te/I8hjRPNu1uwORY9a1eEQoifXbKPQMfMTOA==} engines: {node: ^20.17.0 || >=22.9.0} @@ -1384,8 +1484,8 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} - chardet@2.1.1: - resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} + chardet@2.2.0: + resolution: {integrity: sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==} chokidar@4.0.3: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} @@ -1395,22 +1495,10 @@ packages: resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} engines: {node: '>=18'} - cli-cursor@3.1.0: - resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} - engines: {node: '>=8'} - - cli-spinners@2.9.2: - resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} - engines: {node: '>=6'} - cli-width@4.1.0: resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} engines: {node: '>= 12'} - clone@1.0.4: - resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} - engines: {node: '>=0.8'} - clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} @@ -1426,9 +1514,9 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - commander@12.1.0: - resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} - engines: {node: '>=18'} + commander@15.0.0: + resolution: {integrity: sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==} + engines: {node: '>=22.12.0'} common-ancestor-path@2.0.0: resolution: {integrity: sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng==} @@ -1466,9 +1554,6 @@ packages: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} - defaults@1.0.4: - resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} - dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -1483,11 +1568,8 @@ packages: dom-accessibility-api@0.5.16: resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} - electron-to-chromium@1.5.416: - resolution: {integrity: sha512-K6bvB2BjnNrugtIih6ewlbBI9DXa976jIdiIlRLHhBoEI9a4JaQjjHyF+A1IQI543aQYR4LnmOrT/K5fZj0aPA==} - - emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + electron-to-chromium@1.5.422: + resolution: {integrity: sha512-UvA/32XqrLDdZSn7Jllo1AYNcWji/G0d5M0GTViE7KoGBiMunw3a34Sb2KO4ZZyrSEhqsxFoVhWWJshdyfKqJA==} env-paths@2.2.1: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} @@ -1521,6 +1603,15 @@ packages: exponential-backoff@3.1.3: resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==} + fast-string-truncated-width@3.0.3: + resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} + + fast-string-width@3.0.2: + resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} + + fast-wrap-ansi@0.2.2: + resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -1574,51 +1665,34 @@ packages: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} - iconv-lite@0.7.2: - resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} - engines: {node: '>=0.10.0'} - iconv-lite@0.7.3: resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==} engines: {node: '>=0.10.0'} - ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - ignore-walk@8.0.0: resolution: {integrity: sha512-FCeMZT4NiRQGh+YkeKMtWrOmBgWjHjMJ26WQWrRQyoyzqevdaGSakUaJW5xQYmjLlUVk2qUnCjYVBax9EKKg8A==} engines: {node: ^20.17.0 || >=22.9.0} - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - ini@6.0.0: resolution: {integrity: sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==} engines: {node: ^20.17.0 || >=22.9.0} - inquirer@9.3.8: - resolution: {integrity: sha512-pFGGdaHrmRKMh4WoDDSowddgjT1Vkl90atobmTeSmcPGdYiwikch/m/Ef5wRaiamHejtw0cUUMMerzDUXCci2w==} - engines: {node: '>=18'} + inquirer@14.2.1: + resolution: {integrity: sha512-jLO8986l0/jW+K2Y5BHAhrD6ztnm8MHQgnTI5BwKV6can/LN6xYBcLbtUSvDt1jXNTASwCZszZk0ZBUT5YmH6Q==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true ip-address@10.7.0: resolution: {integrity: sha512-BGFsyJd5mpXp3rK6jIdADLNgpJUK1jnjzvYF8lK+VyDab9JAmqN0YOKDdP17HlgKb2+ehPgDc8EtnRLbGCAMhA==} engines: {node: '>= 12'} - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - - is-interactive@1.0.0: - resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} - engines: {node: '>=8'} - is-reference@3.0.3: resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} - is-unicode-supported@0.1.0: - resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} - engines: {node: '>=10'} - isexe@4.0.0: resolution: {integrity: sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==} engines: {node: '>=20'} @@ -1746,10 +1820,6 @@ packages: lodash.clonedeep@4.5.0: resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} - log-symbols@4.1.0: - resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} - engines: {node: '>=10'} - lru-cache@11.5.2: resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} engines: {node: 20 || >=22} @@ -1779,10 +1849,6 @@ packages: engines: {node: '>=16'} hasBin: true - mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} - minimatch@10.2.6: resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} engines: {node: 18 || 20 || >=22} @@ -1835,14 +1901,9 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - mute-stream@1.0.0: - resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - nanoid@3.3.12: - resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true + mute-stream@3.0.0: + resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==} + engines: {node: ^20.17.0 || >=22.9.0} nanoid@3.3.18: resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==} @@ -1907,14 +1968,6 @@ packages: resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==} engines: {node: '>=12.20.0'} - onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} - - ora@5.4.1: - resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} - engines: {node: '>=10'} - oxfmt@0.64.0: resolution: {integrity: sha512-XZ4GFBN/PLbXKq+0zrgpQfPKYuJlUuj+nzZJY7UpIbFMNyefNLCdN9EwViycNqnYcv0wrn0jXcQLlqJp8RCKBg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1998,27 +2051,27 @@ packages: resolution: {integrity: sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==} engines: {node: '>=4'} - postcss@8.5.15: - resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} - engines: {node: ^10 || ^12 || >=14} - postcss@8.5.26: resolution: {integrity: sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==} engines: {node: ^10 || ^12 || >=14} + postcss@8.5.28: + resolution: {integrity: sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==} + engines: {node: ^10 || ^12 || >=14} + precompile-intl-runtime@0.8.5: resolution: {integrity: sha512-gxDphR6a6qFBINiLf4ior2LBHCfW2XNksYrIgcvEvO0uc5vgdsTfWdqdyX68nDbSVh1H4Y/q+Lqm7Z68b1x/Pg==} peerDependencies: svelte: ^3.37.0 || ^4.0.0 - pretty-bytes@6.1.1: - resolution: {integrity: sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==} - engines: {node: ^14.13.1 || >=16.0.0} - pretty-bytes@7.1.2: resolution: {integrity: sha512-spZIOSORH9joSPGN0L/tYfvINH+xbOpnI3kdcEkq0mzGMjFvDZzyRVbqEOV0wACF0gggdKE9RWO2mosGsBh/Rg==} engines: {node: '>=20'} + pretty-bytes@7.1.3: + resolution: {integrity: sha512-U2KZ675nqba6XSBppSjU2pRgc9Ffx9+uUtd/RTWYmOawVkZuUbBYuwdtFZQmLv+yMgQ2TQxWWuOpyNje67J9tg==} + engines: {node: '>=20'} + pretty-format@27.5.1: resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} @@ -2044,37 +2097,26 @@ packages: resolution: {integrity: sha512-1zM5HuOfagXCBWMN83fuFI/x+T/UhZ7k+KIzhrHXcQoeX5+7gmaDYjELQHmmzIodumBHeByBJT4QYS7ufAgs7A==} engines: {node: ^20.17.0 || >=22.9.0} - readable-stream@3.6.2: - resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} - engines: {node: '>= 6'} - readdirp@4.1.2: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} - restore-cursor@3.1.0: - resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} - engines: {node: '>=8'} - rolldown@1.2.6: resolution: {integrity: sha512-vMM4q3aixf46GiF1Kok8jDPFsEpXgFWGjUHXNkNHNm+Y2adXAG2dbX91jkti3i0ZRsOlcmbuzAz1poObSHCmUA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - run-async@3.0.0: - resolution: {integrity: sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==} + run-async@4.0.6: + resolution: {integrity: sha512-IoDlSLTs3Yq593mb3ZoKWKXMNu3UpObxhgA/Xuid5p4bbfi2jdY1Hj0m1K+0/tEuQTxIGMhQDqGjKb7RuxGpAQ==} engines: {node: '>=0.12.0'} - rxjs@7.8.1: - resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} + rxjs@7.8.2: + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} sade@1.8.1: resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} engines: {node: '>=6'} - safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -2093,9 +2135,6 @@ packages: siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} @@ -2158,17 +2197,6 @@ packages: std-env@4.1.0: resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} - string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} - - string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - - strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - strip-bom@5.0.0: resolution: {integrity: sha512-p+byADHF7SzEcVnLvc/r3uognM1hUhObuHXxJcgLCfD194XAkaLbjq3Wzb0N5G2tgIjH0dgT708Z51QxMeu60A==} engines: {node: '>=12'} @@ -2219,6 +2247,10 @@ packages: resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} + tinyrainbow@3.1.1: + resolution: {integrity: sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==} + engines: {node: '>=14.0.0'} + totalist@3.0.1: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} @@ -2238,10 +2270,6 @@ packages: resolution: {integrity: sha512-50QV99kCKH5P/Vs4E2Gzp7BopNV+KzTXqWeaxrfu5IQJBOULRsTIS9seSsOVT8ZnGXzCyx55nYWAi4qJzpZKEQ==} engines: {node: ^20.17.0 || >=22.9.0} - type-fest@0.21.3: - resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} - engines: {node: '>=10'} - typescript@6.0.3: resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} engines: {node: '>=14.17'} @@ -2387,9 +2415,6 @@ packages: resolution: {integrity: sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==} engines: {node: 20 || >=22} - wcwidth@1.0.1: - resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} - which@6.0.1: resolution: {integrity: sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==} engines: {node: ^20.17.0 || >=22.9.0} @@ -2400,10 +2425,6 @@ packages: engines: {node: '>=8'} hasBin: true - wrap-ansi@6.2.0: - resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} - engines: {node: '>=8'} - write-file-atomic@7.0.1: resolution: {integrity: sha512-OTIk8iR8/aCRWBqvxrzxR0hgxWpnYBblY1S5hDWBQfk/VFmJwzmJgQFN3WsoUKHISv2eAwe+PpbUzyL1CKTLXg==} engines: {node: ^20.17.0 || >=22.9.0} @@ -2430,10 +2451,6 @@ packages: resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} engines: {node: '>=18'} - yoctocolors-cjs@2.1.2: - resolution: {integrity: sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==} - engines: {node: '>=18'} - yuku-codegen@0.5.48: resolution: {integrity: sha512-p7HxD5Xl4jzDzqMrGePAOeSHmRY4g58h4HuGq15weQFPxuPWd/W6e7nqp/+Lea6JfpOdBwJOAyXFqIZ/J9Zfnw==} @@ -2450,12 +2467,6 @@ snapshots: '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 - '@babel/code-frame@7.26.2': - dependencies: - '@babel/helper-validator-identifier': 7.25.9 - js-tokens: 4.0.0 - picocolors: 1.1.1 - '@babel/code-frame@7.29.7': dependencies: '@babel/helper-validator-identifier': 7.29.7 @@ -2496,7 +2507,7 @@ snapshots: dependencies: '@babel/compat-data': 7.29.7 '@babel/helper-validator-option': 7.29.7 - browserslist: 4.28.8 + browserslist: 4.28.9 lru-cache: 5.1.1 semver: 6.3.1 @@ -2522,8 +2533,6 @@ snapshots: '@babel/helper-string-parser@7.29.7': {} - '@babel/helper-validator-identifier@7.25.9': {} - '@babel/helper-validator-identifier@7.29.7': {} '@babel/helper-validator-option@7.29.7': {} @@ -2564,9 +2573,9 @@ snapshots: '@blazediff/core@1.9.1': {} - '@commander-js/extra-typings@12.1.0(commander@12.1.0)': + '@commander-js/extra-typings@15.0.0(commander@15.0.0)': dependencies: - commander: 12.1.0 + commander: 15.0.0 '@fontsource/fira-mono@5.3.0': {} @@ -2598,14 +2607,124 @@ snapshots: '@gar/promise-retry@1.0.3': {} - '@inquirer/external-editor@1.0.3(@types/node@22.20.0)': - dependencies: - chardet: 2.1.1 - iconv-lite: 0.7.2 - optionalDependencies: - '@types/node': 22.20.0 + '@inquirer/ansi@2.0.8': {} - '@inquirer/figures@1.0.9': {} + '@inquirer/checkbox@5.2.4(@types/node@22.20.1)': + dependencies: + '@inquirer/ansi': 2.0.8 + '@inquirer/core': 12.0.2(@types/node@22.20.1) + '@inquirer/figures': 2.0.9 + '@inquirer/type': 4.1.1(@types/node@22.20.1) + optionalDependencies: + '@types/node': 22.20.1 + + '@inquirer/confirm@6.3.1(@types/node@22.20.1)': + dependencies: + '@inquirer/core': 12.0.2(@types/node@22.20.1) + '@inquirer/type': 4.1.1(@types/node@22.20.1) + optionalDependencies: + '@types/node': 22.20.1 + + '@inquirer/core@12.0.2(@types/node@22.20.1)': + dependencies: + '@inquirer/ansi': 2.0.8 + '@inquirer/figures': 2.0.9 + '@inquirer/type': 4.1.1(@types/node@22.20.1) + cli-width: 4.1.0 + fast-wrap-ansi: 0.2.2 + mute-stream: 3.0.0 + signal-exit: 4.1.0 + optionalDependencies: + '@types/node': 22.20.1 + + '@inquirer/editor@5.3.2(@types/node@22.20.1)': + dependencies: + '@inquirer/core': 12.0.2(@types/node@22.20.1) + '@inquirer/external-editor': 3.0.5(@types/node@22.20.1) + '@inquirer/type': 4.1.1(@types/node@22.20.1) + optionalDependencies: + '@types/node': 22.20.1 + + '@inquirer/expand@5.1.4(@types/node@22.20.1)': + dependencies: + '@inquirer/core': 12.0.2(@types/node@22.20.1) + '@inquirer/type': 4.1.1(@types/node@22.20.1) + optionalDependencies: + '@types/node': 22.20.1 + + '@inquirer/external-editor@3.0.5(@types/node@22.20.1)': + dependencies: + chardet: 2.2.0 + iconv-lite: 0.7.3 + optionalDependencies: + '@types/node': 22.20.1 + + '@inquirer/figures@2.0.9': {} + + '@inquirer/input@5.1.5(@types/node@22.20.1)': + dependencies: + '@inquirer/core': 12.0.2(@types/node@22.20.1) + '@inquirer/type': 4.1.1(@types/node@22.20.1) + optionalDependencies: + '@types/node': 22.20.1 + + '@inquirer/number@4.2.2(@types/node@22.20.1)': + dependencies: + '@inquirer/core': 12.0.2(@types/node@22.20.1) + '@inquirer/type': 4.1.1(@types/node@22.20.1) + optionalDependencies: + '@types/node': 22.20.1 + + '@inquirer/password@5.2.1(@types/node@22.20.1)': + dependencies: + '@inquirer/ansi': 2.0.8 + '@inquirer/core': 12.0.2(@types/node@22.20.1) + '@inquirer/type': 4.1.1(@types/node@22.20.1) + optionalDependencies: + '@types/node': 22.20.1 + + '@inquirer/prompts@8.7.1(@types/node@22.20.1)': + dependencies: + '@inquirer/checkbox': 5.2.4(@types/node@22.20.1) + '@inquirer/confirm': 6.3.1(@types/node@22.20.1) + '@inquirer/editor': 5.3.2(@types/node@22.20.1) + '@inquirer/expand': 5.1.4(@types/node@22.20.1) + '@inquirer/input': 5.1.5(@types/node@22.20.1) + '@inquirer/number': 4.2.2(@types/node@22.20.1) + '@inquirer/password': 5.2.1(@types/node@22.20.1) + '@inquirer/rawlist': 5.3.4(@types/node@22.20.1) + '@inquirer/search': 4.3.2(@types/node@22.20.1) + '@inquirer/select': 5.2.4(@types/node@22.20.1) + optionalDependencies: + '@types/node': 22.20.1 + + '@inquirer/rawlist@5.3.4(@types/node@22.20.1)': + dependencies: + '@inquirer/core': 12.0.2(@types/node@22.20.1) + '@inquirer/type': 4.1.1(@types/node@22.20.1) + optionalDependencies: + '@types/node': 22.20.1 + + '@inquirer/search@4.3.2(@types/node@22.20.1)': + dependencies: + '@inquirer/core': 12.0.2(@types/node@22.20.1) + '@inquirer/figures': 2.0.9 + '@inquirer/type': 4.1.1(@types/node@22.20.1) + optionalDependencies: + '@types/node': 22.20.1 + + '@inquirer/select@5.2.4(@types/node@22.20.1)': + dependencies: + '@inquirer/ansi': 2.0.8 + '@inquirer/core': 12.0.2(@types/node@22.20.1) + '@inquirer/figures': 2.0.9 + '@inquirer/type': 4.1.1(@types/node@22.20.1) + optionalDependencies: + '@types/node': 22.20.1 + + '@inquirer/type@4.1.1(@types/node@22.20.1)': + optionalDependencies: + '@types/node': 22.20.1 '@isaacs/fs-minipass@4.0.1': dependencies: @@ -3024,7 +3143,7 @@ snapshots: '@testing-library/dom@10.4.1': dependencies: - '@babel/code-frame': 7.26.2 + '@babel/code-frame': 7.29.7 '@babel/runtime': 7.29.7 '@types/aria-query': 5.0.4 aria-query: 5.3.0 @@ -3033,7 +3152,7 @@ snapshots: picocolors: 1.1.1 pretty-format: 27.5.1 - '@testing-library/user-event@14.6.6(@testing-library/dom@10.4.1)': + '@testing-library/user-event@14.6.7(@testing-library/dom@10.4.1)': dependencies: '@testing-library/dom': 10.4.1 @@ -3062,11 +3181,11 @@ snapshots: '@types/inquirer@9.0.10': dependencies: '@types/through': 0.0.33 - rxjs: 7.8.1 + rxjs: 7.8.2 '@types/lz4js@0.2.2': {} - '@types/node@22.20.0': + '@types/node@22.20.1': dependencies: undici-types: 6.21.0 @@ -3076,7 +3195,7 @@ snapshots: '@types/through@0.0.33': dependencies: - '@types/node': 22.20.0 + '@types/node': 22.20.1 '@typescript/typescript-aix-ppc64@7.0.2': optional: true @@ -3138,12 +3257,12 @@ snapshots: '@typescript/typescript-win32-x64@7.0.2': optional: true - '@vitest/browser-preview@4.1.11(vite@8.2.2(@types/node@22.20.0))(vitest@4.1.11)': + '@vitest/browser-preview@4.1.11(vite@8.2.2(@types/node@22.20.1))(vitest@4.1.11)': dependencies: '@testing-library/dom': 10.4.1 - '@testing-library/user-event': 14.6.6(@testing-library/dom@10.4.1) - '@vitest/browser': 4.1.11(vite@8.2.2(@types/node@22.20.0))(vitest@4.1.11) - vitest: 4.1.11(@types/node@22.20.0)(@vitest/browser-preview@4.1.11)(vite@8.2.2(@types/node@22.20.0)) + '@testing-library/user-event': 14.6.7(@testing-library/dom@10.4.1) + '@vitest/browser': 4.1.11(vite@8.2.2(@types/node@22.20.1))(vitest@4.1.11) + vitest: 4.1.11(@types/node@22.20.1)(@vitest/browser-preview@4.1.11)(vite@8.2.2(@types/node@22.20.1)) transitivePeerDependencies: - bufferutil - msw @@ -3153,7 +3272,7 @@ snapshots: '@vitest/browser-preview@4.1.11(vite@8.2.2(@types/node@24.13.3))(vitest@4.1.11)': dependencies: '@testing-library/dom': 10.4.1 - '@testing-library/user-event': 14.6.6(@testing-library/dom@10.4.1) + '@testing-library/user-event': 14.6.7(@testing-library/dom@10.4.1) '@vitest/browser': 4.1.11(vite@8.2.2(@types/node@24.13.3))(vitest@4.1.11) vitest: 4.1.11(@types/node@24.13.3)(@vitest/browser-preview@4.1.11)(vite@8.2.2(@types/node@24.13.3)) transitivePeerDependencies: @@ -3163,16 +3282,16 @@ snapshots: - vite optional: true - '@vitest/browser@4.1.11(vite@8.2.2(@types/node@22.20.0))(vitest@4.1.11)': + '@vitest/browser@4.1.11(vite@8.2.2(@types/node@22.20.1))(vitest@4.1.11)': dependencies: '@blazediff/core': 1.9.1 - '@vitest/mocker': 4.1.11(vite@8.2.2(@types/node@22.20.0)) + '@vitest/mocker': 4.1.11(vite@8.2.2(@types/node@22.20.1)) '@vitest/utils': 4.1.11 magic-string: 0.30.21 pngjs: 7.0.0 sirv: 3.0.2 - tinyrainbow: 3.1.0 - vitest: 4.1.11(@types/node@22.20.0)(@vitest/browser-preview@4.1.11)(vite@8.2.2(@types/node@22.20.0)) + tinyrainbow: 3.1.1 + vitest: 4.1.11(@types/node@22.20.1)(@vitest/browser-preview@4.1.11)(vite@8.2.2(@types/node@22.20.1)) ws: 8.21.3 transitivePeerDependencies: - bufferutil @@ -3188,7 +3307,7 @@ snapshots: magic-string: 0.30.21 pngjs: 7.0.0 sirv: 3.0.2 - tinyrainbow: 3.1.0 + tinyrainbow: 3.1.1 vitest: 4.1.11(@types/node@24.13.3)(@vitest/browser-preview@4.1.11)(vite@8.2.2(@types/node@24.13.3)) ws: 8.21.3 transitivePeerDependencies: @@ -3205,15 +3324,15 @@ snapshots: '@vitest/spy': 4.1.11 '@vitest/utils': 4.1.11 chai: 6.2.2 - tinyrainbow: 3.1.0 + tinyrainbow: 3.1.1 - '@vitest/mocker@4.1.11(vite@8.2.2(@types/node@22.20.0))': + '@vitest/mocker@4.1.11(vite@8.2.2(@types/node@22.20.1))': dependencies: '@vitest/spy': 4.1.11 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.2.2(@types/node@22.20.0) + vite: 8.2.2(@types/node@22.20.1) '@vitest/mocker@4.1.11(vite@8.2.2(@types/node@24.13.3))': dependencies: @@ -3225,7 +3344,7 @@ snapshots: '@vitest/pretty-format@4.1.11': dependencies: - tinyrainbow: 3.1.0 + tinyrainbow: 3.1.1 '@vitest/runner@4.1.11': dependencies: @@ -3245,18 +3364,18 @@ snapshots: dependencies: '@vitest/pretty-format': 4.1.11 convert-source-map: 2.0.0 - tinyrainbow: 3.1.0 + tinyrainbow: 3.1.1 - '@voidzero-dev/vite-plus-core@0.3.0(@types/node@22.20.0)(typescript@7.0.2)': + '@voidzero-dev/vite-plus-core@0.3.0(@types/node@22.20.1)(typescript@7.0.2)': dependencies: '@oxc-project/runtime': 0.146.0 '@oxc-project/types': 0.146.0 lightningcss: 1.33.0 - postcss: 8.5.15 + postcss: 8.5.28 yuku-codegen: 0.5.48 yuku-parser: 0.5.48 optionalDependencies: - '@types/node': 22.20.0 + '@types/node': 22.20.1 '@voidzero-dev/vite-plus-darwin-arm64': 0.3.0 '@voidzero-dev/vite-plus-darwin-x64': 0.3.0 '@voidzero-dev/vite-plus-linux-arm64-gnu': 0.3.0 @@ -3372,10 +3491,6 @@ snapshots: agent-base@7.1.4: {} - ansi-escapes@4.3.2: - dependencies: - type-fest: 0.21.3 - ansi-regex@5.0.1: {} ansi-styles@4.3.0: @@ -3406,9 +3521,7 @@ snapshots: balanced-match@4.0.4: {} - base64-js@1.5.1: {} - - baseline-browser-mapping@2.11.20: {} + baseline-browser-mapping@2.11.21: {} bin-links@6.0.2: dependencies: @@ -3418,28 +3531,17 @@ snapshots: read-cmd-shim: 6.0.0 write-file-atomic: 7.0.1 - bl@4.1.0: - dependencies: - buffer: 5.7.1 - inherits: 2.0.4 - readable-stream: 3.6.2 - brace-expansion@5.0.9: dependencies: balanced-match: 4.0.4 - browserslist@4.28.8: + browserslist@4.28.9: dependencies: - baseline-browser-mapping: 2.11.20 + baseline-browser-mapping: 2.11.21 caniuse-lite: 1.0.30001810 - electron-to-chromium: 1.5.416 + electron-to-chromium: 1.5.422 node-releases: 2.0.54 - update-browserslist-db: 1.3.2(browserslist@4.28.8) - - buffer@5.7.1: - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 + update-browserslist-db: 1.3.2(browserslist@4.28.9) cacache@20.0.4: dependencies: @@ -3463,7 +3565,7 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 - chardet@2.1.1: {} + chardet@2.2.0: {} chokidar@4.0.3: dependencies: @@ -3471,16 +3573,8 @@ snapshots: chownr@3.0.0: {} - cli-cursor@3.1.0: - dependencies: - restore-cursor: 3.1.0 - - cli-spinners@2.9.2: {} - cli-width@4.1.0: {} - clone@1.0.4: {} - clsx@2.1.1: {} cmd-shim@8.0.0: {} @@ -3491,7 +3585,7 @@ snapshots: color-name@1.1.4: {} - commander@12.1.0: {} + commander@15.0.0: {} common-ancestor-path@2.0.0: {} @@ -3511,10 +3605,6 @@ snapshots: deepmerge@4.3.1: {} - defaults@1.0.4: - dependencies: - clone: 1.0.4 - dequal@2.0.3: {} detect-libc@2.1.2: {} @@ -3523,9 +3613,7 @@ snapshots: dom-accessibility-api@0.5.16: {} - electron-to-chromium@1.5.416: {} - - emoji-regex@8.0.0: {} + electron-to-chromium@1.5.422: {} env-paths@2.2.1: {} @@ -3547,6 +3635,16 @@ snapshots: exponential-backoff@3.1.3: {} + fast-string-truncated-width@3.0.3: {} + + fast-string-width@3.0.2: + dependencies: + fast-string-truncated-width: 3.0.3 + + fast-wrap-ansi@0.2.2: + dependencies: + fast-string-width: 3.0.2 + fdir@6.5.0(picomatch@4.0.4): optionalDependencies: picomatch: 4.0.4 @@ -3597,54 +3695,33 @@ snapshots: transitivePeerDependencies: - supports-color - iconv-lite@0.7.2: - dependencies: - safer-buffer: 2.1.2 - iconv-lite@0.7.3: dependencies: safer-buffer: 2.1.2 - optional: true - - ieee754@1.2.1: {} ignore-walk@8.0.0: dependencies: minimatch: 10.2.6 - inherits@2.0.4: {} - ini@6.0.0: {} - inquirer@9.3.8(@types/node@22.20.0): + inquirer@14.2.1(@types/node@22.20.1): dependencies: - '@inquirer/external-editor': 1.0.3(@types/node@22.20.0) - '@inquirer/figures': 1.0.9 - ansi-escapes: 4.3.2 - cli-width: 4.1.0 - mute-stream: 1.0.0 - ora: 5.4.1 - run-async: 3.0.0 - rxjs: 7.8.1 - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 6.2.0 - yoctocolors-cjs: 2.1.2 - transitivePeerDependencies: - - '@types/node' + '@inquirer/ansi': 2.0.8 + '@inquirer/core': 12.0.2(@types/node@22.20.1) + '@inquirer/prompts': 8.7.1(@types/node@22.20.1) + '@inquirer/type': 4.1.1(@types/node@22.20.1) + mute-stream: 3.0.0 + run-async: 4.0.6 + optionalDependencies: + '@types/node': 22.20.1 ip-address@10.7.0: {} - is-fullwidth-code-point@3.0.0: {} - - is-interactive@1.0.0: {} - is-reference@3.0.3: dependencies: '@types/estree': 1.0.9 - is-unicode-supported@0.1.0: {} - isexe@4.0.0: {} js-tokens@4.0.0: {} @@ -3739,11 +3816,6 @@ snapshots: lodash.clonedeep@4.5.0: {} - log-symbols@4.1.0: - dependencies: - chalk: 4.1.2 - is-unicode-supported: 0.1.0 - lru-cache@11.5.2: {} lru-cache@5.1.1: @@ -3781,8 +3853,6 @@ snapshots: mime@4.1.0: {} - mimic-fn@2.1.0: {} - minimatch@10.2.6: dependencies: brace-expansion: 5.0.9 @@ -3829,9 +3899,7 @@ snapshots: ms@2.1.3: {} - mute-stream@1.0.0: {} - - nanoid@3.3.12: {} + mute-stream@3.0.0: {} nanoid@3.3.18: {} @@ -3908,23 +3976,7 @@ snapshots: obug@2.1.4: {} - onetime@5.1.2: - dependencies: - mimic-fn: 2.1.0 - - ora@5.4.1: - dependencies: - bl: 4.1.0 - chalk: 4.1.2 - cli-cursor: 3.1.0 - cli-spinners: 2.9.2 - is-interactive: 1.0.0 - is-unicode-supported: 0.1.0 - log-symbols: 4.1.0 - strip-ansi: 6.0.1 - wcwidth: 1.0.1 - - oxfmt@0.64.0(svelte@5.57.0)(vite-plus@0.3.0(@types/node@22.20.0)(svelte@5.57.0)(typescript@7.0.2)(vite@8.2.2(@types/node@22.20.0))): + oxfmt@0.64.0(svelte@5.57.0)(vite-plus@0.3.0(@types/node@22.20.1)(svelte@5.57.0)(typescript@7.0.2)(vite@8.2.2(@types/node@22.20.1))): dependencies: tinypool: 2.1.0 optionalDependencies: @@ -3948,7 +4000,7 @@ snapshots: '@oxfmt/binding-win32-ia32-msvc': 0.64.0 '@oxfmt/binding-win32-x64-msvc': 0.64.0 svelte: 5.57.0 - vite-plus: 0.3.0(@types/node@22.20.0)(svelte@5.57.0)(typescript@7.0.2)(vite@8.2.2(@types/node@22.20.0)) + vite-plus: 0.3.0(@types/node@22.20.1)(svelte@5.57.0)(typescript@7.0.2)(vite@8.2.2(@types/node@22.20.1)) oxlint-tsgolint@7.0.2001: optionalDependencies: @@ -3959,7 +4011,7 @@ snapshots: '@oxlint-tsgolint/win32-arm64': 7.0.2001 '@oxlint-tsgolint/win32-x64': 7.0.2001 - oxlint@1.79.0(oxlint-tsgolint@7.0.2001)(vite-plus@0.3.0(@types/node@22.20.0)(svelte@5.57.0)(typescript@7.0.2)(vite@8.2.2(@types/node@22.20.0))): + oxlint@1.79.0(oxlint-tsgolint@7.0.2001)(vite-plus@0.3.0(@types/node@22.20.1)(svelte@5.57.0)(typescript@7.0.2)(vite@8.2.2(@types/node@22.20.1))): optionalDependencies: '@oxlint/binding-android-arm-eabi': 1.79.0 '@oxlint/binding-android-arm64': 1.79.0 @@ -3981,7 +4033,7 @@ snapshots: '@oxlint/binding-win32-ia32-msvc': 1.79.0 '@oxlint/binding-win32-x64-msvc': 1.79.0 oxlint-tsgolint: 7.0.2001 - vite-plus: 0.3.0(@types/node@22.20.0)(svelte@5.57.0)(typescript@7.0.2)(vite@8.2.2(@types/node@22.20.0)) + vite-plus: 0.3.0(@types/node@22.20.1)(svelte@5.57.0)(typescript@7.0.2)(vite@8.2.2(@types/node@22.20.1)) p-map@7.0.7: {} @@ -4043,13 +4095,13 @@ snapshots: cssesc: 3.0.0 util-deprecate: 1.0.2 - postcss@8.5.15: + postcss@8.5.26: dependencies: - nanoid: 3.3.12 + nanoid: 3.3.18 picocolors: 1.1.1 source-map-js: 1.2.1 - postcss@8.5.26: + postcss@8.5.28: dependencies: nanoid: 3.3.18 picocolors: 1.1.1 @@ -4059,10 +4111,10 @@ snapshots: dependencies: svelte: 5.57.0 - pretty-bytes@6.1.1: {} - pretty-bytes@7.1.2: {} + pretty-bytes@7.1.3: {} + pretty-format@27.5.1: dependencies: ansi-regex: 5.0.1 @@ -4081,19 +4133,8 @@ snapshots: read-cmd-shim@6.0.0: {} - readable-stream@3.6.2: - dependencies: - inherits: 2.0.4 - string_decoder: 1.3.0 - util-deprecate: 1.0.2 - readdirp@4.1.2: {} - restore-cursor@3.1.0: - dependencies: - onetime: 5.1.2 - signal-exit: 3.0.7 - rolldown@1.2.6: dependencies: '@oxc-project/types': 0.147.0 @@ -4115,9 +4156,9 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.2.6 '@rolldown/binding-win32-x64-msvc': 1.2.6 - run-async@3.0.0: {} + run-async@4.0.6: {} - rxjs@7.8.1: + rxjs@7.8.2: dependencies: tslib: 2.8.1 @@ -4125,8 +4166,6 @@ snapshots: dependencies: mri: 1.2.0 - safe-buffer@5.2.1: {} - safer-buffer@2.1.2: {} semver@6.3.1: {} @@ -4137,8 +4176,6 @@ snapshots: siginfo@2.0.0: {} - signal-exit@3.0.7: {} - signal-exit@4.1.0: {} sigstore@4.1.1: @@ -4216,20 +4253,6 @@ snapshots: std-env@4.1.0: {} - string-width@4.2.3: - dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 - - string_decoder@1.3.0: - dependencies: - safe-buffer: 5.2.1 - - strip-ansi@6.0.1: - dependencies: - ansi-regex: 5.0.1 - strip-bom@5.0.0: {} supports-color@7.2.0: @@ -4307,6 +4330,8 @@ snapshots: tinyrainbow@3.1.0: {} + tinyrainbow@3.1.1: {} + totalist@3.0.1: {} treeify@1.1.0: {} @@ -4323,8 +4348,6 @@ snapshots: transitivePeerDependencies: - supports-color - type-fest@0.21.3: {} - typescript@6.0.3: {} typescript@7.0.2: @@ -4356,9 +4379,9 @@ snapshots: undici@6.28.0: {} - update-browserslist-db@1.3.2(browserslist@4.28.8): + update-browserslist-db@1.3.2(browserslist@4.28.9): dependencies: - browserslist: 4.28.8 + browserslist: 4.28.9 escalade: 3.2.0 picocolors: 1.1.1 @@ -4368,24 +4391,24 @@ snapshots: validate-npm-package-name@7.0.2: {} - vite-plus@0.3.0(@types/node@22.20.0)(svelte@5.57.0)(typescript@7.0.2)(vite@8.2.2(@types/node@22.20.0)): + vite-plus@0.3.0(@types/node@22.20.1)(svelte@5.57.0)(typescript@7.0.2)(vite@8.2.2(@types/node@22.20.1)): dependencies: '@oxc-project/types': 0.146.0 '@oxlint/plugins': 1.79.0 - '@vitest/browser': 4.1.11(vite@8.2.2(@types/node@22.20.0))(vitest@4.1.11) - '@vitest/browser-preview': 4.1.11(vite@8.2.2(@types/node@22.20.0))(vitest@4.1.11) + '@vitest/browser': 4.1.11(vite@8.2.2(@types/node@22.20.1))(vitest@4.1.11) + '@vitest/browser-preview': 4.1.11(vite@8.2.2(@types/node@22.20.1))(vitest@4.1.11) '@vitest/expect': 4.1.11 - '@vitest/mocker': 4.1.11(vite@8.2.2(@types/node@22.20.0)) + '@vitest/mocker': 4.1.11(vite@8.2.2(@types/node@22.20.1)) '@vitest/pretty-format': 4.1.11 '@vitest/runner': 4.1.11 '@vitest/snapshot': 4.1.11 '@vitest/spy': 4.1.11 '@vitest/utils': 4.1.11 - '@voidzero-dev/vite-plus-core': 0.3.0(@types/node@22.20.0)(typescript@7.0.2) - oxfmt: 0.64.0(svelte@5.57.0)(vite-plus@0.3.0(@types/node@22.20.0)(svelte@5.57.0)(typescript@7.0.2)(vite@8.2.2(@types/node@22.20.0))) - oxlint: 1.79.0(oxlint-tsgolint@7.0.2001)(vite-plus@0.3.0(@types/node@22.20.0)(svelte@5.57.0)(typescript@7.0.2)(vite@8.2.2(@types/node@22.20.0))) + '@voidzero-dev/vite-plus-core': 0.3.0(@types/node@22.20.1)(typescript@7.0.2) + oxfmt: 0.64.0(svelte@5.57.0)(vite-plus@0.3.0(@types/node@22.20.1)(svelte@5.57.0)(typescript@7.0.2)(vite@8.2.2(@types/node@22.20.1))) + oxlint: 1.79.0(oxlint-tsgolint@7.0.2001)(vite-plus@0.3.0(@types/node@22.20.1)(svelte@5.57.0)(typescript@7.0.2)(vite@8.2.2(@types/node@22.20.1))) oxlint-tsgolint: 7.0.2001 - vitest: 4.1.11(@types/node@22.20.0)(@vitest/browser-preview@4.1.11)(vite@8.2.2(@types/node@22.20.0)) + vitest: 4.1.11(@types/node@22.20.1)(@vitest/browser-preview@4.1.11)(vite@8.2.2(@types/node@22.20.1)) optionalDependencies: '@voidzero-dev/vite-plus-darwin-arm64': 0.3.0 '@voidzero-dev/vite-plus-darwin-x64': 0.3.0 @@ -4426,7 +4449,7 @@ snapshots: - vite - yaml - vite@8.2.2(@types/node@22.20.0): + vite@8.2.2(@types/node@22.20.1): dependencies: lightningcss: 1.33.0 picomatch: 4.0.7 @@ -4434,7 +4457,7 @@ snapshots: rolldown: 1.2.6 tinyglobby: 0.2.17 optionalDependencies: - '@types/node': 22.20.0 + '@types/node': 22.20.1 fsevents: 2.3.3 vite@8.2.2(@types/node@24.13.3): @@ -4452,10 +4475,10 @@ snapshots: optionalDependencies: vite: 8.2.2(@types/node@24.13.3) - vitest@4.1.11(@types/node@22.20.0)(@vitest/browser-preview@4.1.11)(vite@8.2.2(@types/node@22.20.0)): + vitest@4.1.11(@types/node@22.20.1)(@vitest/browser-preview@4.1.11)(vite@8.2.2(@types/node@22.20.1)): dependencies: '@vitest/expect': 4.1.11 - '@vitest/mocker': 4.1.11(vite@8.2.2(@types/node@22.20.0)) + '@vitest/mocker': 4.1.11(vite@8.2.2(@types/node@22.20.1)) '@vitest/pretty-format': 4.1.11 '@vitest/runner': 4.1.11 '@vitest/snapshot': 4.1.11 @@ -4472,11 +4495,11 @@ snapshots: tinyexec: 1.2.4 tinyglobby: 0.2.16 tinyrainbow: 3.1.0 - vite: 8.2.2(@types/node@22.20.0) + vite: 8.2.2(@types/node@22.20.1) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 22.20.0 - '@vitest/browser-preview': 4.1.11(vite@8.2.2(@types/node@22.20.0))(vitest@4.1.11) + '@types/node': 22.20.1 + '@vitest/browser-preview': 4.1.11(vite@8.2.2(@types/node@22.20.1))(vitest@4.1.11) transitivePeerDependencies: - msw @@ -4510,10 +4533,6 @@ snapshots: walk-up-path@4.0.0: {} - wcwidth@1.0.1: - dependencies: - defaults: 1.0.4 - which@6.0.1: dependencies: isexe: 4.0.0 @@ -4523,12 +4542,6 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 - wrap-ansi@6.2.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - write-file-atomic@7.0.1: dependencies: signal-exit: 4.1.0 @@ -4541,8 +4554,6 @@ snapshots: yallist@5.0.0: {} - yoctocolors-cjs@2.1.2: {} - yuku-codegen@0.5.48: dependencies: '@yuku-toolchain/types': 0.5.43 From 7653409473b013f799b9533fdc329e3fc6440261 Mon Sep 17 00:00:00 2001 From: cupcakearmy Date: Sat, 12 Sep 2026 16:13:41 +0200 Subject: [PATCH 35/46] chore: bump to 3.0.0-rc.3 --- packages/backend/Cargo.lock | 2 +- packages/backend/Cargo.toml | 2 +- packages/cli/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/backend/Cargo.lock b/packages/backend/Cargo.lock index adebd06..a6a5195 100644 --- a/packages/backend/Cargo.lock +++ b/packages/backend/Cargo.lock @@ -252,7 +252,7 @@ dependencies = [ [[package]] name = "cryptgeon" -version = "3.0.0-rc.2" +version = "3.0.0-rc.3" dependencies = [ "axum", "bs62", diff --git a/packages/backend/Cargo.toml b/packages/backend/Cargo.toml index b363e6d..0dcc363 100644 --- a/packages/backend/Cargo.toml +++ b/packages/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cryptgeon" -version = "3.0.0-rc.2" +version = "3.0.0-rc.3" authors = ["cupcakearmy "] edition = "2024" rust-version = "1.95" diff --git a/packages/cli/package.json b/packages/cli/package.json index c1a1d3e..3f5d8bd 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "cryptgeon", - "version": "3.0.0-rc.2", + "version": "3.0.0-rc.3", "homepage": "https://github.com/cupcakearmy/cryptgeon", "repository": { "type": "git", From 0d597edfeef9847b7f46ba4f4b7f4bd8c20635ca Mon Sep 17 00:00:00 2001 From: cupcakearmy Date: Sun, 13 Sep 2026 16:19:41 +0200 Subject: [PATCH 36/46] fix(web): re-add FileDTO import; run svelte-check in CI --- .github/workflows/test.yaml | 3 +++ packages/frontend/src/lib/views/Create.svelte | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 32666d9..7ce11b3 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -34,6 +34,9 @@ jobs: - name: Shared tests run: pnpm --filter @cryptgeon/shared test + - name: Frontend type check + run: pnpm --filter @cryptgeon/web check + - name: Run your tests run: pnpm test diff --git a/packages/frontend/src/lib/views/Create.svelte b/packages/frontend/src/lib/views/Create.svelte index 5545aed..0af7b88 100644 --- a/packages/frontend/src/lib/views/Create.svelte +++ b/packages/frontend/src/lib/views/Create.svelte @@ -3,7 +3,7 @@ bytesToHex, create as apiCreate, packContent, - type ServerNote + type FileDTO, type ServerNote } from '@cryptgeon/shared' import { t } from 'svelte-intl-precompile' import { blur } from 'svelte/transition' From a57ead61b9c29cbab272ca9e589ec296ec3eb404 Mon Sep 17 00:00:00 2001 From: cupcakearmy Date: Sun, 13 Sep 2026 21:17:24 +0200 Subject: [PATCH 37/46] fix(web): snapshot files before worker transfer (Svelte proxy not cloneable) --- packages/frontend/src/lib/views/Create.svelte | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/packages/frontend/src/lib/views/Create.svelte b/packages/frontend/src/lib/views/Create.svelte index 0af7b88..5af4352 100644 --- a/packages/frontend/src/lib/views/Create.svelte +++ b/packages/frontend/src/lib/views/Create.svelte @@ -1,12 +1,15 @@ @@ -92,7 +92,7 @@ async function downloadFile(file: FileDTO) { {/key} {/if} {/each} - + {/if} From 055a48a38d96f3733518fe4d11912c8c5e31c15f Mon Sep 17 00:00:00 2001 From: cupcakearmy Date: Sat, 19 Sep 2026 15:13:34 +0200 Subject: [PATCH 40/46] fix(web): stop transferring nested file buffers in worker unpack (comlink multi-buffer failure) --- packages/frontend/src/lib/worker/worker.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/frontend/src/lib/worker/worker.ts b/packages/frontend/src/lib/worker/worker.ts index c19ec27..3bbf757 100644 --- a/packages/frontend/src/lib/worker/worker.ts +++ b/packages/frontend/src/lib/worker/worker.ts @@ -8,11 +8,7 @@ const contract: WorkerConract = { return transfer(content, [content.data.buffer, content.extra.buffer, content.key.buffer]) }, unpack(data, key) { - const content = unpackContent(data, key) - return transfer( - content, - typeof content.data === 'string' ? [] : content.data.map((f) => f.data.buffer) - ) + return unpackContent(data, key) }, } From 5084ed59f0217eb58196e7d7c430926030bc1012 Mon Sep 17 00:00:00 2001 From: cupcakearmy Date: Sat, 19 Sep 2026 15:47:20 +0200 Subject: [PATCH 41/46] docs: fix incorrect pg note wording in v3 changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df62050..7d97bca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - New shared TypeScript package `@cryptgeon/shared` as single source of truth for crypto, content codec and API client (crypto + compression + payload + types). - Shared payload codec: `packContent` / `unpackContent` (encode → LZ4 → XChaCha20-Poly1305 and reverse). -- New `pg`-backend storage of note hashes in the cache. +- Cache-backed note storage using hashes (valkey/redis) with atomic view counting. ### Changed From 4dfed492bcddcae626e66f0b3af1e4528bd9dbf7 Mon Sep 17 00:00:00 2001 From: cupcakearmy Date: Sat, 19 Sep 2026 15:51:37 +0200 Subject: [PATCH 42/46] docs: correct frontend bullet in v3 changelog (was already SvelteKit) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d97bca..443dcc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Encryption from AES to **XChaCha20-Poly1305** (client-side); dropped `occulto`. - All API bodies switched to **MessagePack**. -- Frontend migrated to SvelteKit + `@cryptgeon/shared`. +- Frontend uses `@cryptgeon/shared` for crypto + payload codec (replacing the previous local `cryptgeon/shared`); heavy pack/unpack runs in a web worker. - CLI rebuilt with `vite-plus` (bundles all deps) and imports from `@cryptgeon/shared`. ### Breaking changes From 282fdb97d1cf297c6ce523e70152466253302c04 Mon Sep 17 00:00:00 2001 From: cupcakearmy Date: Sat, 19 Sep 2026 16:00:04 +0200 Subject: [PATCH 43/46] docs: add LLM usage disclosure to readmes --- README.md | 4 ++++ README_ES.md | 4 ++++ README_zh-CN.md | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/README.md b/README.md index 949242a..81d697e 100644 --- a/README.md +++ b/README.md @@ -169,6 +169,10 @@ See [CONTRIBUTING.md](./CONTRIBUTING.md). Please refer to the security section [here](./SECURITY.md). +## Usage of LLMs + +Starting from V3, I used LLMs heavily to implement _my own ideas_. This means that the direction and architecture choices are human. A lot of the implementation that derives from that, is automated with an LLM. + --- _Attributions_ diff --git a/README_ES.md b/README_ES.md index 5d27498..08fbbad 100644 --- a/README_ES.md +++ b/README_ES.md @@ -151,6 +151,10 @@ Ver [CONTRIBUTING.md](./CONTRIBUTING.md). Por favor dirígete a la sección de seguridad [aquí](./SECURITY.md). +## Uso de LLMs + +A partir de la V3, utilicé LLMs de forma intensiva para implementar _mis propias ideas_. Esto significa que la dirección y las decisiones de arquitectura son humanas. Gran parte de la implementación que deriva de eso está automatizada con un LLM. + --- _Atribuciones_ diff --git a/README_zh-CN.md b/README_zh-CN.md index eb1fc8e..5e9a490 100644 --- a/README_zh-CN.md +++ b/README_zh-CN.md @@ -149,6 +149,10 @@ services: 参见 [CONTRIBUTING.md](./CONTRIBUTING.md)。 +## LLM 的使用 + +从 V3 开始,我大量使用 LLM 来实现_我自己的想法_。这意味着项目的方向和架构选择均由人类决定。由此衍生的大部分实现由 LLM 自动完成。 + ###### Attributions - 测试数据: From 7ef162bd8323a9464988b91caca8fce713437888 Mon Sep 17 00:00:00 2001 From: cupcakearmy Date: Sat, 19 Sep 2026 16:02:55 +0200 Subject: [PATCH 44/46] chore: bump to 3.0.0-rc.4 --- packages/backend/Cargo.lock | 2 +- packages/backend/Cargo.toml | 2 +- packages/cli/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/backend/Cargo.lock b/packages/backend/Cargo.lock index a6a5195..d9d22d4 100644 --- a/packages/backend/Cargo.lock +++ b/packages/backend/Cargo.lock @@ -252,7 +252,7 @@ dependencies = [ [[package]] name = "cryptgeon" -version = "3.0.0-rc.3" +version = "3.0.0-rc.4" dependencies = [ "axum", "bs62", diff --git a/packages/backend/Cargo.toml b/packages/backend/Cargo.toml index 0dcc363..0060ae2 100644 --- a/packages/backend/Cargo.toml +++ b/packages/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cryptgeon" -version = "3.0.0-rc.3" +version = "3.0.0-rc.4" authors = ["cupcakearmy "] edition = "2024" rust-version = "1.95" diff --git a/packages/cli/package.json b/packages/cli/package.json index 3f5d8bd..b956b59 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "cryptgeon", - "version": "3.0.0-rc.3", + "version": "3.0.0-rc.4", "homepage": "https://github.com/cupcakearmy/cryptgeon", "repository": { "type": "git", From c99c62cf001aeba7edb29af7c28c870b10bd7702 Mon Sep 17 00:00:00 2001 From: cupcakearmy Date: Mon, 21 Sep 2026 20:50:48 +0200 Subject: [PATCH 45/46] chore: clean up compose file --- docker-compose.yaml | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index bce7c40..2974d01 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,17 +1,17 @@ services: cache: - image: valkey/valkey:7-alpine + image: valkey/valkey:9-alpine # This is required to stay in RAM only. command: valkey-server --save "" --appendonly no # Set a size limit. See link below on how to customise. - # https://valkey.io/docs/latest/operate/rs/databases/memory-performance/eviction-policy/ - # --maxmemory 1gb --maxmemory-policy allkeys-lrulpine + # https://valkey.io/topics/lru-cache/ + # --maxmemory 1gb --maxmemory-policy allkeys-lru # This prevents the creation of an anonymous volume. tmpfs: - /data app: - image: cupcakearmy/cryptgeon:v3 + image: cupcakearmy/cryptgeon:3 depends_on: - cache environment: @@ -19,11 +19,9 @@ services: SIZE_LIMIT: 4 MiB ports: - 80:8000 - - # Optional health checks - # healthcheck: - # test: ["CMD", "curl", "--fail", "http://127.0.0.1:8000/healthz"] - # interval: 1m - # timeout: 3s - # retries: 2 - # start_period: 5s + healthcheck: + test: ["CMD", "curl", "--fail", "http://127.0.0.1:8000/healthz"] + interval: 1m + timeout: 3s + retries: 2 + start_period: 5s From b113d253a9fc6775075ab7b96d4caad04c1903d8 Mon Sep 17 00:00:00 2001 From: cupcakearmy Date: Mon, 21 Sep 2026 20:51:51 +0200 Subject: [PATCH 46/46] chore: bump to 3.0.0 --- packages/backend/Cargo.lock | 2 +- packages/backend/Cargo.toml | 2 +- packages/cli/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/backend/Cargo.lock b/packages/backend/Cargo.lock index d9d22d4..dba5e3c 100644 --- a/packages/backend/Cargo.lock +++ b/packages/backend/Cargo.lock @@ -252,7 +252,7 @@ dependencies = [ [[package]] name = "cryptgeon" -version = "3.0.0-rc.4" +version = "3.0.0" dependencies = [ "axum", "bs62", diff --git a/packages/backend/Cargo.toml b/packages/backend/Cargo.toml index 0060ae2..2646a8f 100644 --- a/packages/backend/Cargo.toml +++ b/packages/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cryptgeon" -version = "3.0.0-rc.4" +version = "3.0.0" authors = ["cupcakearmy "] edition = "2024" rust-version = "1.95" diff --git a/packages/cli/package.json b/packages/cli/package.json index b956b59..dee0e45 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "cryptgeon", - "version": "3.0.0-rc.4", + "version": "3.0.0", "homepage": "https://github.com/cupcakearmy/cryptgeon", "repository": { "type": "git",