From 95d689c76d67c71c134d717de90716898ed885c5 Mon Sep 17 00:00:00 2001 From: Timon Date: Sun, 22 Feb 2026 23:02:11 +0000 Subject: [PATCH 01/12] Reimplement third-party-licenses file generation This includes licenses from npm, cargo and newly added cef (parsed from CREDITS.html) --- .gitignore | 1 + Cargo.lock | 307 ++++++++- Cargo.toml | 3 + about.hbs | 27 - desktop/Cargo.toml | 3 +- desktop/src/app.rs | 13 + .../src/handle_desktop_wrapper_message.rs | 4 + .../wrapper/src/intercept_frontend_message.rs | 3 + desktop/wrapper/src/messages.rs | 2 + frontend/package-lock.json | 620 +++++++++++++++--- frontend/package.json | 6 +- frontend/src/state-providers/dialog.ts | 1 - frontend/vite.config.ts | 452 ++----------- package.json | 6 +- tools/third-party-licenses/.gitignore | 1 + tools/third-party-licenses/Cargo.toml | 14 + tools/third-party-licenses/build.rs | 5 + tools/third-party-licenses/src/cargo.rs | 107 +++ tools/third-party-licenses/src/cef.rs | 102 +++ tools/third-party-licenses/src/main.rs | 232 +++++++ tools/third-party-licenses/src/npm.rs | 90 +++ 21 files changed, 1480 insertions(+), 519 deletions(-) delete mode 100644 about.hbs create mode 100644 tools/third-party-licenses/.gitignore create mode 100644 tools/third-party-licenses/Cargo.toml create mode 100644 tools/third-party-licenses/build.rs create mode 100644 tools/third-party-licenses/src/cargo.rs create mode 100644 tools/third-party-licenses/src/cef.rs create mode 100644 tools/third-party-licenses/src/main.rs create mode 100644 tools/third-party-licenses/src/npm.rs diff --git a/.gitignore b/.gitignore index d6cc8a574c..06bcee0483 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ branding/ target/ +third-party-licenses.txt* result/ .flatpak-builder/ *.spv diff --git a/Cargo.lock b/Cargo.lock index 2a28d56c5c..9070c0e7d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1245,7 +1245,7 @@ checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ "bitflags 2.9.3", "crossterm_winapi", - "derive_more", + "derive_more 2.0.1", "document-features", "mio", "parking_lot", @@ -1280,6 +1280,29 @@ dependencies = [ "typenum", ] +[[package]] +name = "cssparser" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c66d1cd8ed61bf80b38432613a7a2f09401ab8d0501110655f8b341484a3e3" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "phf", + "smallvec", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +dependencies = [ + "quote", + "syn 2.0.106", +] + [[package]] name = "ctor" version = "0.2.9" @@ -1336,6 +1359,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "derive_more" +version = "0.99.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "derive_more" version = "2.0.1" @@ -1453,8 +1487,7 @@ checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] name = "download-cef" version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98178d9254efef0f69c1f584713d69c790ec00668cd98f783a5085fbefdbddc" +source = "git+https://github.com/timon-schelling/cef-rs.git?branch=graphite#8efeb241d1837447eccaee5d713a7c1ce331cd52" dependencies = [ "bzip2", "clap", @@ -1477,6 +1510,21 @@ dependencies = [ "serde", ] +[[package]] +name = "dtoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" + +[[package]] +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] + [[package]] name = "dunce" version = "1.0.5" @@ -1509,6 +1557,12 @@ dependencies = [ "graphite-editor", ] +[[package]] +name = "ego-tree" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2972feb8dffe7bc8c5463b1dacda1b0dfbed3710e50f977d965429692d74cd8" + [[package]] name = "either" version = "1.15.0" @@ -1912,6 +1966,16 @@ dependencies = [ "libc", ] +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + [[package]] name = "futures" version = "0.3.31" @@ -2025,6 +2089,15 @@ dependencies = [ "slab", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -2045,6 +2118,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "getopts" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df" +dependencies = [ + "unicode-width", +] + [[package]] name = "getrandom" version = "0.2.16" @@ -2356,6 +2438,7 @@ dependencies = [ "glam", "graphite-desktop-embedded-resources", "graphite-desktop-wrapper", + "lzma-rust2", "muda", "objc2 0.6.3", "objc2-app-kit 0.3.2", @@ -2602,6 +2685,18 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" +[[package]] +name = "html5ever" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c" +dependencies = [ + "log", + "mac", + "markup5ever", + "match_token", +] + [[package]] name = "http" version = "1.3.1" @@ -2730,7 +2825,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b1e4910d3a9137442723dfb772c32dc10674c4181ca078d2fd227cd5dce9db0" dependencies = [ "bincode", - "derive_more", + "derive_more 2.0.1", "iai-callgrind-macros", "iai-callgrind-runner", ] @@ -2741,7 +2836,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d03775318d3f9f01b39ac6612b01464006dc397a654a89dd57df2fd34fb68c3" dependencies = [ - "derive_more", + "derive_more 2.0.1", "proc-macro-error2", "proc-macro2", "quote", @@ -3403,6 +3498,21 @@ dependencies = [ "num-traits", ] +[[package]] +name = "lzma-rust2" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47bb1e988e6fb779cf720ad431242d3f03167c1b3f2b1aae7f1a94b2495b36ae" +dependencies = [ + "sha2", +] + +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + [[package]] name = "malloc_buf" version = "0.0.6" @@ -3412,6 +3522,31 @@ dependencies = [ "libc", ] +[[package]] +name = "markup5ever" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18" +dependencies = [ + "log", + "phf", + "phf_codegen", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "match_token" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "matchers" version = "0.2.0" @@ -4391,6 +4526,58 @@ dependencies = [ "indexmap", ] +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + [[package]] name = "pico-args" version = "0.5.0" @@ -4568,6 +4755,12 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + [[package]] name = "preprocessor" version = "0.1.0" @@ -5419,6 +5612,21 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scraper" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc3d051b884f40e309de6c149734eab57aa8cc1347992710dc80bcc1c2194c15" +dependencies = [ + "cssparser", + "ego-tree", + "getopts", + "html5ever", + "precomputed-hash", + "selectors", + "tendril", +] + [[package]] name = "sctk-adwaita" version = "0.11.0" @@ -5455,6 +5663,25 @@ dependencies = [ "libc", ] +[[package]] +name = "selectors" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd568a4c9bb598e291a08244a5c1f5a8a6650bee243b5b0f8dbb3d9cc1d87fe8" +dependencies = [ + "bitflags 2.9.3", + "cssparser", + "derive_more 0.99.20", + "fxhash", + "log", + "new_debug_unreachable", + "phf", + "phf_codegen", + "precomputed-hash", + "servo_arc", + "smallvec", +] + [[package]] name = "semver" version = "1.0.26" @@ -5560,6 +5787,15 @@ dependencies = [ "serde", ] +[[package]] +name = "servo_arc" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "170fb83ab34de17dc69aa7c67482b22218ddb85da56546f9bd6b929e32a05930" +dependencies = [ + "stable_deref_trait", +] + [[package]] name = "sha1_smol" version = "1.0.1" @@ -5577,6 +5813,19 @@ dependencies = [ "digest", ] +[[package]] +name = "sha256" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f880fc8562bdeb709793f00eb42a2ad0e672c4f883bbe59122b926eca935c8f6" +dependencies = [ + "async-trait", + "bytes", + "hex", + "sha2", + "tokio", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -5877,6 +6126,31 @@ dependencies = [ "float-cmp", ] +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", +] + [[package]] name = "strsim" version = "0.11.1" @@ -6049,6 +6323,17 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + [[package]] name = "termcolor" version = "1.4.1" @@ -6073,6 +6358,18 @@ dependencies = [ "vector-types", ] +[[package]] +name = "third-party-licenses" +version = "0.0.0" +dependencies = [ + "cef-dll-sys", + "lzma-rust2", + "scraper", + "serde", + "serde_json", + "sha256", +] + [[package]] name = "thiserror" version = "1.0.69" diff --git a/Cargo.toml b/Cargo.toml index 54dae87f82..ab35c83773 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ members = [ "node-graph/preprocessor", "proc-macros", "tools/crate-hierarchy-viz", + "tools/third-party-licenses", "tools/editor-message-tree", "tools/node-docs", ] @@ -247,6 +248,7 @@ clap = "4.5" spirv-std = { git = "https://github.com/Firestar99/rust-gpu-new", rev = "c12f216121820580731440ee79ebc7403d6ea04f", features = ["bytemuck"] } cargo-gpu = { git = "https://github.com/Firestar99/cargo-gpu", rev = "3952a22d16edbd38689f3a876e417899f21e1fe7", default-features = false } qrcodegen = "1.8" +lzma-rust2 = { version = "0.16", default-features = false, features = ["std", "encoder", "optimization", "xz"] } [workspace.lints.rust] unexpected_cfgs = { level = "allow", check-cfg = ['cfg(target_arch, values("spirv"))'] } @@ -277,3 +279,4 @@ debug = true [patch.crates-io] # Force cargo to use only one version of the dpi crate (vendoring breaks without this) dpi = { git = "https://github.com/rust-windowing/winit.git" } +download-cef = { git = "https://github.com/timon-schelling/cef-rs.git", branch = "graphite" } diff --git a/about.hbs b/about.hbs deleted file mode 100644 index e8cb48ace5..0000000000 --- a/about.hbs +++ /dev/null @@ -1,27 +0,0 @@ -{{! -Be careful to prevent auto-formatting from breaking this file's indentation. -Replace this file with JSON output once this is resolved: https://github.com/EmbarkStudios/cargo-about/issues/73 - -The `GENERATED_BY_CARGO_ABOUT` prefix is a JS labeled statement -() -used so the reader of the generated file can verify the file does indeed start with that string, -while remaining valid JS for subsequent parsing. -}} -GENERATED_BY_CARGO_ABOUT: [ - {{#each licenses}} - { - licenseName: `{{name}}`, - licenseText: `{{text}}`, - packages: [ - {{#each used_by}} - { - name: `{{crate.name}}`, - version: `{{crate.version}}`, - author: `{{crate.authors}}`, - repository: `{{crate.repository}}`, - }, - {{/each}} - ], - }, - {{/each}} -] diff --git a/desktop/Cargo.toml b/desktop/Cargo.toml index 372c9ba742..2dcdd7e24e 100644 --- a/desktop/Cargo.toml +++ b/desktop/Cargo.toml @@ -44,8 +44,9 @@ vello = { workspace = true } derivative = { workspace = true } rfd = { workspace = true } open = { workspace = true } -rand = { workspace = true, features = ["thread_rng"] } +lzma-rust2 = { workspace = true } serde = { workspace = true } +rand = { workspace = true, features = ["thread_rng"] } clap = { workspace = true, features = ["derive"] } fd-lock = "4.0.4" ctrlc = "3.5.1" diff --git a/desktop/src/app.rs b/desktop/src/app.rs index 0511588568..3a13cc843e 100644 --- a/desktop/src/app.rs +++ b/desktop/src/app.rs @@ -1,6 +1,7 @@ use rand::Rng; use rfd::AsyncFileDialog; use std::fs; +use std::io::Read; use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::{Receiver, Sender, SyncSender}; @@ -416,6 +417,18 @@ impl App { DesktopFrontendMessage::Restart => { self.exit(Some(ExitReason::Restart)); } + DesktopFrontendMessage::LoadThirdPartyLicenses => { + let compressed = include_bytes!(concat!(env!("CARGO_WORKSPACE_DIR"), "/third-party-licenses.txt.xz")); + let mut reader = lzma_rust2::XzReader::new(compressed.as_slice(), false); + let mut string = String::new(); + if let Err(e) = reader.read_to_string(&mut string) { + tracing::error!("Failed to decompress third-party licenses: {e}"); + return; + } + + let message = DesktopWrapperMessage::LoadThirdPartyLicenses(string); + responses.push(message); + } } } diff --git a/desktop/wrapper/src/handle_desktop_wrapper_message.rs b/desktop/wrapper/src/handle_desktop_wrapper_message.rs index e4efc915c2..9cd88a0da9 100644 --- a/desktop/wrapper/src/handle_desktop_wrapper_message.rs +++ b/desktop/wrapper/src/handle_desktop_wrapper_message.rs @@ -97,5 +97,9 @@ pub(super) fn handle_desktop_wrapper_message(dispatcher: &mut DesktopWrapperMess let message = AppWindowMessage::PointerLockMove { x, y }; dispatcher.queue_editor_message(message); } + DesktopWrapperMessage::LoadThirdPartyLicenses(string) => { + let message = DialogMessage::RequestLicensesThirdPartyDialogWithLicenseText { license_text: string }; + dispatcher.queue_editor_message(message); + } } } diff --git a/desktop/wrapper/src/intercept_frontend_message.rs b/desktop/wrapper/src/intercept_frontend_message.rs index 0d8e6b21ac..6294bb2115 100644 --- a/desktop/wrapper/src/intercept_frontend_message.rs +++ b/desktop/wrapper/src/intercept_frontend_message.rs @@ -154,6 +154,9 @@ pub(super) fn intercept_frontend_message(dispatcher: &mut DesktopWrapperMessageD FrontendMessage::WindowRestart => { dispatcher.respond(DesktopFrontendMessage::Restart); } + FrontendMessage::TriggerDisplayThirdPartyLicensesDialog => { + dispatcher.respond(DesktopFrontendMessage::LoadThirdPartyLicenses); + } m => return Some(m), } None diff --git a/desktop/wrapper/src/messages.rs b/desktop/wrapper/src/messages.rs index 6a7105d5c7..3b8ee1b0a5 100644 --- a/desktop/wrapper/src/messages.rs +++ b/desktop/wrapper/src/messages.rs @@ -75,6 +75,7 @@ pub enum DesktopFrontendMessage { WindowHideOthers, WindowShowAll, Restart, + LoadThirdPartyLicenses, } pub enum DesktopWrapperMessage { @@ -126,6 +127,7 @@ pub enum DesktopWrapperMessage { x: f64, y: f64, }, + LoadThirdPartyLicenses(String), } #[derive(Clone, serde::Serialize, serde::Deserialize, Debug)] diff --git a/frontend/package-lock.json b/frontend/package-lock.json index a059b6e5f0..701c8a0872 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9,6 +9,7 @@ "dependencies": { "class-transformer": "^0.5.1", "idb-keyval": "^6.2.2", + "license-checker-rseidelsohn": "^4.4.2", "reflect-metadata": "^0.2.2", "source-code-pro": "2.42.0", "source-sans-pro": "2.45.0" @@ -611,7 +612,6 @@ "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", @@ -627,7 +627,6 @@ "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@eslint/core": "^0.17.0" }, @@ -641,7 +640,6 @@ "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@types/json-schema": "^7.0.15" }, @@ -718,7 +716,6 @@ "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", "dev": true, "license": "Apache-2.0", - "peer": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } @@ -729,7 +726,6 @@ "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" @@ -744,7 +740,6 @@ "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@types/json-schema": "^7.0.15" }, @@ -758,7 +753,6 @@ "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", "dev": true, "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=18.18.0" } @@ -769,7 +763,6 @@ "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" @@ -784,7 +777,6 @@ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=12.22" }, @@ -799,7 +791,6 @@ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=18.18" }, @@ -808,6 +799,102 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@isaacs/fs-minipass": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", @@ -922,6 +1009,30 @@ "node": ">= 8" } }, + "node_modules/@npmcli/fs": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", + "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/fs/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@parcel/watcher": { "version": "2.5.4", "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.4.tgz", @@ -1232,6 +1343,16 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@pkgr/core": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", @@ -1618,6 +1739,7 @@ "integrity": "sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", "deepmerge": "^4.3.1", @@ -1717,6 +1839,7 @@ "integrity": "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -1766,6 +1889,7 @@ "integrity": "sha512-nm3cvFN9SqZGXjmw5bZ6cGmvJSyJPn0wU9gHAZZHDnZl2wF9PhHv78Xf06E0MaNk4zLVHL8hb2/c32XvyJOLQg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.53.1", "@typescript-eslint/types": "8.53.1", @@ -2259,12 +2383,22 @@ "win32" ] }, + "node_modules/abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2316,7 +2450,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -2326,7 +2459,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -2383,7 +2515,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", "integrity": "sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -2534,7 +2665,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, "license": "MIT" }, "node_modules/base64-js": { @@ -2671,7 +2801,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -2688,7 +2817,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -2758,7 +2886,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -2771,7 +2898,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, "node_modules/commenting": { @@ -2824,9 +2950,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, "license": "MIT", - "peer": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -2907,7 +3031,6 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2926,8 +3049,7 @@ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/deepmerge": { "version": "4.3.1", @@ -3031,11 +3153,16 @@ "node": ">= 0.4" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, "license": "MIT" }, "node_modules/es-abstract": { @@ -3245,7 +3372,6 @@ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -3430,6 +3556,7 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -3595,7 +3722,6 @@ "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@types/json-schema": "^7.0.15" }, @@ -3634,7 +3760,6 @@ "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "estraverse": "^5.1.0" }, @@ -3741,8 +3866,7 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/fastq": { "version": "1.20.1", @@ -3778,7 +3902,6 @@ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "flat-cache": "^4.0.0" }, @@ -3805,7 +3928,6 @@ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -3823,7 +3945,6 @@ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" @@ -3837,8 +3958,7 @@ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/for-each": { "version": "0.3.5", @@ -3856,6 +3976,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -3875,7 +4011,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4002,13 +4137,33 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "is-glob": "^4.0.3" }, @@ -4023,6 +4178,30 @@ "dev": true, "license": "BSD-2-Clause" }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/globals": { "version": "17.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-17.0.0.tgz", @@ -4070,7 +4249,7 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/has-bigints": { @@ -4090,7 +4269,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -4158,7 +4336,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -4167,6 +4344,18 @@ "node": ">= 0.4" } }, + "node_modules/hosted-git-info": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.3.tgz", + "integrity": "sha512-HVJyzUrLIL1c0QmviVh5E8VGyUS7xCFPS6yydaVd1UegW+ibV/CohqTH9MkOLDp5o+rb82DMo77PTuc9F/8GKw==", + "license": "ISC", + "dependencies": { + "lru-cache": "^7.5.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/idb-keyval": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.2.tgz", @@ -4234,7 +4423,6 @@ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.8.19" } @@ -4365,7 +4553,6 @@ "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -4442,7 +4629,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -4700,9 +4886,22 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } }, "node_modules/js-yaml": { "version": "4.1.1", @@ -4722,8 +4921,16 @@ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", + "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", "license": "MIT", - "peer": true + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } }, "node_modules/json-schema-traverse": { "version": "0.4.1", @@ -4737,8 +4944,7 @@ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/json5": { "version": "1.0.2", @@ -4759,7 +4965,6 @@ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "json-buffer": "3.0.1" } @@ -4777,7 +4982,6 @@ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -4786,6 +4990,44 @@ "node": ">= 0.8.0" } }, + "node_modules/license-checker-rseidelsohn": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/license-checker-rseidelsohn/-/license-checker-rseidelsohn-4.4.2.tgz", + "integrity": "sha512-Sf8WaJhd2vELvCne+frS9AXqnY/vv591s2/nZcJDwTnoNgltG4mAmoenffVb8L2YPRYbxARLyrHJBC38AVfpuA==", + "license": "BSD-3-Clause", + "dependencies": { + "chalk": "4.1.2", + "debug": "^4.3.4", + "lodash.clonedeep": "^4.5.0", + "mkdirp": "^1.0.4", + "nopt": "^7.2.0", + "read-installed-packages": "^2.0.1", + "semver": "^7.3.5", + "spdx-correct": "^3.1.1", + "spdx-expression-parse": "^3.0.1", + "spdx-satisfies": "^5.0.1", + "treeify": "^1.1.0" + }, + "bin": { + "license-checker-rseidelsohn": "bin/license-checker-rseidelsohn.js" + }, + "engines": { + "node": ">=18", + "npm": ">=8" + } + }, + "node_modules/license-checker-rseidelsohn/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/lilconfig": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", @@ -4809,7 +5051,6 @@ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "p-locate": "^5.0.0" }, @@ -4827,13 +5068,27 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "license": "ISC", + "engines": { + "node": ">=12" + } }, "node_modules/magic-string": { "version": "0.30.21", @@ -4905,7 +5160,6 @@ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -4951,7 +5205,6 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" @@ -4970,6 +5223,18 @@ "node": ">= 18" } }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/moment": { "version": "2.30.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", @@ -4984,7 +5249,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/nanoid": { @@ -5037,6 +5301,57 @@ "license": "MIT", "optional": true }, + "node_modules/nopt": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "license": "ISC", + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/normalize-package-data": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-5.0.0.tgz", + "integrity": "sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q==", + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^6.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", + "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -5151,7 +5466,6 @@ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", @@ -5188,7 +5502,6 @@ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "yocto-queue": "^0.1.0" }, @@ -5205,7 +5518,6 @@ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "p-limit": "^3.0.2" }, @@ -5216,6 +5528,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, "node_modules/package-name-regex": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/package-name-regex/-/package-name-regex-2.0.6.tgz", @@ -5248,7 +5566,6 @@ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -5257,9 +5574,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -5271,6 +5586,28 @@ "dev": true, "license": "MIT" }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -5321,6 +5658,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -5444,7 +5782,6 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8.0" } @@ -5455,6 +5792,7 @@ "integrity": "sha512-yEPsovQfpxYfgWNhCfECjG5AQaO+K3dp6XERmOepyPDVqcJm+bjyCVO3pmU+nAPe0N5dDvekfGezt/EIiRe1TA==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -5530,6 +5868,41 @@ ], "license": "MIT" }, + "node_modules/read-installed-packages": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/read-installed-packages/-/read-installed-packages-2.0.1.tgz", + "integrity": "sha512-t+fJOFOYaZIjBpTVxiV8Mkt7yQyy4E6MSrrnt5FmPd4enYvpU/9DYGirDmN1XQwkfeuWIhM/iu0t2rm6iSr0CA==", + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^3.1.0", + "debug": "^4.3.4", + "read-package-json": "^6.0.0", + "semver": "2 || 3 || 4 || 5 || 6 || 7", + "slide": "~1.1.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.2" + } + }, + "node_modules/read-package-json": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-6.0.4.tgz", + "integrity": "sha512-AEtWXYfopBj2z5N5PbkAOeNHRPUg5q+Nen7QLxV8M2zJq1ym6/lCz3fYNTCXe19puu2d06jfHhrP7v/S2PtMMw==", + "deprecated": "This package is no longer supported. Please use @npmcli/package-json instead.", + "license": "ISC", + "dependencies": { + "glob": "^10.2.2", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^5.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/readdirp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", @@ -5662,6 +6035,7 @@ "integrity": "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -5819,6 +6193,7 @@ "integrity": "sha512-y5LWb0IlbO4e97Zr7c3mlpabcbBtS+ieiZ9iwDooShpFKWXf62zz5pEPdwrLYm+Bxn1fnbwFGzHuCLSA9tBmrw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", @@ -5838,7 +6213,6 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -5897,9 +6271,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "license": "MIT", - "peer": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -5911,9 +6283,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -6007,6 +6377,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slide": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", + "integrity": "sha512-NwrtjCg+lZoqhFU8fOwl4ay2ei8PaqCBOUV3/ektPY9trO1yQ1oXEfmHAhKArUVUr/hOHvy5f6AdP17dCM0zMw==", + "license": "ISC", + "engines": { + "node": "*" + } + }, "node_modules/source-code-pro": { "version": "2.42.0", "resolved": "https://registry.npmjs.org/source-code-pro/-/source-code-pro-2.42.0.tgz", @@ -6034,7 +6425,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/spdx-compare/-/spdx-compare-1.0.0.tgz", "integrity": "sha512-C1mDZOX0hnu0ep9dfmuoi03+eOdDoz2yvK79RxbcrVEG1NO1Ph35yW102DHWKN4pk80nwCgeMmSY5L25VE4D9A==", - "dev": true, "license": "MIT", "dependencies": { "array-find-index": "^1.0.2", @@ -6042,18 +6432,26 @@ "spdx-ranges": "^2.0.0" } }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, "node_modules/spdx-exceptions": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", - "dev": true, "license": "CC-BY-3.0" }, "node_modules/spdx-expression-parse": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, "license": "MIT", "dependencies": { "spdx-exceptions": "^2.1.0", @@ -6074,21 +6472,18 @@ "version": "3.0.22", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz", "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==", - "dev": true, "license": "CC0-1.0" }, "node_modules/spdx-ranges": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/spdx-ranges/-/spdx-ranges-2.1.1.tgz", "integrity": "sha512-mcdpQFV7UDAgLpXEE/jOMqvK4LBoO0uTQg0uvXUewmEFhpiZx5yJSZITHB8w1ZahKdhfZqP5GPEOKLyEq5p8XA==", - "dev": true, "license": "(MIT AND CC-BY-3.0)" }, "node_modules/spdx-satisfies": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/spdx-satisfies/-/spdx-satisfies-5.0.1.tgz", "integrity": "sha512-Nwor6W6gzFp8XX4neaKQ7ChV4wmpSh2sSDemMFSzHxpTw460jxFYeOn+jq4ybnSSw/5sc3pjka9MQPouksQNpw==", - "dev": true, "license": "MIT", "dependencies": { "spdx-compare": "^1.0.0", @@ -6124,7 +6519,21 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -6198,7 +6607,19 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -6265,6 +6686,7 @@ "integrity": "sha512-MhSWfWEpG5T57z0Oyfk9D1GhAz/KTZKZZlWtGEsy9zNk2fafpuU7sJQlXNSA8HtvwKxVC9XlDyl5YovXUXjjHA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", @@ -6445,6 +6867,15 @@ "tree-kill": "cli.js" } }, + "node_modules/treeify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/treeify/-/treeify-1.1.0.tgz", + "integrity": "sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, "node_modules/ts-api-utils": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", @@ -6464,6 +6895,7 @@ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -6528,7 +6960,6 @@ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "prelude-ls": "^1.2.1" }, @@ -6620,6 +7051,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -6685,6 +7117,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "napi-postinstall": "^0.3.0" }, @@ -6737,12 +7170,23 @@ "dev": true, "license": "MIT" }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, "node_modules/vite": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -6867,9 +7311,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "license": "ISC", - "peer": true, "dependencies": { "isexe": "^2.0.0" }, @@ -6975,7 +7417,6 @@ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -6998,6 +7439,24 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -7063,7 +7522,6 @@ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, diff --git a/frontend/package.json b/frontend/package.json index e90047f897..48da952460 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -21,8 +21,8 @@ "lint-fix": "eslint . --fix && tsc --noEmit", "---------- INTERNAL ----------": "", "setup": "node package-installer.js && node branding-installer.js", - "native:build-dev": "wasm-pack build ./wasm --dev --target=web --no-default-features --features native && vite build --mode dev", - "native:build-production": "wasm-pack build ./wasm --release --target=web --no-default-features --features native && vite build", + "native:build-dev": "wasm-pack build ./wasm --dev --target=web --no-default-features --features native && vite build --mode native", + "native:build-production": "wasm-pack build ./wasm --release --target=web --no-default-features --features native && vite build --mode native", "wasm:build-dev": "wasm-pack build ./wasm --dev --target=web", "wasm:build-profiling": "wasm-pack build ./wasm --profiling --target=web", "wasm:build-production": "wasm-pack build ./wasm --release --target=web", @@ -39,6 +39,7 @@ "source-sans-pro": "2.45.0" }, "devDependencies": { + "license-checker-rseidelsohn": "^4.4.2", "@eslint/compat": "^2.0.1", "@eslint/eslintrc": "^3.3.3", "@eslint/js": "^9.39.2", @@ -55,7 +56,6 @@ "prettier": "^3.8.0", "prettier-plugin-svelte": "^3.4.1", "process": "^0.11.10", - "rollup-plugin-license": "^3.6.0", "sass": "^1.97.2", "svelte": "5.47.1", "svelte-preprocess": "^6.0.3", diff --git a/frontend/src/state-providers/dialog.ts b/frontend/src/state-providers/dialog.ts index 82b070b1e3..0dd03171e7 100644 --- a/frontend/src/state-providers/dialog.ts +++ b/frontend/src/state-providers/dialog.ts @@ -81,7 +81,6 @@ export function createDialogState(editor: Editor) { editor.subscriptions.subscribeJsMessage(TriggerDisplayThirdPartyLicensesDialog, async () => { const BACKUP_URL = "https://editor.graphite.art/third-party-licenses.txt"; let licenseText = `Content was not able to load. Please check your network connection and try again.\n\nOr visit ${BACKUP_URL} for the license notices.`; - if (editor.handle.inDevelopmentMode()) licenseText = `Third-party licenses are not available in development builds.\n\nVisit ${BACKUP_URL} for the license notices.`; const response = await fetch("/third-party-licenses.txt"); if (response.ok && response.headers.get("Content-Type")?.includes("text/plain")) licenseText = await response.text(); diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 5713812e38..749e1a3437 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,14 +1,10 @@ -/* eslint-disable no-console */ - -import { spawnSync } from "child_process"; -import fs from "fs"; -import os from "os"; +import { execSync } from "child_process"; +import { readFileSync } from "fs"; import path from "path"; import { svelte } from "@sveltejs/vite-plugin-svelte"; -import rollupPluginLicense, { type Dependency } from "rollup-plugin-license"; import { sveltePreprocess } from "svelte-preprocess"; -import { defineConfig } from "vite"; +import { defineConfig, type PluginOption } from "vite"; import { DynamicPublicDirectory as viteMultipleAssets } from "vite-multiple-assets"; const projectRootDir = path.resolve(__dirname); @@ -16,35 +12,7 @@ const projectRootDir = path.resolve(__dirname); // https://vitejs.dev/config/ export default defineConfig(({ mode }) => { return { - plugins: [ - svelte({ - preprocess: [sveltePreprocess()], - onwarn(warning, defaultHandler) { - const suppressed = [ - "css-unused-selector", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json` - "vite-plugin-svelte-css-no-scopable-elements", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json` - "a11y-no-static-element-interactions", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json` - "a11y-no-noninteractive-element-interactions", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json` - "a11y-click-events-have-key-events", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json` - "a11y_consider_explicit_label", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json` - "a11y_click_events_have_key_events", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json` - "a11y_no_noninteractive_element_interactions", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json` - ]; - if (suppressed.includes(warning.code)) return; - - defaultHandler?.(warning); - }, - }), - viteMultipleAssets( - // Additional static asset directories besides `public/` - [ - { input: "../demo-artwork/**", output: "demo-artwork" }, - { input: "../branding/favicons/**", output: "" }, - ], - // Options where we set custom MIME types - { mimeTypes: { ".graphite": "application/json" } }, - ), - ], + plugins: plugins(mode), resolve: { alias: [ { find: /@branding\/(.*\.svg)/, replacement: path.resolve(projectRootDir, "../branding", "$1?raw") }, @@ -57,374 +25,62 @@ export default defineConfig(({ mode }) => { port: 8080, host: "0.0.0.0", }, - build: { - rollupOptions: { - plugins: - mode !== "dev" - ? [ - rollupPluginLicense({ - thirdParty: { - includePrivate: false, - multipleVersions: true, - allow: { - test: `(${getAcceptedLicenses()})`, - failOnUnlicensed: true, - failOnViolation: true, - }, - output: { - file: path.resolve(__dirname, "./dist/third-party-licenses.txt"), - template: formatThirdPartyLicenses, - }, - }, - }), - ] - : [], - }, - }, }; }); -type LicenseInfo = { - licenseName: string; - licenseText: string; - noticeText?: string; - packages: PackageInfo[]; -}; - -type PackageInfo = { - name: string; - version: string; - author: string; - repository: string; -}; - -function formatThirdPartyLicenses(jsLicenses: Dependency[]): string { - // Generate the Rust license information. - const rustLicenses = generateRustLicenses(); - const additionalLicenses = generateAdditionalLicenses(); - - // Ensure we have the required license information to work with before proceeding. - if (rustLicenses.length === 0) { - // This is probably caused by `cargo about` not being installed. - console.error("Could not run `cargo about`, which is required to generate license information."); - console.error("To install cargo-about on your system, you can run `cargo install cargo-about`."); - console.error("License information is required in production builds. Aborting."); - - process.exit(1); - } - if (jsLicenses.length === 0) { - console.error("No JavaScript package licenses were found by `rollup-plugin-license`. Please investigate."); - console.error("License information is required in production builds. Aborting."); - - process.exit(1); - } - - let licenses = rustLicenses.concat(additionalLicenses); - - // SPECIAL CASE: Find then duplicate this license if one of its packages is `path-bool`, adding its notice text. - let foundLicensesIndex: number | undefined = undefined; - let foundPackagesIndex: number | undefined = undefined; - licenses.forEach((license, licenseIndex) => { - license.packages.forEach((pkg, pkgIndex) => { - if (pkg.name === "path-bool") { - foundLicensesIndex = licenseIndex; - foundPackagesIndex = pkgIndex; - } - }); - }); - if (foundLicensesIndex !== undefined && foundPackagesIndex !== undefined) { - const license = licenses[foundLicensesIndex]; - const pkg = license.packages[foundPackagesIndex]; - - license.packages = license.packages.filter((pkg) => pkg.name !== "path-bool"); - const noticeText = fs.readFileSync(path.resolve(__dirname, "../libraries/path-bool/NOTICE"), "utf8"); - - licenses.push({ - licenseName: license.licenseName, - licenseText: license.licenseText, - noticeText, - packages: [pkg], - }); - } - - // Extend the license list with the provided JS licenses. - jsLicenses.forEach((jsLicense) => { - const name = jsLicense.name || ""; - const version = jsLicense.version || ""; - const author = jsLicense.author?.text() || ""; - const licenseName = jsLicense.license || ""; - const licenseText = trimBlankLines(jsLicense.licenseText || ""); - const noticeText = trimBlankLines(jsLicense.noticeText || ""); - - let repository = jsLicense.repository || ""; - if (repository && typeof repository === "object") repository = repository.url; - - const matchedLicense = licenses.find( - (license) => license.licenseName === licenseName && trimBlankLines(license.licenseText || "") === licenseText && trimBlankLines(license.noticeText || "") === noticeText, - ); - - const pkg: PackageInfo = { name, version, author, repository }; - if (matchedLicense) matchedLicense.packages.push(pkg); - else licenses.push({ licenseName, licenseText, noticeText, packages: [pkg] }); - }); - - // Combine any license notices into the license text. - licenses.forEach((license, index) => { - if (license.noticeText) { - licenses[index].licenseText += "\n\n"; - licenses[index].licenseText += " _______________________________________\n"; - licenses[index].licenseText += "│ │\n"; - licenses[index].licenseText += "│ THE FOLLOWING NOTICE FILE IS INCLUDED │\n"; - licenses[index].licenseText += "│ │\n"; - licenses[index].licenseText += " ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n"; - licenses[index].licenseText += `${license.noticeText}\n`; - licenses[index].noticeText = undefined; - } - }); - - // De-duplicate any licenses with the same text by merging their lists of packages. - const licensesNormalizedWhitespace = licenses.map((license) => license.licenseText.replace(/[\n\s]+/g, " ").trim()); - licenses.forEach((currentLicense, currentLicenseIndex) => { - licenses.slice(0, currentLicenseIndex).forEach((comparisonLicense, comparisonLicenseIndex) => { - if (licensesNormalizedWhitespace[currentLicenseIndex] === licensesNormalizedWhitespace[comparisonLicenseIndex]) { - currentLicense.packages.push(...comparisonLicense.packages); - comparisonLicense.packages = []; - // After emptying the packages, the redundant license with no packages will be removed in the next step's `filter()`. - } - }); - }); - - // Filter out first-party internal Graphite crates. - licenses = licenses.filter((license) => { - license.packages = license.packages.filter( - (packageInfo) => - !(packageInfo.repository && packageInfo.repository.toLowerCase().includes("github.com/GraphiteEditor/Graphite".toLowerCase())) && - !( - packageInfo.author && - packageInfo.author.toLowerCase().includes("contact@graphite.art") && - // Exclude a comma which indicates multiple authors, which we need to not filter out - !packageInfo.author.toLowerCase().includes(",") - ), - ); - return license.packages.length > 0; - }); - - // Sort the licenses by the number of packages using the same license, and then alphabetically by license name. - licenses.sort((a, b) => a.licenseText.localeCompare(b.licenseText)); - licenses.sort((a, b) => a.licenseName.localeCompare(b.licenseName)); - licenses.sort((a, b) => b.packages.length - a.packages.length); - // Sort the individual packages using each license alphabetically. - licenses.forEach((license) => { - license.packages.sort((a, b) => a.name.localeCompare(b.name)); - }); - - // Prepare a header for the license notice. - let formattedLicenseNotice = ""; - formattedLicenseNotice += "▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐\n"; - formattedLicenseNotice += "▐▐ ▐▐\n"; - formattedLicenseNotice += "▐▐ GRAPHITE THIRD-PARTY SOFTWARE LICENSE NOTICES ▐▐\n"; - formattedLicenseNotice += "▐▐ ▐▐\n"; - formattedLicenseNotice += "▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐\n"; - - // Append a block for each license shared by multiple packages with identical license text. - licenses.forEach((license) => { - let packagesWithSameLicense = license.packages.map((packageInfo) => { - const { name, version, author, repository } = packageInfo; - - // Remove the `git+` or `git://` prefix and `.git` suffix. - let repo = repository; - if (repo.startsWith("git+")) repo = repo.slice("git+".length); - if (repo.startsWith("git://")) repo = repo.slice("git://".length); - if (repo.endsWith(".git")) repo = repo.slice(0, -".git".length); - if (repo.endsWith(".git#release")) repo = repo.slice(0, -".git#release".length); - - return `${name} ${version}${author ? ` - ${author}` : ""}${repo ? ` - ${repo}` : ""}`; - }); - const multi = packagesWithSameLicense.length !== 1; - const saysLicense = license.licenseName.toLowerCase().includes("license"); - const header = `The package${multi ? "s" : ""} listed here ${multi ? "are" : "is"} licensed under the terms of the ${license.licenseName}${saysLicense ? "" : " license"} printed beneath`; - const packagesLineLength = Math.max(header.length, ...packagesWithSameLicense.map((line) => line.length)); - packagesWithSameLicense = packagesWithSameLicense.map((line) => `│ ${line}${" ".repeat(packagesLineLength - line.length)} │`); - - formattedLicenseNotice += "\n"; - formattedLicenseNotice += ` ${"_".repeat(packagesLineLength + 2)}\n`; - formattedLicenseNotice += `│ ${" ".repeat(packagesLineLength)} │\n`; - formattedLicenseNotice += `│ ${header}${" ".repeat(packagesLineLength - header.length)} │\n`; - formattedLicenseNotice += `│${"_".repeat(packagesLineLength + 2)}│\n`; - formattedLicenseNotice += `${packagesWithSameLicense.join("\n")}\n`; - formattedLicenseNotice += ` ${"‾".repeat(packagesLineLength + 2)}\n`; - formattedLicenseNotice += `${license.licenseText}\n`; - }); - - formattedLicenseNotice += "\n"; - return formattedLicenseNotice; -} - -// Include additional licenses that aren't automatically generated by `cargo about` or `rollup-plugin-license`. -function generateAdditionalLicenses(): LicenseInfo[] { - const ADDITIONAL_LICENSES = [ - { - licenseName: "SIL Open Font License 1.1", - licenseTextPath: "node_modules/source-sans-pro/LICENSE.txt", - manifestPath: "node_modules/source-sans-pro/package.json", - }, - { - licenseName: "SIL Open Font License 1.1", - licenseTextPath: "node_modules/source-code-pro/LICENSE.md", - manifestPath: "node_modules/source-code-pro/package.json", - }, +function plugins(mode: string): PluginOption[] { + const plugins = [ + svelte({ + preprocess: [sveltePreprocess()], + onwarn(warning, defaultHandler) { + const suppressed = [ + "css-unused-selector", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json` + "vite-plugin-svelte-css-no-scopable-elements", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json` + "a11y-no-static-element-interactions", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json` + "a11y-no-noninteractive-element-interactions", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json` + "a11y-click-events-have-key-events", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json` + "a11y_consider_explicit_label", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json` + "a11y_click_events_have_key_events", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json` + "a11y_no_noninteractive_element_interactions", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json` + ]; + if (suppressed.includes(warning.code)) return; + + defaultHandler?.(warning); + }, + }), + viteMultipleAssets( + // Additional static asset directories besides `public/` + [ + { input: "../demo-artwork/**", output: "demo-artwork" }, + { input: "../branding/favicons/**", output: "" }, + ], + // Options where we set custom MIME types + { mimeTypes: { ".graphite": "application/json" } }, + ), ]; - return ADDITIONAL_LICENSES.map(({ licenseName, licenseTextPath, manifestPath }) => { - const licenseText = (fs.existsSync(licenseTextPath) && fs.readFileSync(licenseTextPath, "utf8")) || ""; - - const manifestJSON = (fs.existsSync(manifestPath) && JSON.parse(fs.readFileSync(manifestPath, "utf8"))) || {}; - const name = manifestJSON.name || ""; - const version = manifestJSON.version || ""; - const author = manifestJSON.author.name || manifestJSON.author || ""; - const repository = manifestJSON.repository?.url || ""; - - return { - licenseName, - licenseText: trimBlankLines(licenseText), - packages: [{ name, version, author, repository }], - }; - }); -} - -function generateRustLicenses(): LicenseInfo[] { - // Log the starting status to the build output. - console.info("\n\nGenerating license information for Rust code\n"); - - try { - // Call `cargo about` in the terminal to generate the license information for Rust crates. - // The `about.hbs` file is written so it generates a valid JavaScript array expression which we evaluate below. - const { licenses, status, stderr } = (() => { - // On Windows, we have to write the output to a temporary file because of powershell's handling of stdout. - if (os.platform() === "win32") { - const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "graphite-licenses-")); - const licensesFile = path.join(tmpDir, "licenses.js"); - - const { status, stderr } = spawnSync("cargo", ["about", "generate", "about.hbs", "-o", licensesFile], { - cwd: path.join(__dirname, ".."), - encoding: "utf8", - shell: true, - windowsHide: true, // Hide the terminal on Windows - }); - - const licenses = fs.existsSync(licensesFile) ? fs.readFileSync(licensesFile, "utf8") : ""; - - return { licenses, status, stderr }; - } else { - const { stdout, status, stderr } = spawnSync("cargo", ["about", "generate", "about.hbs"], { - cwd: path.join(__dirname, ".."), - encoding: "utf8", - shell: true, + if (mode !== "native") { + plugins.push({ + name: "third-party-licenses", + buildStart() { + try { + execSync("cargo run -p third-party-licenses -- --web", { + stdio: "inherit", + }); + } catch (_e) { + this.error("Failed to generate third-party licenses"); + } + }, + generateBundle() { + const source = readFileSync(path.resolve(projectRootDir, "third-party-licenses.txt"), "utf-8"); + this.emitFile({ + type: "asset", + fileName: "third-party-licenses.txt", + source, }); - - return { licenses: stdout, status, stderr }; - } - })(); - - // If the command failed, print the error message and exit early. - if (status !== 0) { - // Cargo returns 101 when the subcommand (`about`) wasn't found, so we skip printing the below error message in that case. - if (status !== 101) { - console.error("cargo-about failed", status, stderr); - } - return []; - } - - // Make sure the output starts with this expected label, which lets us know the file generated with expected output. - // We don't want to eval an error message or something else, so we fail early if that happens. - if (!licenses.trim().startsWith("GENERATED_BY_CARGO_ABOUT:")) { - console.error("Unexpected output from cargo-about", licenses); - return []; - } - - // Convert the array JS syntax string into an actual JS array in memory. - // Security-wise, eval() isn't any worse than require(), but it's able to work without a temporary file. - // We call eval indirectly to avoid a warning as explained here: . - const indirectEval = eval; - const licensesArray = indirectEval(licenses) as LicenseInfo[]; - - // Remove the HTML character encoding caused by Handlebars. - const rustLicenses = (licensesArray || []).map( - (rustLicense): LicenseInfo => ({ - licenseName: htmlDecode(rustLicense.licenseName), - licenseText: trimBlankLines(htmlDecode(rustLicense.licenseText)), - packages: rustLicense.packages.map( - (packageInfo): PackageInfo => ({ - name: htmlDecode(packageInfo.name), - version: htmlDecode(packageInfo.version), - author: htmlDecode(packageInfo.author) - .replace(/\[(.*), \]/, "$1") - .replace("[]", ""), - repository: htmlDecode(packageInfo.repository), - }), - ), - }), - ); - - return rustLicenses; - } catch (_) { - return []; - } -} - -function htmlDecode(input: string): string { - if (!input) return input; - - const htmlEntities = { - nbsp: " ", - copy: "©", - reg: "®", - lt: "<", - gt: ">", - amp: "&", - apos: "'", - quot: `"`, - }; - - return input.replace(/&([^;]+);/g, (entity: string, entityCode: string) => { - const maybeEntity = Object.entries(htmlEntities).find(([key, _]) => key === entityCode); - if (maybeEntity) return maybeEntity[1]; - - let match; - if ((match = entityCode.match(/^#x([\da-fA-F]+)$/))) { - return String.fromCharCode(parseInt(match[1], 16)); - } - if ((match = entityCode.match(/^#(\d+)$/))) { - return String.fromCharCode(~~match[1]); - } - return entity; - }); -} - -function trimBlankLines(input: string): string { - let result = input.replace(/\r/g, ""); - - while (result.charAt(0) === "\r" || result.charAt(0) === "\n") { - result = result.slice(1); - } - while (result.slice(-1) === "\r" || result.slice(-1) === "\n") { - result = result.slice(0, -1); + }, + }); } - return result; -} - -function getAcceptedLicenses() { - const tomlContent = fs.readFileSync(path.resolve(__dirname, "../about.toml"), "utf8"); - - const licensesBlock = tomlContent?.match(/accepted\s*=\s*\[([^\]]*)\]/)?.[1] || ""; - - return licensesBlock - .split("\n") - .map((line) => line.replace(/#.*$/, "")) // Remove comments - .join("\n") - .split(",") - .map((license) => license.trim().replace(/"/g, "")) - .filter((license) => license.length > 0) - .join(" OR "); + return plugins; } diff --git a/package.json b/package.json index 44bc1f9614..eb78e0b812 100644 --- a/package.json +++ b/package.json @@ -4,15 +4,15 @@ "scripts": { "---------- DEV SERVER ----------": "", "start": "cd frontend && npm start", - "start-desktop": "cd frontend && npm run build-native-dev && cargo run -p graphite-desktop-bundle -- open", + "start-desktop": "cd frontend && npm run build-native-dev && cargo run -p third-party-licenses && cargo run -p graphite-desktop-bundle -- open", "profiling": "cd frontend && npm run profiling", "production": "cd frontend && npm run production", "---------- BUILDS ----------": "", "build-dev": "cd frontend && npm run build-dev", "build-profiling": "cd frontend && npm run build-profiling", "build": "cd frontend && npm run build", - "build-desktop": "cd frontend && npm run build-native && cargo run -r -p graphite-desktop-bundle", - "build-desktop-dev": "cd frontend && npm run build-native-dev && cargo run -p graphite-desktop-bundle", + "build-desktop": "cd frontend && npm run build-native && cargo run -p third-party-licenses && cargo run -r -p graphite-desktop-bundle", + "build-desktop-dev": "cd frontend && npm run build-native-dev && cargo run -p third-party-licenses && cargo run -p graphite-desktop-bundle", "---------- UTILITIES ----------": "", "lint": "cd frontend && npm run lint", "lint-fix": "cd frontend && npm run lint-fix" diff --git a/tools/third-party-licenses/.gitignore b/tools/third-party-licenses/.gitignore new file mode 100644 index 0000000000..db71f1e3f6 --- /dev/null +++ b/tools/third-party-licenses/.gitignore @@ -0,0 +1 @@ +*.hash diff --git a/tools/third-party-licenses/Cargo.toml b/tools/third-party-licenses/Cargo.toml new file mode 100644 index 0000000000..b2ec0cf5ae --- /dev/null +++ b/tools/third-party-licenses/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "third-party-licenses" +edition.workspace = true +version.workspace = true +license.workspace = true +authors.workspace = true + +[dependencies] +serde = { workspace = true } +serde_json = { workspace = true } +lzma-rust2 = { workspace = true } +cef-dll-sys = { workspace = true } +scraper = "0.22" +sha256 = "1" diff --git a/tools/third-party-licenses/build.rs b/tools/third-party-licenses/build.rs new file mode 100644 index 0000000000..1905d7302c --- /dev/null +++ b/tools/third-party-licenses/build.rs @@ -0,0 +1,5 @@ +fn main() { + println!("cargo:rerun-if-env-changed=DEP_CEF_DLL_WRAPPER_CEF_DIR"); + let cef_dir = std::env::var("DEP_CEF_DLL_WRAPPER_CEF_DIR").unwrap(); + println!("cargo:rustc-env=CEF_PATH={cef_dir}"); +} diff --git a/tools/third-party-licenses/src/cargo.rs b/tools/third-party-licenses/src/cargo.rs new file mode 100644 index 0000000000..1343e5ed90 --- /dev/null +++ b/tools/third-party-licenses/src/cargo.rs @@ -0,0 +1,107 @@ +use crate::{LicenceSource, LicenseEntry, Package}; +use serde::Deserialize; +use sha256::TrySha256Digest; +use std::{ + path::PathBuf, + process::{self, Command}, +}; + +pub struct CargoAboutLicenseSource {} + +impl CargoAboutLicenseSource { + pub fn new() -> Self { + Self {} + } +} + +impl LicenceSource for CargoAboutLicenseSource { + fn licenses(&self) -> Vec { + parse(run()) + } + fn hash(&self) -> String { + let lock_path = PathBuf::from(env!("CARGO_WORKSPACE_DIR")).join("Cargo.lock"); + lock_path.digest().unwrap_or_else(|e| { + eprintln!("Failed to hash Cargo.lock: {e}"); + process::exit(1); + }) + } +} + +#[derive(Deserialize)] +struct Output { + licenses: Vec, +} + +#[derive(Deserialize)] +struct License { + name: Option, + text: Option, + used_by: Vec, +} + +#[derive(Deserialize)] +struct UsedBy { + #[serde(rename = "crate")] + crate_info: Crate, +} + +#[derive(Deserialize)] +struct Crate { + name: Option, + version: Option, + authors: Option>, + repository: Option, +} + +fn parse(parsed: Output) -> Vec { + parsed + .licenses + .into_iter() + .map(|license| { + let packages = license + .used_by + .into_iter() + .map(|used| { + let name = used.crate_info.name.as_deref().unwrap_or_default(); + let version = used.crate_info.version.as_deref().unwrap_or_default(); + let display_name = if version.is_empty() { name.to_string() } else { format!("{name}@{version}") }; + + let repository = used.crate_info.repository.filter(|s| !s.is_empty()); + + Package { + name: display_name, + authors: used.crate_info.authors.unwrap_or_default(), + url: repository, + } + }) + .collect(); + + LicenseEntry { + name: license.name, + text: license.text.as_deref().unwrap_or_default().to_string(), + packages, + } + }) + .collect() +} + +fn run() -> Output { + let output = Command::new("cargo") + .args(["about", "generate", "--format", "json", "--frozen"]) + .current_dir(env!("CARGO_WORKSPACE_DIR")) + .output() + .unwrap_or_else(|e| { + eprintln!("Failed to run cargo about generate: {e}"); + process::exit(1) + }); + + if !output.status.success() { + eprintln!("cargo about generate failed:\n{}", String::from_utf8_lossy(&output.stderr)); + process::exit(1) + } + + serde_json::from_str(&String::from_utf8(output.stdout).expect("cargo about generate should return valid UTF-8")).unwrap_or_else(|e| { + eprintln!("Failed to parse cargo about generate JSON: {e}"); + process::exit(1) + }) +} diff --git a/tools/third-party-licenses/src/cef.rs b/tools/third-party-licenses/src/cef.rs new file mode 100644 index 0000000000..5311a5585f --- /dev/null +++ b/tools/third-party-licenses/src/cef.rs @@ -0,0 +1,102 @@ +use lzma_rust2::XzReader; +use scraper::{Html, Selector}; +use std::io::Read; +use std::path::PathBuf; +use std::{fs, process}; + +use crate::{LicenceSource, LicenseEntry, Package}; + +pub struct CefLicenseSource; + +impl CefLicenseSource { + pub fn new() -> Self { + Self {} + } +} + +impl LicenceSource for CefLicenseSource { + fn licenses(&self) -> Vec { + let html = read(); + parse(&html) + } + fn hash(&self) -> String { + let html = read(); + sha256::digest(html) + } +} + +fn read() -> String { + let cef_path = PathBuf::from(env!("CEF_PATH")); + let cef_credits = std::fs::read_dir(&cef_path) + .unwrap_or_else(|e| { + eprintln!("Failed to read CEF_PATH directory {}: {e}", cef_path.display()); + process::exit(1); + }) + .filter_map(|entry| entry.ok()) + .find(|entry| { + let name = entry.file_name(); + name.eq_ignore_ascii_case("credits.html") || name.eq_ignore_ascii_case("credits.html.xz") + }) + .map(|entry| entry.path()) + .unwrap_or_else(|| { + eprintln!("Could not find CREDITS.html or CREDITS.html.xz in {}", cef_path.display()); + process::exit(1); + }); + + let decompress_xz = cef_credits.extension().map(|ext| ext.eq_ignore_ascii_case("xz")).unwrap_or(false); + + if decompress_xz { + let file = fs::File::open(&cef_credits).unwrap_or_else(|e| { + eprintln!("Failed to open CEF credits file {}: {e}", cef_credits.display()); + process::exit(1); + }); + let mut reader = XzReader::new(file, false); + let mut html = String::new(); + reader.read_to_string(&mut html).unwrap_or_else(|e| { + eprintln!("Failed to decompress CEF credits file {}: {e}", cef_credits.display()); + process::exit(1); + }); + html + } else { + fs::read_to_string(&cef_credits).unwrap_or_else(|e| { + eprintln!("Failed to read CEF credits file {}: {e}", cef_credits.display()); + process::exit(1); + }) + } +} + +fn parse(html: &str) -> Vec { + let document = Html::parse_document(html); + + let product_sel = Selector::parse("div.product").unwrap(); + let title_sel = Selector::parse("span.title").unwrap(); + let homepage_sel = Selector::parse("span.homepage a").unwrap(); + let license_sel = Selector::parse("div.license pre").unwrap(); + + document + .select(&product_sel) + .filter_map(|product| { + let name: String = product.select(&title_sel).next().map(|el| el.text().collect()).unwrap_or_default(); + + if name.is_empty() { + return None; + } + + let homepage = product.select(&homepage_sel).next().and_then(|el| el.value().attr("href").map(String::from)); + + let license_text: String = product.select(&license_sel).next().map(|el| el.text().collect::()).unwrap_or_default().trim().to_string(); + + let pkg = Package { + name, + url: homepage, + authors: Vec::new(), + }; + + Some(LicenseEntry { + name: None, + text: license_text, + packages: vec![pkg], + }) + }) + .collect() +} diff --git a/tools/third-party-licenses/src/main.rs b/tools/third-party-licenses/src/main.rs new file mode 100644 index 0000000000..edf4a062a2 --- /dev/null +++ b/tools/third-party-licenses/src/main.rs @@ -0,0 +1,232 @@ +use lzma_rust2::{XzOptions, XzWriter}; +use sha256::Sha256Digest; +use std::collections::BTreeMap; +use std::io::Write; +use std::path::PathBuf; +use std::{fs, process}; + +mod cargo; +mod cef; +mod npm; + +use crate::cargo::CargoAboutLicenseSource; +use crate::cef::CefLicenseSource; +use crate::npm::NpmLicenseSource; + +pub trait LicenceSource { + fn licenses(&self) -> Vec; + fn hash(&self) -> String; +} + +pub struct LicenseEntry { + name: Option, + text: String, + packages: Vec, +} + +pub struct Package { + name: String, + authors: Vec, + url: Option, +} + +struct Run { + output_hash: String, + cargo_hash: String, + npm_hash: String, + cef_hash: Option, +} + +impl Run { + fn hash(&self) -> String { + format!("{}{}{}{}", self.cargo_hash, self.npm_hash, self.cef_hash.as_deref().unwrap_or_default(), self.output_hash).digest() + } +} + +fn main() { + let web = std::env::args().any(|arg| arg == "--web"); + + let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let workspace_dir = PathBuf::from(env!("CARGO_WORKSPACE_DIR")); + let output_path = if web { + workspace_dir.join("frontend/third-party-licenses.txt") + } else { + workspace_dir.join("third-party-licenses.txt.xz") + }; + let current_hash_path = manifest_dir.join(if web { "web.hash" } else { "desktop.hash" }); + + let cargo_source = CargoAboutLicenseSource::new(); + let npm_source = NpmLicenseSource::new(workspace_dir.join("frontend")); + let cef_source = if web { None } else { Some(CefLicenseSource::new()) }; + + let mut run = Run { + cargo_hash: cargo_source.hash(), + npm_hash: npm_source.hash(), + cef_hash: cef_source.as_ref().map(|s| s.hash()), + output_hash: if output_path.exists() { + fs::read_to_string(&output_path).unwrap_or_default().digest() + } else { + Default::default() + }, + }; + + if run.hash() == fs::read_to_string(¤t_hash_path).unwrap_or_default() { + eprintln!("No changes in licenses detected, skipping generation."); + return; + } + eprintln!("Changes in licenses detected, generating new license file."); + + let mut sources = vec![cargo_source.licenses(), npm_source.licenses()]; + if !web { + let cef_source = CefLicenseSource::new(); + sources.push(cef_source.licenses()); + } + let credits = merge_filter_dedup_and_sort(sources); + + let formatted = format_credits(&credits); + + if web { + if let Some(parent) = output_path.parent() { + fs::create_dir_all(parent).unwrap_or_else(|e| { + eprintln!("Failed to create directory {}: {e}", parent.display()); + std::process::exit(1); + }); + } + fs::write(&output_path, &formatted).unwrap_or_else(|e| { + eprintln!("Failed to write {}: {e}", &output_path.display()); + std::process::exit(1); + }); + run.output_hash = formatted.digest(); + } else { + let formatted_compressed = compress(&formatted); + fs::write(&output_path, &formatted_compressed).unwrap_or_else(|e| { + eprintln!("Failed to write {}: {e}", &output_path.display()); + std::process::exit(1); + }); + run.output_hash = formatted_compressed.digest(); + } + + fs::write(¤t_hash_path, run.hash()).unwrap_or_else(|e| { + eprintln!("Failed to write hash file {}: {e}", current_hash_path.display()); + process::exit(1); + }); +} + +fn format_credits(licenses: &Vec) -> String { + let mut out = String::new(); + + out.push_str("▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐\n"); + out.push_str("▐▐ ▐▐\n"); + out.push_str("▐▐ GRAPHITE THIRD-PARTY SOFTWARE LICENSE NOTICES ▐▐\n"); + out.push_str("▐▐ ▐▐\n"); + out.push_str("▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐\n"); + + for license in licenses { + let package_lines: Vec = license + .packages + .iter() + .map(|pkg| match &pkg { + Package { name, authors, url: Some(url) } if !authors.is_empty() => format!("{} [{}] - {}", name, authors.join(", "), url), + Package { name, authors: _, url: Some(url) } => format!("{} - {}", name, url), + Package { name, authors, url: None } if !authors.is_empty() => format!("{} [{}]", name, authors.join(", ")), + _ => pkg.name.clone(), + }) + .collect(); + + let multi = package_lines.len() > 1; + + let header = format!( + "The package{} listed here {} licensed under the terms of the {} printed beneath ({} lines)", + if multi { "s" } else { "" }, + if multi { "are" } else { "is" }, + if let Some(license) = license.name.as_ref() { + format!("\"{}\" license", license) + } else { + "license".to_string() + }, + license.text.lines().count() + ); + + let max_len = std::iter::once(header.len()).chain(package_lines.iter().map(|l| l.chars().count())).max().unwrap_or(0); + + let padded_packages: Vec = package_lines + .iter() + .map(|line| { + let pad = max_len - line.chars().count(); + format!("│ {}{} │", line, " ".repeat(pad)) + }) + .collect(); + + out.push_str(&format!("\n {}\n", "_".repeat(max_len + 2))); + out.push_str(&format!("│ {} │\n", " ".repeat(max_len))); + out.push_str(&format!("│ {}{} │\n", header, " ".repeat(max_len - header.len()))); + out.push_str(&format!("│{}│\n", "_".repeat(max_len + 2))); + out.push_str(&padded_packages.join("\n")); + out.push('\n'); + out.push_str(&format!(" {}", "\u{203e}".repeat(max_len + 2))); + for line in license.text.lines() { + if line.is_empty() { + out.push('\n'); + continue; + } + out.push('\n'); + out.push_str(" "); + out.push_str(line); + } + } + + out.push('\n'); + out +} + +fn merge_filter_dedup_and_sort(sources: Vec>) -> Vec { + let mut all = Vec::new(); + for source in sources { + all.extend(source); + } + filter(&mut all); + let mut all = dedup_by_licence_text(all); + all.sort_by(|a, b| b.packages.len().cmp(&a.packages.len()).then(a.text.len().cmp(&b.text.len()))); + all +} + +fn filter(licenses: &mut Vec) { + licenses.iter_mut().for_each(|l| { + l.packages.retain(|p| !(p.authors.len() == 1 && p.authors[0].contains("contact@graphite.art"))); + }); + licenses.retain(|l| !l.packages.is_empty()); +} + +fn dedup_by_licence_text(vec: Vec) -> Vec { + let mut map: BTreeMap = BTreeMap::new(); + + for entry in vec { + match map.entry(entry.text.clone()) { + std::collections::btree_map::Entry::Occupied(mut e) => { + e.get_mut().packages.extend(entry.packages); + } + std::collections::btree_map::Entry::Vacant(e) => { + e.insert(entry); + } + } + } + + map.into_values().collect() +} + +fn compress(content: &str) -> Vec { + let mut buf = Vec::new(); + let mut writer = XzWriter::new(&mut buf, XzOptions::default()).unwrap_or_else(|e| { + eprintln!("Failed to create XZ writer: {e}"); + std::process::exit(1); + }); + writer.write_all(content.as_bytes()).unwrap_or_else(|e| { + eprintln!("Failed to write compressed credits: {e}"); + std::process::exit(1); + }); + writer.finish().unwrap_or_else(|e| { + eprintln!("Failed to finish XZ compression: {e}"); + std::process::exit(1); + }); + buf +} diff --git a/tools/third-party-licenses/src/npm.rs b/tools/third-party-licenses/src/npm.rs new file mode 100644 index 0000000000..c03bc130f5 --- /dev/null +++ b/tools/third-party-licenses/src/npm.rs @@ -0,0 +1,90 @@ +use serde::Deserialize; +use sha256::TrySha256Digest; +use std::{ + collections::BTreeMap, + fs, + path::PathBuf, + process::{self, Command}, +}; + +use crate::{LicenceSource, LicenseEntry, Package}; + +pub struct NpmLicenseSource { + dir: PathBuf, +} +impl NpmLicenseSource { + pub fn new(dir: PathBuf) -> Self { + Self { dir } + } +} + +impl LicenceSource for NpmLicenseSource { + fn licenses(&self) -> Vec { + parse(&run(&self.dir)) + } + fn hash(&self) -> String { + let lock_path = self.dir.join("package-lock.json"); + lock_path.digest().unwrap_or_else(|e| { + eprintln!("Failed to hash npm package-lock.json: {e}"); + process::exit(1); + }) + } +} + +#[derive(Deserialize)] +struct NpmEntry { + licenses: Option, + repository: Option, + #[serde(rename = "licenseFile")] + license_file: Option, + publisher: Option, +} + +pub fn run(dir: &std::path::Path) -> String { + let mut cmd = Command::new("npx"); + cmd.args(["license-checker-rseidelsohn", "--json"]); + cmd.current_dir(dir); + + let output = cmd.output().unwrap_or_else(|e| { + eprintln!("Failed to run npx license-checker-rseidelsohn: {e}"); + process::exit(1); + }); + + if !output.status.success() { + eprintln!("npx license-checker-rseidelsohn failed:\n{}", String::from_utf8_lossy(&output.stderr)); + process::exit(1); + } + + String::from_utf8(output.stdout).expect("Invalid UTF-8 from license-checker") +} + +pub fn parse(json_str: &str) -> Vec { + let entries: BTreeMap = serde_json::from_str(json_str).unwrap_or_else(|e| { + eprintln!("Failed to parse license-checker JSON: {e}"); + process::exit(1); + }); + + entries + .iter() + .map(|(name, entry)| { + let license_text = entry + .license_file + .as_ref() + .and_then(|p| fs::read_to_string(p).ok()) + .map(|s| s.trim().to_string()) + .unwrap_or_else(|| entry.licenses.clone().unwrap_or_default()); + + let pkg = Package { + name: name.to_string(), + url: entry.repository.clone(), + authors: entry.publisher.as_ref().map(|p| vec![p.clone()]).unwrap_or_default(), + }; + + LicenseEntry { + name: None, + text: license_text, + packages: vec![pkg], + } + }) + .collect() +} From b0ef117e9d82d1ae5934f333b67274a9d722e89a Mon Sep 17 00:00:00 2001 From: Timon Date: Sun, 22 Feb 2026 23:06:56 +0000 Subject: [PATCH 02/12] Add third-party-licenses to Nix build and use cleaner more flake centric structure --- .nix/deps/cef.nix | 4 +- .nix/deps/rust-gpu.nix | 2 +- .nix/dev.nix | 60 +++++- .nix/flake.lock | 34 --- .nix/flake.nix | 197 ++++++------------ .nix/pkgs/graphite-branding.nix | 20 ++ .nix/pkgs/graphite-bundle.nix | 13 +- .nix/pkgs/graphite-flatpak-manifest.nix | 6 +- ....nix => graphite-raster-nodes-shaders.nix} | 12 +- .nix/pkgs/graphite.nix | 122 ++++++----- .nix/pkgs/tools/third-party-licenses.nix | 29 +++ 11 files changed, 253 insertions(+), 246 deletions(-) create mode 100644 .nix/pkgs/graphite-branding.nix rename .nix/pkgs/{raster-nodes-shaders.nix => graphite-raster-nodes-shaders.nix} (78%) create mode 100644 .nix/pkgs/tools/third-party-licenses.nix diff --git a/.nix/deps/cef.nix b/.nix/deps/cef.nix index 04b605600a..c0ce387cbf 100644 --- a/.nix/deps/cef.nix +++ b/.nix/deps/cef.nix @@ -1,4 +1,4 @@ -{ pkgs, inputs, ... }: +{ pkgs, ... }: let cefPath = pkgs.cef-binary.overrideAttrs (finalAttrs: { @@ -10,6 +10,8 @@ let mv ./Resources/* $out/ mv ./include $out/ + cat ./CREDITS.html | ${pkgs.xz}/bin/xz -9 -e -c > $out/CREDITS.html.xz + echo '${ builtins.toJSON { type = "minimal"; diff --git a/.nix/deps/rust-gpu.nix b/.nix/deps/rust-gpu.nix index 2970d515a2..a44f70b368 100644 --- a/.nix/deps/rust-gpu.nix +++ b/.nix/deps/rust-gpu.nix @@ -1,4 +1,4 @@ -{ pkgs, inputs, ... }: +{ pkgs, ... }: let extensions = [ diff --git a/.nix/dev.nix b/.nix/dev.nix index d80d7793e6..e9333d4940 100644 --- a/.nix/dev.nix +++ b/.nix/dev.nix @@ -1,16 +1,58 @@ -{ - pkgs, - deps, - libs, - tools, - ... -}: +{ pkgs, deps, ... }: +let + libs = [ + pkgs.wayland + pkgs.vulkan-loader + pkgs.libGL + pkgs.openssl + pkgs.libraw + + # X11 Support + pkgs.libxkbcommon + pkgs.libXcursor + pkgs.libxcb + pkgs.libX11 + ]; +in pkgs.mkShell ( { - packages = tools.all ++ libs.all; + packages = libs ++ [ + pkgs.pkg-config + + pkgs.lld + pkgs.nodejs + pkgs.nodePackages.npm + pkgs.binaryen + pkgs.wasm-bindgen-cli_0_2_100 + pkgs.wasm-pack + pkgs.cargo-about + + pkgs.rustc + pkgs.cargo + pkgs.rust-analyzer + pkgs.clippy + pkgs.rustfmt + + pkgs.git + + pkgs.cargo-watch + pkgs.cargo-nextest + pkgs.cargo-expand + + # Linker + pkgs.mold + + # Profiling tools + pkgs.gnuplot + pkgs.samply + pkgs.cargo-flamegraph + + # Plotting tools + pkgs.graphviz + ]; - LD_LIBRARY_PATH = "${pkgs.lib.makeLibraryPath libs.all}:${deps.cef.env.CEF_PATH}"; + LD_LIBRARY_PATH = "${pkgs.lib.makeLibraryPath libs}:${deps.cef.env.CEF_PATH}"; XDG_DATA_DIRS = "${pkgs.gsettings-desktop-schemas}/share/gsettings-schemas/${pkgs.gsettings-desktop-schemas.name}:${pkgs.gtk3}/share/gsettings-schemas/${pkgs.gtk3.name}:$XDG_DATA_DIRS"; shellHook = '' diff --git a/.nix/flake.lock b/.nix/flake.lock index 03cb965eff..7bb7e2f254 100644 --- a/.nix/flake.lock +++ b/.nix/flake.lock @@ -29,24 +29,6 @@ "url": "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz" } }, - "flake-utils": { - "inputs": { - "systems": "systems" - }, - "locked": { - "lastModified": 1731533236, - "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, "nixpkgs": { "locked": { "lastModified": 1770197578, @@ -67,7 +49,6 @@ "inputs": { "crane": "crane", "flake-compat": "flake-compat", - "flake-utils": "flake-utils", "nixpkgs": "nixpkgs", "rust-overlay": "rust-overlay" } @@ -91,21 +72,6 @@ "repo": "rust-overlay", "type": "github" } - }, - "systems": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } } }, "root": "root", diff --git a/.nix/flake.nix b/.nix/flake.nix index 5a1f419347..270bb870b2 100644 --- a/.nix/flake.nix +++ b/.nix/flake.nix @@ -18,7 +18,6 @@ url = "github:oxalica/rust-overlay"; inputs.nixpkgs.follows = "nixpkgs"; }; - flake-utils.url = "github:numtide/flake-utils"; crane.url = "github:ipetkov/crane"; # This is used to provide a identical development shell at `shell.nix` for users that do not use flakes @@ -27,143 +26,85 @@ outputs = inputs: - inputs.flake-utils.lib.eachDefaultSystem ( - system: + ( let - info = { - pname = "graphite"; - version = "unstable"; - src = pkgs.lib.cleanSourceWith { - src = ./..; - filter = path: type: !(type == "directory" && builtins.baseNameOf path == ".nix"); - }; - }; - - pkgs = import inputs.nixpkgs { - inherit system; - overlays = [ (import inputs.rust-overlay) ]; - }; - - deps = { - crane = import ./deps/crane.nix { inherit pkgs inputs; }; - cef = import ./deps/cef.nix { inherit pkgs inputs; }; - rustGPU = import ./deps/rust-gpu.nix { inherit pkgs inputs; }; - }; - - libs = rec { - desktop = [ - pkgs.wayland - pkgs.openssl - pkgs.vulkan-loader - pkgs.libraw - pkgs.libGL - ]; - desktop-x11 = [ - pkgs.libxkbcommon - pkgs.xorg.libXcursor - pkgs.xorg.libxcb - pkgs.xorg.libX11 - ]; - desktop-all = desktop ++ desktop-x11; - all = desktop-all; - }; - - tools = rec { - desktop = [ - pkgs.pkg-config - ]; - frontend = [ - pkgs.lld - pkgs.nodejs - pkgs.nodePackages.npm - pkgs.binaryen - pkgs.wasm-bindgen-cli_0_2_100 - pkgs.wasm-pack - pkgs.cargo-about - ]; - dev = [ - pkgs.rustc - pkgs.cargo - pkgs.rust-analyzer - pkgs.clippy - pkgs.rustfmt - - pkgs.git + systems = [ + "x86_64-linux" + "aarch64-linux" + ]; + forAllSystems = f: inputs.nixpkgs.lib.genAttrs systems (system: f system); + args = + system: + ( + let + lib = inputs.nixpkgs.lib // { + call = p: import p args; + }; - pkgs.cargo-watch - pkgs.cargo-nextest - pkgs.cargo-expand + pkgs = import inputs.nixpkgs { + inherit system; + overlays = [ (import inputs.rust-overlay) ]; + }; - # Linker - pkgs.mold + info = { + pname = "graphite"; + version = "unstable"; + src = inputs.nixpkgs.lib.cleanSourceWith { + src = ./..; + filter = path: type: !(type == "directory" && builtins.baseNameOf path == ".nix"); + }; + cargoVendored = deps.crane.lib.vendorCargoDeps { inherit (info) src; }; + }; - # Profiling tools - pkgs.gnuplot - pkgs.samply - pkgs.cargo-flamegraph + deps = { + crane = lib.call ./deps/crane.nix; + cef = lib.call ./deps/cef.nix; + rustGPU = lib.call ./deps/rust-gpu.nix; + }; - # Plotting tools - pkgs.graphviz - ]; - all = desktop ++ frontend ++ dev; - }; + args = { + inherit system; + inherit (inputs) self; + inherit inputs; + inherit pkgs; + inherit lib; + inherit info; + inherit deps; + } + // inputs; + in + args + ); + withArgs = f: forAllSystems (system: f (args system)); in { - packages = rec { - graphiteWithArgs = - args: - (import ./pkgs/graphite.nix { - pkgs = pkgs // { - inherit raster-nodes-shaders; - }; - inherit - info - inputs - deps - libs - tools - ; - }) - args; - graphite = graphiteWithArgs { }; - graphite-dev = graphiteWithArgs { dev = true; }; - graphite-without-resources = graphiteWithArgs { embeddedResources = false; }; - graphite-without-resources-dev = graphiteWithArgs { - embeddedResources = false; - dev = true; - }; - graphite-bundle = import ./pkgs/graphite-bundle.nix { - inherit pkgs graphite; - }; - graphite-flatpak-manifest = import ./pkgs/graphite-flatpak-manifest.nix { - inherit pkgs; - archive = graphite-bundle.tar; - }; - #TODO: graphene-cli = import ./pkgs/graphene-cli.nix { inherit info pkgs inputs deps libs tools; }; - raster-nodes-shaders = import ./pkgs/raster-nodes-shaders.nix { - inherit - info - pkgs - inputs - deps - libs - tools - ; - }; + packages = withArgs ( + { lib, ... }: + rec { + default = graphite; + graphite = (lib.call ./pkgs/graphite.nix) { }; + graphite-dev = (lib.call ./pkgs/graphite.nix) { dev = true; }; + graphite-raster-nodes-shaders = lib.call ./pkgs/graphite-raster-nodes-shaders.nix; + graphite-branding = lib.call ./pkgs/graphite-branding.nix; + graphite-bundle = lib.call ./pkgs/graphite-bundle.nix; + graphite-flatpak-manifest = lib.call ./pkgs/graphite-flatpak-manifest.nix; + + # TODO: graphene-cli = lib.call ./pkgs/graphene-cli.nix; - default = graphite; - }; + tools = { + third-party-licenses = lib.call ./pkgs/tools/third-party-licenses.nix; + }; + } + ); - devShells.default = import ./dev.nix { - inherit - pkgs - deps - libs - tools - ; - }; + devShells = withArgs ( + { lib, ... }: + { + default = lib.call ./dev.nix; + } + ); - formatter = pkgs.nixfmt-tree; + formatter = withArgs ({ pkgs, ... }: pkgs.nixfmt-tree); } ); } diff --git a/.nix/pkgs/graphite-branding.nix b/.nix/pkgs/graphite-branding.nix new file mode 100644 index 0000000000..85b53791f8 --- /dev/null +++ b/.nix/pkgs/graphite-branding.nix @@ -0,0 +1,20 @@ +{ info, pkgs, ... }: + +let + brandingTar = pkgs.fetchurl ( + let + lockContent = builtins.readFile "${info.src}/.branding"; + lines = builtins.filter (s: s != [ ]) (builtins.split "\n" lockContent); + url = builtins.elemAt lines 0; + hash = builtins.elemAt lines 1; + in + { + url = url; + sha256 = hash; + } + ); +in +pkgs.runCommand "${info.pname}-branding" { } '' + mkdir -p $out + tar -xvf ${brandingTar} -C $out --strip-components 1 +'' diff --git a/.nix/pkgs/graphite-bundle.nix b/.nix/pkgs/graphite-bundle.nix index 815fd1e5e9..d50b00fccd 100644 --- a/.nix/pkgs/graphite-bundle.nix +++ b/.nix/pkgs/graphite-bundle.nix @@ -1,18 +1,19 @@ { pkgs, - graphite, + self, + system, + ... }: let bundle = { - pkgs, - graphite, archive ? false, compression ? null, - passthru ? {}, + passthru ? { }, }: ( let + graphite = self.packages.${system}.graphite; tar = if compression == null then archive else true; nameArchiveSuffix = if tar then ".tar" else ""; nameCompressionSuffix = if compression == null then "" else "." + compression; @@ -75,18 +76,14 @@ let ); in bundle { - inherit pkgs graphite; passthru = { tar = bundle { - inherit pkgs graphite; archive = true; passthru = { gz = bundle { - inherit pkgs graphite; compression = "gz"; }; xz = bundle { - inherit pkgs graphite; compression = "xz"; }; }; diff --git a/.nix/pkgs/graphite-flatpak-manifest.nix b/.nix/pkgs/graphite-flatpak-manifest.nix index 03d7146f74..fee6603119 100644 --- a/.nix/pkgs/graphite-flatpak-manifest.nix +++ b/.nix/pkgs/graphite-flatpak-manifest.nix @@ -1,6 +1,8 @@ { pkgs, - archive, + self, + system, + ... }: (pkgs.formats.json { }).generate "art.graphite.Graphite.json" { @@ -28,7 +30,7 @@ sources = [ { type = "archive"; - path = archive; + path = self.packages.${system}.graphite-bundle.tar; strip-components = 0; } ]; diff --git a/.nix/pkgs/raster-nodes-shaders.nix b/.nix/pkgs/graphite-raster-nodes-shaders.nix similarity index 78% rename from .nix/pkgs/raster-nodes-shaders.nix rename to .nix/pkgs/graphite-raster-nodes-shaders.nix index 46e2f90a29..727e8c9555 100644 --- a/.nix/pkgs/raster-nodes-shaders.nix +++ b/.nix/pkgs/graphite-raster-nodes-shaders.nix @@ -1,12 +1,4 @@ -{ - info, - pkgs, - inputs, - deps, - libs, - tools, - ... -}: +{ info, deps, ... }: (deps.crane.lib.overrideToolchain (_: deps.rustGPU.toolchain)).buildPackage { pname = "raster-nodes-shaders"; @@ -16,7 +8,7 @@ inherit (deps.crane.lib.findCargoFiles (deps.crane.lib.cleanCargoSource info.src)) cargoConfigs; cargoLockList = [ "${info.src}/Cargo.lock" - "${deps.rustGPU.toolchain.passthru.availableComponents.rust-src}/lib/rustlib/src/rust/library/Cargo.lock" + "${deps.rustGPU.toolchain.availableComponents.rust-src}/lib/rustlib/src/rust/library/Cargo.lock" ]; }; diff --git a/.nix/pkgs/graphite.nix b/.nix/pkgs/graphite.nix index 6ffb014496..bd757add80 100644 --- a/.nix/pkgs/graphite.nix +++ b/.nix/pkgs/graphite.nix @@ -1,42 +1,36 @@ { info, pkgs, - inputs, + self, deps, - libs, - tools, + system, + lib, ... }: { - embeddedResources ? true, dev ? false, }: let - brandingTar = pkgs.fetchurl ( - let - lockContent = builtins.readFile "${info.src}/.branding"; - lines = builtins.filter (s: s != [ ]) (builtins.split "\n" lockContent); - url = builtins.elemAt lines 0; - hash = builtins.elemAt lines 1; - in - { - url = url; - sha256 = hash; - } - ); - branding = pkgs.runCommand "${info.pname}-branding" { } '' - mkdir -p $out - tar -xvf ${brandingTar} -C $out --strip-components 1 - ''; - cargoVendorDir = deps.crane.lib.vendorCargoDeps { inherit (info) src; }; + branding = self.packages.${system}.graphite-branding; + cargoVendorDir = deps.crane.lib.vendorCargoDeps { inherit (info) src; }; resourcesCommon = { pname = "${info.pname}-resources"; inherit (info) version src; inherit cargoVendorDir; strictDeps = true; - nativeBuildInputs = tools.frontend; + nativeBuildInputs = [ + pkgs.pkg-config + pkgs.lld + pkgs.nodejs + pkgs.nodePackages.npm + pkgs.binaryen + pkgs.wasm-bindgen-cli_0_2_100 + pkgs.wasm-pack + pkgs.cargo-about + ]; + buildInputs = [ pkgs.openssl ]; env.CARGO_PROFILE = if dev then "dev" else "release"; cargoExtraArgs = "--target wasm32-unknown-unknown -p graphite-wasm --no-default-features --features native"; doCheck = false; @@ -54,7 +48,11 @@ let npmConfigScript = "setup"; makeCacheWritable = true; - nativeBuildInputs = tools.frontend ++ [ pkgs.importNpmLock.npmConfigHook pkgs.removeReferencesTo ]; + nativeBuildInputs = [ + pkgs.importNpmLock.npmConfigHook + pkgs.removeReferencesTo + ] + ++ resourcesCommon.nativeBuildInputs; prePatch = '' mkdir branding @@ -80,18 +78,33 @@ let ''; } ); + libs = [ + pkgs.wayland + pkgs.vulkan-loader + pkgs.libGL + pkgs.openssl + pkgs.libraw + + # X11 Support + pkgs.libxkbcommon + pkgs.libXcursor + pkgs.libxcb + pkgs.libX11 + ]; common = { inherit (info) pname version src; inherit cargoVendorDir; strictDeps = true; - buildInputs = libs.desktop-all; - nativeBuildInputs = tools.desktop ++ [ pkgs.makeWrapper pkgs.removeReferencesTo ]; + buildInputs = libs; + nativeBuildInputs = [ + pkgs.pkg-config + pkgs.cargo-about + pkgs.removeReferencesTo + ]; env = deps.cef.env // { CARGO_PROFILE = if dev then "dev" else "release"; }; - cargoExtraArgs = "-p graphite-desktop${ - if embeddedResources then "" else " --no-default-features --features recommended" - }"; + cargoExtraArgs = "-p graphite-desktop"; doCheck = false; }; in @@ -101,31 +114,34 @@ deps.crane.lib.buildPackage ( // { cargoArtifacts = deps.crane.lib.buildDepsOnly common; - env = - common.env - // { - RASTER_NODES_SHADER_PATH = pkgs.raster-nodes-shaders; - } - // ( - if embeddedResources then - { - EMBEDDED_RESOURCES = resources; - } - else - { } - ) // { - GRAPHITE_GIT_COMMIT_HASH = inputs.self.rev or "unknown"; - GRAPHITE_GIT_COMMIT_DATE = inputs.self.lastModified or "unknown"; - }; - - postUnpack = '' - mkdir ./branding - cp -r ${branding}/* ./branding - ''; + env = common.env // { + RASTER_NODES_SHADER_PATH = self.packages.${system}.graphite-raster-nodes-shaders; + EMBEDDED_RESOURCES = resources; + GRAPHITE_GIT_COMMIT_HASH = self.rev or "unknown"; + GRAPHITE_GIT_COMMIT_DATE = self.lastModified or "unknown"; + }; - preBuild = if inputs.self ? rev then '' - export GRAPHITE_GIT_COMMIT_DATE="$(date -u -d "@$GRAPHITE_GIT_COMMIT_DATE" +"%Y-%m-%dT%H:%M:%SZ")" - '' else ""; + npmDeps = pkgs.importNpmLock { + npmRoot = "${info.src}/frontend"; + }; + npmRoot = "frontend"; + nativeBuildInputs = [ + pkgs.importNpmLock.npmConfigHook + pkgs.nodePackages.npm + ] + ++ common.nativeBuildInputs; + + preBuild = '' + ${lib.getExe self.packages.${system}.tools.third-party-licenses} + '' + + ( + if self ? rev then + '' + export GRAPHITE_GIT_COMMIT_DATE="$(date -u -d "@$GRAPHITE_GIT_COMMIT_DATE" +"%Y-%m-%dT%H:%M:%SZ")" + '' + else + "" + ); installPhase = '' mkdir -p $out/bin @@ -148,7 +164,7 @@ deps.crane.lib.buildPackage ( remove-references-to -t "${cargoVendorDir}" $out/bin/graphite patchelf \ - --set-rpath "${pkgs.lib.makeLibraryPath libs.desktop-all}:${deps.cef.env.CEF_PATH}" \ + --set-rpath "${pkgs.lib.makeLibraryPath libs}:${deps.cef.env.CEF_PATH}" \ --add-needed libGL.so \ $out/bin/graphite ''; diff --git a/.nix/pkgs/tools/third-party-licenses.nix b/.nix/pkgs/tools/third-party-licenses.nix new file mode 100644 index 0000000000..5671c7bc8a --- /dev/null +++ b/.nix/pkgs/tools/third-party-licenses.nix @@ -0,0 +1,29 @@ +{ + info, + deps, + pkgs, + ... +}: + +let + cargoVendorDir = deps.crane.lib.vendorCargoDeps { inherit (info) src; }; + common = { + pname = "third-party-licenses"; + inherit (info) version src; + inherit cargoVendorDir; + nativeBuildInputs = [ pkgs.pkg-config ]; + buildInputs = [ pkgs.openssl ]; + strictDeps = true; + env = deps.cef.env // { + CARGO_PROFILE = "dev"; + }; + cargoExtraArgs = "-p third-party-licenses"; + doCheck = false; + }; +in +deps.crane.lib.buildPackage common +// { + inherit cargoVendorDir; + cargoArtifacts = deps.crane.lib.buildDepsOnly common; + meta.mainProgram = "third-party-licenses"; +} From 74122f94d0645f3ed43f8fb6d1d7d722d6ed6115 Mon Sep 17 00:00:00 2001 From: Timon Date: Mon, 23 Feb 2026 16:28:01 +0100 Subject: [PATCH 03/12] Fix windows build --- .github/workflows/build-win-bundle.yml | 1 + tools/third-party-licenses/src/npm.rs | 3 +++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/build-win-bundle.yml b/.github/workflows/build-win-bundle.yml index 2ba73fb5d0..33fff4f7e3 100644 --- a/.github/workflows/build-win-bundle.yml +++ b/.github/workflows/build-win-bundle.yml @@ -70,6 +70,7 @@ jobs: cargo binstall --no-confirm --force "wasm-bindgen-cli@$env:WASM_BINDGEN_CLI_VERSION" - name: Build Windows Bundle + shell: bash # cargo about refuses to run in powershell env: CARGO_TERM_COLOR: always run: npm run build-desktop diff --git a/tools/third-party-licenses/src/npm.rs b/tools/third-party-licenses/src/npm.rs index c03bc130f5..00b0725003 100644 --- a/tools/third-party-licenses/src/npm.rs +++ b/tools/third-party-licenses/src/npm.rs @@ -41,7 +41,10 @@ struct NpmEntry { } pub fn run(dir: &std::path::Path) -> String { + #[cfg(not(target_os = "windows"))] let mut cmd = Command::new("npx"); + #[cfg(target_os = "windows")] + let mut cmd = Command::new("npx.cmd"); cmd.args(["license-checker-rseidelsohn", "--json"]); cmd.current_dir(dir); From c8b55a750540a0cdc8effd5bb29cef6765decd5f Mon Sep 17 00:00:00 2001 From: Timon Date: Wed, 25 Feb 2026 11:03:31 +0000 Subject: [PATCH 04/12] Fixup --- frontend/vite.config.ts | 34 +++++++++---------- node-graph/graph-craft/Cargo.toml | 1 + node-graph/interpreted-executor/Cargo.toml | 2 +- node-graph/libraries/wgpu-executor/Cargo.toml | 1 + node-graph/nodes/graphic/Cargo.toml | 1 + node-graph/preprocessor/Cargo.toml | 1 + tools/third-party-licenses/src/main.rs | 15 +++----- tools/third-party-licenses/src/npm.rs | 19 ++++++----- .../volunteer/guide/project-setup/_index.md | 13 +++---- 9 files changed, 41 insertions(+), 46 deletions(-) diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index e46b95430c..f6e8032ef9 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -32,24 +32,24 @@ export default defineConfig(({ mode }) => { function plugins(mode: string): PluginOption[] { const plugins = [ svelte({ - preprocess: [sveltePreprocess()], - onwarn(warning, defaultHandler) { - const suppressed = [ - "css-unused-selector", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json` - "vite-plugin-svelte-css-no-scopable-elements", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json` - "a11y-no-static-element-interactions", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json` - "a11y-no-noninteractive-element-interactions", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json` - "a11y-click-events-have-key-events", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json` - "a11y_consider_explicit_label", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json` - "a11y_click_events_have_key_events", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json` - "a11y_no_noninteractive_element_interactions", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json` - "a11y_no_static_element_interactions", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json` - ]; - if (suppressed.includes(warning.code)) return; + preprocess: [sveltePreprocess()], + onwarn(warning, defaultHandler) { + const suppressed = [ + "css-unused-selector", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json` + "vite-plugin-svelte-css-no-scopable-elements", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json` + "a11y-no-static-element-interactions", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json` + "a11y-no-noninteractive-element-interactions", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json` + "a11y-click-events-have-key-events", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json` + "a11y_consider_explicit_label", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json` + "a11y_click_events_have_key_events", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json` + "a11y_no_noninteractive_element_interactions", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json` + "a11y_no_static_element_interactions", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json` + ]; + if (suppressed.includes(warning.code)) return; - defaultHandler?.(warning); - }, - }), + defaultHandler?.(warning); + }, + }), viteMultipleAssets( // Additional static asset directories besides `public/` [ diff --git a/node-graph/graph-craft/Cargo.toml b/node-graph/graph-craft/Cargo.toml index a31eea4256..9906a2afa5 100644 --- a/node-graph/graph-craft/Cargo.toml +++ b/node-graph/graph-craft/Cargo.toml @@ -3,6 +3,7 @@ name = "graph-craft" version = "0.1.0" edition = "2024" license = "MIT OR Apache-2.0" +authors.workspace = true [features] default = ["dealloc_nodes", "wgpu"] diff --git a/node-graph/interpreted-executor/Cargo.toml b/node-graph/interpreted-executor/Cargo.toml index 4100c67ef4..05a9f9c8e9 100644 --- a/node-graph/interpreted-executor/Cargo.toml +++ b/node-graph/interpreted-executor/Cargo.toml @@ -3,6 +3,7 @@ name = "interpreted-executor" version = "0.1.0" edition = "2024" license = "MIT OR Apache-2.0" +authors.workspace = true [features] default = [] @@ -56,4 +57,3 @@ harness = false [[bench]] name = "run_cached_iai" harness = false - diff --git a/node-graph/libraries/wgpu-executor/Cargo.toml b/node-graph/libraries/wgpu-executor/Cargo.toml index 046ff6ad99..23ae4fd5d3 100644 --- a/node-graph/libraries/wgpu-executor/Cargo.toml +++ b/node-graph/libraries/wgpu-executor/Cargo.toml @@ -3,6 +3,7 @@ name = "wgpu-executor" version = "0.1.0" edition = "2024" license = "MIT OR Apache-2.0" +authors.workspace = true [dependencies] # Local dependencies diff --git a/node-graph/nodes/graphic/Cargo.toml b/node-graph/nodes/graphic/Cargo.toml index 30077176f8..c67322948e 100644 --- a/node-graph/nodes/graphic/Cargo.toml +++ b/node-graph/nodes/graphic/Cargo.toml @@ -3,6 +3,7 @@ name = "graphic-nodes" version = "0.1.0" edition = "2024" license = "MIT OR Apache-2.0" +authors.workspace = true [dependencies] # Local dependencies diff --git a/node-graph/preprocessor/Cargo.toml b/node-graph/preprocessor/Cargo.toml index 08d03c8524..6ec3c3fe99 100644 --- a/node-graph/preprocessor/Cargo.toml +++ b/node-graph/preprocessor/Cargo.toml @@ -3,6 +3,7 @@ name = "preprocessor" version = "0.1.0" edition = "2024" license = "MIT OR Apache-2.0" +authors.workspace = true [features] diff --git a/tools/third-party-licenses/src/main.rs b/tools/third-party-licenses/src/main.rs index edf4a062a2..2ea8b202f3 100644 --- a/tools/third-party-licenses/src/main.rs +++ b/tools/third-party-licenses/src/main.rs @@ -126,9 +126,9 @@ fn format_credits(licenses: &Vec) -> String { .packages .iter() .map(|pkg| match &pkg { - Package { name, authors, url: Some(url) } if !authors.is_empty() => format!("{} [{}] - {}", name, authors.join(", "), url), + Package { name, authors, url: Some(url) } if !authors.is_empty() => format!("{} - [{}] - {}", name, authors.join(", "), url), Package { name, authors: _, url: Some(url) } => format!("{} - {}", name, url), - Package { name, authors, url: None } if !authors.is_empty() => format!("{} [{}]", name, authors.join(", ")), + Package { name, authors, url: None } if !authors.is_empty() => format!("{} - [{}]", name, authors.join(", ")), _ => pkg.name.clone(), }) .collect(); @@ -136,15 +136,10 @@ fn format_credits(licenses: &Vec) -> String { let multi = package_lines.len() > 1; let header = format!( - "The package{} listed here {} licensed under the terms of the {} printed beneath ({} lines)", + "The package{} listed here {} licensed under the terms of the {} printed beneath", if multi { "s" } else { "" }, if multi { "are" } else { "is" }, - if let Some(license) = license.name.as_ref() { - format!("\"{}\" license", license) - } else { - "license".to_string() - }, - license.text.lines().count() + if let Some(license) = license.name.as_ref() { license.to_string() } else { "license".to_string() } ); let max_len = std::iter::once(header.len()).chain(package_lines.iter().map(|l| l.chars().count())).max().unwrap_or(0); @@ -173,9 +168,9 @@ fn format_credits(licenses: &Vec) -> String { out.push_str(" "); out.push_str(line); } + out.push('\n'); } - out.push('\n'); out } diff --git a/tools/third-party-licenses/src/npm.rs b/tools/third-party-licenses/src/npm.rs index 00b0725003..184bb6abf1 100644 --- a/tools/third-party-licenses/src/npm.rs +++ b/tools/third-party-licenses/src/npm.rs @@ -38,6 +38,7 @@ struct NpmEntry { #[serde(rename = "licenseFile")] license_file: Option, publisher: Option, + email: Option, } pub fn run(dir: &std::path::Path) -> String { @@ -45,7 +46,7 @@ pub fn run(dir: &std::path::Path) -> String { let mut cmd = Command::new("npx"); #[cfg(target_os = "windows")] let mut cmd = Command::new("npx.cmd"); - cmd.args(["license-checker-rseidelsohn", "--json"]); + cmd.args(["license-checker-rseidelsohn", "--production", "--json"]); cmd.current_dir(dir); let output = cmd.output().unwrap_or_else(|e| { @@ -70,21 +71,21 @@ pub fn parse(json_str: &str) -> Vec { entries .iter() .map(|(name, entry)| { - let license_text = entry - .license_file - .as_ref() - .and_then(|p| fs::read_to_string(p).ok()) - .map(|s| s.trim().to_string()) - .unwrap_or_else(|| entry.licenses.clone().unwrap_or_default()); + let publisher_info = entry.publisher.as_ref().map(|p| { + let email_part = entry.email.as_ref().map(|e| format!(" <{}>", e)).unwrap_or_default(); + format!("{}{}", p, email_part) + }); let pkg = Package { name: name.to_string(), url: entry.repository.clone(), - authors: entry.publisher.as_ref().map(|p| vec![p.clone()]).unwrap_or_default(), + authors: publisher_info.into_iter().collect(), }; + let license_text = entry.license_file.as_ref().and_then(|p| fs::read_to_string(p).ok()).map(|s| s.to_string()).unwrap_or_default(); + LicenseEntry { - name: None, + name: entry.licenses.clone(), text: license_text, packages: vec![pkg], } diff --git a/website/content/volunteer/guide/project-setup/_index.md b/website/content/volunteer/guide/project-setup/_index.md index 17c47708e8..3934df3a20 100644 --- a/website/content/volunteer/guide/project-setup/_index.md +++ b/website/content/volunteer/guide/project-setup/_index.md @@ -19,9 +19,10 @@ Graphite is built with Rust and web technologies, which means you will need to i Next, install the dependencies required for development builds: ```sh -cargo install cargo-watch -cargo install wasm-pack cargo install -f wasm-bindgen-cli@0.2.100 +cargo install wasm-pack +cargo install cargo-watch +cargo install cargo-about ``` Regarding the last one: you'll likely get faster build times if you manually install that specific version of `wasm-bindgen-cli`. It is supposed to be installed automatically but a version mismatch causes it to reinstall every single recompilation. It may need to be manually updated periodically to match the version of the `wasm-bindgen` dependency in [`Cargo.toml`](https://github.com/GraphiteEditor/Graphite/blob/master/Cargo.toml). @@ -66,13 +67,7 @@ npm run production
Production build instructions: click here -You'll rarely need to compile your own production builds because our CI/CD system takes care of deployments. However, you can compile a production build with full optimizations by first installing the additional `cargo-about` dev dependency: - -```sh -cargo install cargo-about -``` - -And then running: +You'll rarely need to compile your own production builds because our CI/CD system takes care of deployments. However, you can compile a production build with full optimizations by running: ```sh npm run build From 31598786d3a8a3e328ea77c864ea7729d91b013f Mon Sep 17 00:00:00 2001 From: Timon Date: Wed, 25 Feb 2026 12:56:05 +0000 Subject: [PATCH 05/12] Review --- Cargo.lock | 14 ----- desktop/src/app.rs | 2 +- frontend/vite.config.ts | 1 - tools/third-party-licenses/Cargo.toml | 3 +- tools/third-party-licenses/src/cargo.rs | 25 ++++---- tools/third-party-licenses/src/cef.rs | 10 +++- tools/third-party-licenses/src/main.rs | 80 ++++++++++++------------- tools/third-party-licenses/src/npm.rs | 29 ++++----- 8 files changed, 74 insertions(+), 90 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9070c0e7d9..6b1ee1e571 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5813,19 +5813,6 @@ dependencies = [ "digest", ] -[[package]] -name = "sha256" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f880fc8562bdeb709793f00eb42a2ad0e672c4f883bbe59122b926eca935c8f6" -dependencies = [ - "async-trait", - "bytes", - "hex", - "sha2", - "tokio", -] - [[package]] name = "sharded-slab" version = "0.1.7" @@ -6367,7 +6354,6 @@ dependencies = [ "scraper", "serde", "serde_json", - "sha256", ] [[package]] diff --git a/desktop/src/app.rs b/desktop/src/app.rs index 3a13cc843e..5b8af9654e 100644 --- a/desktop/src/app.rs +++ b/desktop/src/app.rs @@ -418,7 +418,7 @@ impl App { self.exit(Some(ExitReason::Restart)); } DesktopFrontendMessage::LoadThirdPartyLicenses => { - let compressed = include_bytes!(concat!(env!("CARGO_WORKSPACE_DIR"), "/third-party-licenses.txt.xz")); + let compressed = include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/third-party-licenses.txt.xz")); let mut reader = lzma_rust2::XzReader::new(compressed.as_slice(), false); let mut string = String::new(); if let Err(e) = reader.read_to_string(&mut string) { diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index f6e8032ef9..bf49c44c86 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -13,7 +13,6 @@ const projectRootDir = path.resolve(__dirname); export default defineConfig(({ mode }) => { return { plugins: plugins(mode), - resolve: { alias: [ { find: /@branding\/(.*\.svg)/, replacement: path.resolve(projectRootDir, "../branding", "$1?raw") }, diff --git a/tools/third-party-licenses/Cargo.toml b/tools/third-party-licenses/Cargo.toml index b2ec0cf5ae..a200ef5057 100644 --- a/tools/third-party-licenses/Cargo.toml +++ b/tools/third-party-licenses/Cargo.toml @@ -9,6 +9,5 @@ authors.workspace = true serde = { workspace = true } serde_json = { workspace = true } lzma-rust2 = { workspace = true } -cef-dll-sys = { workspace = true } +cef-dll-sys = { workspace = true } # TODO: make optional to now slow down web builds scraper = "0.22" -sha256 = "1" diff --git a/tools/third-party-licenses/src/cargo.rs b/tools/third-party-licenses/src/cargo.rs index 1343e5ed90..49d6731dd4 100644 --- a/tools/third-party-licenses/src/cargo.rs +++ b/tools/third-party-licenses/src/cargo.rs @@ -1,29 +1,28 @@ use crate::{LicenceSource, LicenseEntry, Package}; use serde::Deserialize; -use sha256::TrySha256Digest; -use std::{ - path::PathBuf, - process::{self, Command}, -}; +use std::fs; +use std::hash::{Hash, Hasher}; +use std::path::PathBuf; +use std::process::{self, Command}; -pub struct CargoAboutLicenseSource {} +pub struct CargoLicenseSource {} -impl CargoAboutLicenseSource { +impl CargoLicenseSource { pub fn new() -> Self { Self {} } } -impl LicenceSource for CargoAboutLicenseSource { +impl LicenceSource for CargoLicenseSource { fn licenses(&self) -> Vec { parse(run()) } - fn hash(&self) -> String { +} + +impl Hash for CargoLicenseSource { + fn hash(&self, state: &mut H) { let lock_path = PathBuf::from(env!("CARGO_WORKSPACE_DIR")).join("Cargo.lock"); - lock_path.digest().unwrap_or_else(|e| { - eprintln!("Failed to hash Cargo.lock: {e}"); - process::exit(1); - }) + fs::read_to_string(lock_path).unwrap().hash(state) } } diff --git a/tools/third-party-licenses/src/cef.rs b/tools/third-party-licenses/src/cef.rs index 5311a5585f..672c0af3c1 100644 --- a/tools/third-party-licenses/src/cef.rs +++ b/tools/third-party-licenses/src/cef.rs @@ -1,5 +1,6 @@ use lzma_rust2::XzReader; use scraper::{Html, Selector}; +use std::hash::Hash; use std::io::Read; use std::path::PathBuf; use std::{fs, process}; @@ -19,9 +20,12 @@ impl LicenceSource for CefLicenseSource { let html = read(); parse(&html) } - fn hash(&self) -> String { - let html = read(); - sha256::digest(html) +} + +impl Hash for CefLicenseSource { + fn hash(&self, state: &mut H) { + let cef_path = PathBuf::from(env!("CEF_PATH")).join("CREDITS.html"); + fs::read_to_string(cef_path).unwrap().hash(state) } } diff --git a/tools/third-party-licenses/src/main.rs b/tools/third-party-licenses/src/main.rs index 2ea8b202f3..7f1c007994 100644 --- a/tools/third-party-licenses/src/main.rs +++ b/tools/third-party-licenses/src/main.rs @@ -1,6 +1,6 @@ use lzma_rust2::{XzOptions, XzWriter}; -use sha256::Sha256Digest; -use std::collections::BTreeMap; +use std::collections::HashMap; +use std::hash::{DefaultHasher, Hash, Hasher}; use std::io::Write; use std::path::PathBuf; use std::{fs, process}; @@ -9,13 +9,12 @@ mod cargo; mod cef; mod npm; -use crate::cargo::CargoAboutLicenseSource; +use crate::cargo::CargoLicenseSource; use crate::cef::CefLicenseSource; use crate::npm::NpmLicenseSource; -pub trait LicenceSource { +pub trait LicenceSource: std::hash::Hash { fn licenses(&self) -> Vec; - fn hash(&self) -> String; } pub struct LicenseEntry { @@ -30,17 +29,12 @@ pub struct Package { url: Option, } -struct Run { - output_hash: String, - cargo_hash: String, - npm_hash: String, - cef_hash: Option, -} - -impl Run { - fn hash(&self) -> String { - format!("{}{}{}{}", self.cargo_hash, self.npm_hash, self.cef_hash.as_deref().unwrap_or_default(), self.output_hash).digest() - } +#[derive(Hash)] +struct RunHashes<'a> { + output: &'a Vec, + cargo: &'a CargoLicenseSource, + npm: &'a NpmLicenseSource, + cef: &'a Option, } fn main() { @@ -51,41 +45,40 @@ fn main() { let output_path = if web { workspace_dir.join("frontend/third-party-licenses.txt") } else { - workspace_dir.join("third-party-licenses.txt.xz") + workspace_dir.join("desktop/third-party-licenses.txt.xz") }; let current_hash_path = manifest_dir.join(if web { "web.hash" } else { "desktop.hash" }); - let cargo_source = CargoAboutLicenseSource::new(); + let cargo_source = CargoLicenseSource::new(); let npm_source = NpmLicenseSource::new(workspace_dir.join("frontend")); let cef_source = if web { None } else { Some(CefLicenseSource::new()) }; - let mut run = Run { - cargo_hash: cargo_source.hash(), - npm_hash: npm_source.hash(), - cef_hash: cef_source.as_ref().map(|s| s.hash()), - output_hash: if output_path.exists() { - fs::read_to_string(&output_path).unwrap_or_default().digest() - } else { - Default::default() - }, + let mut run = RunHashes { + cargo: &cargo_source, + npm: &npm_source, + cef: &cef_source, + output: &fs::read(&output_path).unwrap_or_default(), }; - if run.hash() == fs::read_to_string(¤t_hash_path).unwrap_or_default() { + let mut hasher = DefaultHasher::new(); + run.hash(&mut hasher); + let current_hash = hasher.finish().to_string(); + + if current_hash == fs::read_to_string(¤t_hash_path).unwrap_or_default() { eprintln!("No changes in licenses detected, skipping generation."); return; } eprintln!("Changes in licenses detected, generating new license file."); let mut sources = vec![cargo_source.licenses(), npm_source.licenses()]; - if !web { - let cef_source = CefLicenseSource::new(); + if let Some(cef_source) = cef_source.as_ref() { sources.push(cef_source.licenses()); } let credits = merge_filter_dedup_and_sort(sources); let formatted = format_credits(&credits); - if web { + let output = if web { if let Some(parent) = output_path.parent() { fs::create_dir_all(parent).unwrap_or_else(|e| { eprintln!("Failed to create directory {}: {e}", parent.display()); @@ -96,17 +89,24 @@ fn main() { eprintln!("Failed to write {}: {e}", &output_path.display()); std::process::exit(1); }); - run.output_hash = formatted.digest(); + formatted.as_bytes().to_vec() } else { - let formatted_compressed = compress(&formatted); - fs::write(&output_path, &formatted_compressed).unwrap_or_else(|e| { + let compressed = compress(&formatted); + fs::write(&output_path, &compressed).unwrap_or_else(|e| { eprintln!("Failed to write {}: {e}", &output_path.display()); std::process::exit(1); }); - run.output_hash = formatted_compressed.digest(); - } + compressed + }; + run.output = &output; + + let hash = { + let mut hasher = DefaultHasher::new(); + run.hash(&mut hasher); + hasher.finish().to_string() + }; - fs::write(¤t_hash_path, run.hash()).unwrap_or_else(|e| { + fs::write(¤t_hash_path, hash).unwrap_or_else(|e| { eprintln!("Failed to write hash file {}: {e}", current_hash_path.display()); process::exit(1); }); @@ -193,14 +193,14 @@ fn filter(licenses: &mut Vec) { } fn dedup_by_licence_text(vec: Vec) -> Vec { - let mut map: BTreeMap = BTreeMap::new(); + let mut map: HashMap = HashMap::new(); for entry in vec { match map.entry(entry.text.clone()) { - std::collections::btree_map::Entry::Occupied(mut e) => { + std::collections::hash_map::Entry::Occupied(mut e) => { e.get_mut().packages.extend(entry.packages); } - std::collections::btree_map::Entry::Vacant(e) => { + std::collections::hash_map::Entry::Vacant(e) => { e.insert(entry); } } diff --git a/tools/third-party-licenses/src/npm.rs b/tools/third-party-licenses/src/npm.rs index 184bb6abf1..379a134c54 100644 --- a/tools/third-party-licenses/src/npm.rs +++ b/tools/third-party-licenses/src/npm.rs @@ -1,11 +1,8 @@ -use serde::Deserialize; -use sha256::TrySha256Digest; -use std::{ - collections::BTreeMap, - fs, - path::PathBuf, - process::{self, Command}, -}; +use std::collections::HashMap; +use std::fs; +use std::path::PathBuf; +use std::process; +use std::process::Command; use crate::{LicenceSource, LicenseEntry, Package}; @@ -22,16 +19,16 @@ impl LicenceSource for NpmLicenseSource { fn licenses(&self) -> Vec { parse(&run(&self.dir)) } - fn hash(&self) -> String { +} + +impl std::hash::Hash for NpmLicenseSource { + fn hash(&self, state: &mut H) { let lock_path = self.dir.join("package-lock.json"); - lock_path.digest().unwrap_or_else(|e| { - eprintln!("Failed to hash npm package-lock.json: {e}"); - process::exit(1); - }) + fs::read_to_string(lock_path).unwrap().hash(state) } } -#[derive(Deserialize)] +#[derive(serde::Deserialize)] struct NpmEntry { licenses: Option, repository: Option, @@ -63,9 +60,9 @@ pub fn run(dir: &std::path::Path) -> String { } pub fn parse(json_str: &str) -> Vec { - let entries: BTreeMap = serde_json::from_str(json_str).unwrap_or_else(|e| { + let entries: HashMap = serde_json::from_str(json_str).unwrap_or_else(|e| { eprintln!("Failed to parse license-checker JSON: {e}"); - process::exit(1); + process::exit(1) }); entries From 3636487bee973141bd40c87b892c9b6b72fc9b94 Mon Sep 17 00:00:00 2001 From: Timon Date: Wed, 25 Feb 2026 13:54:22 +0000 Subject: [PATCH 06/12] Review --- .nix/pkgs/tools/third-party-licenses.nix | 2 +- frontend/vite.config.ts | 2 +- package.json | 6 +- tools/third-party-licenses/Cargo.toml | 7 +- tools/third-party-licenses/build.rs | 14 +++- tools/third-party-licenses/src/cef.rs | 3 +- tools/third-party-licenses/src/main.rs | 87 ++++++++++++------------ 7 files changed, 67 insertions(+), 54 deletions(-) diff --git a/.nix/pkgs/tools/third-party-licenses.nix b/.nix/pkgs/tools/third-party-licenses.nix index 5671c7bc8a..4dfb24834a 100644 --- a/.nix/pkgs/tools/third-party-licenses.nix +++ b/.nix/pkgs/tools/third-party-licenses.nix @@ -17,7 +17,7 @@ let env = deps.cef.env // { CARGO_PROFILE = "dev"; }; - cargoExtraArgs = "-p third-party-licenses"; + cargoExtraArgs = "-p third-party-licenses --features desktop"; doCheck = false; }; in diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index bf49c44c86..a585981ef7 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -65,7 +65,7 @@ function plugins(mode: string): PluginOption[] { name: "third-party-licenses", buildStart() { try { - execSync("cargo run -p third-party-licenses -- --web", { + execSync("cargo run -p third-party-licenses", { stdio: "inherit", }); } catch (_e) { diff --git a/package.json b/package.json index eb78e0b812..b49c6ca203 100644 --- a/package.json +++ b/package.json @@ -4,15 +4,15 @@ "scripts": { "---------- DEV SERVER ----------": "", "start": "cd frontend && npm start", - "start-desktop": "cd frontend && npm run build-native-dev && cargo run -p third-party-licenses && cargo run -p graphite-desktop-bundle -- open", + "start-desktop": "cd frontend && npm run build-native-dev && cargo run -p third-party-licenses --features desktop && cargo run -p graphite-desktop-bundle -- open", "profiling": "cd frontend && npm run profiling", "production": "cd frontend && npm run production", "---------- BUILDS ----------": "", "build-dev": "cd frontend && npm run build-dev", "build-profiling": "cd frontend && npm run build-profiling", "build": "cd frontend && npm run build", - "build-desktop": "cd frontend && npm run build-native && cargo run -p third-party-licenses && cargo run -r -p graphite-desktop-bundle", - "build-desktop-dev": "cd frontend && npm run build-native-dev && cargo run -p third-party-licenses && cargo run -p graphite-desktop-bundle", + "build-desktop": "cd frontend && npm run build-native && cargo run -p third-party-licenses --features desktop && cargo run -r -p graphite-desktop-bundle", + "build-desktop-dev": "cd frontend && npm run build-native-dev && cargo run -p third-party-licenses --features desktop && cargo run -p graphite-desktop-bundle", "---------- UTILITIES ----------": "", "lint": "cd frontend && npm run lint", "lint-fix": "cd frontend && npm run lint-fix" diff --git a/tools/third-party-licenses/Cargo.toml b/tools/third-party-licenses/Cargo.toml index a200ef5057..23ade85f96 100644 --- a/tools/third-party-licenses/Cargo.toml +++ b/tools/third-party-licenses/Cargo.toml @@ -5,9 +5,12 @@ version.workspace = true license.workspace = true authors.workspace = true +[features] +desktop = ["dep:cef-dll-sys", "dep:scraper"] + [dependencies] serde = { workspace = true } serde_json = { workspace = true } lzma-rust2 = { workspace = true } -cef-dll-sys = { workspace = true } # TODO: make optional to now slow down web builds -scraper = "0.22" +cef-dll-sys = { workspace = true, optional = true } +scraper = { version = "0.22", optional = true } diff --git a/tools/third-party-licenses/build.rs b/tools/third-party-licenses/build.rs index 1905d7302c..08ea588e05 100644 --- a/tools/third-party-licenses/build.rs +++ b/tools/third-party-licenses/build.rs @@ -1,5 +1,15 @@ fn main() { + println!("cargo:rerun-if-changed=src"); + println!("cargo:rerun-if-env-changed=DEP_CEF_DLL_WRAPPER_CEF_DIR"); - let cef_dir = std::env::var("DEP_CEF_DLL_WRAPPER_CEF_DIR").unwrap(); - println!("cargo:rustc-env=CEF_PATH={cef_dir}"); + if let Ok(cef_dir) = std::env::var("DEP_CEF_DLL_WRAPPER_CEF_DIR") { + println!("cargo:rustc-env=CEF_PATH={cef_dir}"); + } + + let manifest_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); + if std::env::var("CARGO_FEATURE_DESKTOP").is_ok() { + let _ = std::fs::remove_file(manifest_dir.join("desktop.hash")); + } else { + let _ = std::fs::remove_file(manifest_dir.join("web.hash")); + } } diff --git a/tools/third-party-licenses/src/cef.rs b/tools/third-party-licenses/src/cef.rs index 672c0af3c1..47fbc545e7 100644 --- a/tools/third-party-licenses/src/cef.rs +++ b/tools/third-party-licenses/src/cef.rs @@ -24,8 +24,7 @@ impl LicenceSource for CefLicenseSource { impl Hash for CefLicenseSource { fn hash(&self, state: &mut H) { - let cef_path = PathBuf::from(env!("CEF_PATH")).join("CREDITS.html"); - fs::read_to_string(cef_path).unwrap().hash(state) + read().hash(state) } } diff --git a/tools/third-party-licenses/src/main.rs b/tools/third-party-licenses/src/main.rs index 7f1c007994..232b4fef0f 100644 --- a/tools/third-party-licenses/src/main.rs +++ b/tools/third-party-licenses/src/main.rs @@ -1,15 +1,15 @@ -use lzma_rust2::{XzOptions, XzWriter}; use std::collections::HashMap; use std::hash::{DefaultHasher, Hash, Hasher}; -use std::io::Write; use std::path::PathBuf; use std::{fs, process}; mod cargo; +#[cfg(feature = "desktop")] mod cef; mod npm; use crate::cargo::CargoLicenseSource; +#[cfg(feature = "desktop")] use crate::cef::CefLicenseSource; use crate::npm::NpmLicenseSource; @@ -30,39 +30,44 @@ pub struct Package { } #[derive(Hash)] -struct RunHashes<'a> { +struct Run<'a> { output: &'a Vec, cargo: &'a CargoLicenseSource, npm: &'a NpmLicenseSource, - cef: &'a Option, + #[cfg(feature = "desktop")] + cef: &'a CefLicenseSource, } fn main() { - let web = std::env::args().any(|arg| arg == "--web"); - let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let workspace_dir = PathBuf::from(env!("CARGO_WORKSPACE_DIR")); - let output_path = if web { - workspace_dir.join("frontend/third-party-licenses.txt") - } else { - workspace_dir.join("desktop/third-party-licenses.txt.xz") - }; - let current_hash_path = manifest_dir.join(if web { "web.hash" } else { "desktop.hash" }); + + #[cfg(feature = "desktop")] + let output_path = workspace_dir.join("desktop/third-party-licenses.txt.xz"); + #[cfg(not(feature = "desktop"))] + let output_path = workspace_dir.join("frontend/third-party-licenses.txt"); + + #[cfg(feature = "desktop")] + let current_hash_path = manifest_dir.join("desktop.hash"); + #[cfg(not(feature = "desktop"))] + let current_hash_path = manifest_dir.join("web.hash"); let cargo_source = CargoLicenseSource::new(); let npm_source = NpmLicenseSource::new(workspace_dir.join("frontend")); - let cef_source = if web { None } else { Some(CefLicenseSource::new()) }; + #[cfg(feature = "desktop")] + let cef_source = CefLicenseSource::new(); - let mut run = RunHashes { + let mut run = Run { cargo: &cargo_source, npm: &npm_source, + #[cfg(feature = "desktop")] cef: &cef_source, output: &fs::read(&output_path).unwrap_or_default(), }; let mut hasher = DefaultHasher::new(); run.hash(&mut hasher); - let current_hash = hasher.finish().to_string(); + let current_hash = format!("{:016x}", hasher.finish()); if current_hash == fs::read_to_string(¤t_hash_path).unwrap_or_default() { eprintln!("No changes in licenses detected, skipping generation."); @@ -70,40 +75,34 @@ fn main() { } eprintln!("Changes in licenses detected, generating new license file."); - let mut sources = vec![cargo_source.licenses(), npm_source.licenses()]; - if let Some(cef_source) = cef_source.as_ref() { - sources.push(cef_source.licenses()); - } - let credits = merge_filter_dedup_and_sort(sources); - - let formatted = format_credits(&credits); - - let output = if web { - if let Some(parent) = output_path.parent() { - fs::create_dir_all(parent).unwrap_or_else(|e| { - eprintln!("Failed to create directory {}: {e}", parent.display()); - std::process::exit(1); - }); - } - fs::write(&output_path, &formatted).unwrap_or_else(|e| { - eprintln!("Failed to write {}: {e}", &output_path.display()); - std::process::exit(1); - }); - formatted.as_bytes().to_vec() - } else { - let compressed = compress(&formatted); - fs::write(&output_path, &compressed).unwrap_or_else(|e| { - eprintln!("Failed to write {}: {e}", &output_path.display()); + let licenses = merge_filter_dedup_and_sort(vec![ + cargo_source.licenses(), + npm_source.licenses(), + #[cfg(feature = "desktop")] + cef_source.licenses(), + ]); + let formatted = format_credits(&licenses); + + #[cfg(feature = "desktop")] + let output = compress(&formatted); + #[cfg(not(feature = "desktop"))] + let output = formatted.as_bytes().to_vec(); + if let Some(parent) = output_path.parent() { + fs::create_dir_all(parent).unwrap_or_else(|e| { + eprintln!("Failed to create directory {}: {e}", parent.display()); std::process::exit(1); }); - compressed - }; + } + fs::write(&output_path, &output).unwrap_or_else(|e| { + eprintln!("Failed to write {}: {e}", &output_path.display()); + std::process::exit(1); + }); run.output = &output; let hash = { let mut hasher = DefaultHasher::new(); run.hash(&mut hasher); - hasher.finish().to_string() + format!("{:016x}", hasher.finish()) }; fs::write(¤t_hash_path, hash).unwrap_or_else(|e| { @@ -209,9 +208,11 @@ fn dedup_by_licence_text(vec: Vec) -> Vec { map.into_values().collect() } +#[cfg(feature = "desktop")] fn compress(content: &str) -> Vec { + use std::io::Write; let mut buf = Vec::new(); - let mut writer = XzWriter::new(&mut buf, XzOptions::default()).unwrap_or_else(|e| { + let mut writer = lzma_rust2::XzWriter::new(&mut buf, lzma_rust2::XzOptions::default()).unwrap_or_else(|e| { eprintln!("Failed to create XZ writer: {e}"); std::process::exit(1); }); From 7f5d57915a0b2858b1a3f2aeb809f72d94fe28e0 Mon Sep 17 00:00:00 2001 From: Timon Date: Wed, 25 Feb 2026 16:14:23 +0000 Subject: [PATCH 07/12] fix shader ci --- .github/workflows/provide-shaders.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/provide-shaders.yml b/.github/workflows/provide-shaders.yml index 25ae724691..7e38ecf303 100644 --- a/.github/workflows/provide-shaders.yml +++ b/.github/workflows/provide-shaders.yml @@ -17,7 +17,7 @@ jobs: - uses: DeterminateSystems/magic-nix-cache-action@main - name: Build graphene raster nodes shaders - run: nix build .nix#raster-nodes-shaders && cp result raster_nodes_shaders_entrypoint.wgsl + run: nix build .nix#graphite-raster-nodes-shaders && cp result raster_nodes_shaders_entrypoint.wgsl - name: Upload graphene raster nodes shaders to artifacts repository run: | From 1b265d4abb8bbc3a30734bb664af6f2e5bf42ef3 Mon Sep 17 00:00:00 2001 From: Timon Date: Wed, 25 Feb 2026 23:08:36 +0000 Subject: [PATCH 08/12] Review --- Cargo.lock | 112 ++++++++++--------------- Cargo.toml | 1 + frontend/package.json | 12 +-- tools/third-party-licenses/Cargo.toml | 5 +- tools/third-party-licenses/src/cef.rs | 72 ++++++++-------- tools/third-party-licenses/src/main.rs | 1 + tools/third-party-licenses/src/npm.rs | 60 ++++++------- 7 files changed, 123 insertions(+), 140 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6b1ee1e571..f5dfe0b51e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1245,7 +1245,7 @@ checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ "bitflags 2.9.3", "crossterm_winapi", - "derive_more 2.0.1", + "derive_more", "document-features", "mio", "parking_lot", @@ -1282,9 +1282,9 @@ dependencies = [ [[package]] name = "cssparser" -version = "0.34.0" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c66d1cd8ed61bf80b38432613a7a2f09401ab8d0501110655f8b341484a3e3" +checksum = "dae61cf9c0abb83bd659dab65b7e4e38d8236824c85f0f804f173567bda257d2" dependencies = [ "cssparser-macros", "dtoa-short", @@ -1359,17 +1359,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "derive_more" -version = "0.99.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - [[package]] name = "derive_more" version = "2.0.1" @@ -2089,15 +2078,6 @@ dependencies = [ "slab", ] -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -2687,14 +2667,12 @@ checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" [[package]] name = "html5ever" -version = "0.29.1" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c" +checksum = "6452c4751a24e1b99c3260d505eaeee76a050573e61f30ac2c924ddc7236f01e" dependencies = [ "log", - "mac", "markup5ever", - "match_token", ] [[package]] @@ -2825,7 +2803,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b1e4910d3a9137442723dfb772c32dc10674c4181ca078d2fd227cd5dce9db0" dependencies = [ "bincode", - "derive_more 2.0.1", + "derive_more", "iai-callgrind-macros", "iai-callgrind-runner", ] @@ -2836,7 +2814,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d03775318d3f9f01b39ac6612b01464006dc397a654a89dd57df2fd34fb68c3" dependencies = [ - "derive_more 2.0.1", + "derive_more", "proc-macro-error2", "proc-macro2", "quote", @@ -3524,27 +3502,13 @@ dependencies = [ [[package]] name = "markup5ever" -version = "0.14.1" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18" +checksum = "6c3294c4d74d0742910f8c7b466f44dda9eb2d5742c1e430138df290a1e8451c" dependencies = [ "log", - "phf", - "phf_codegen", - "string_cache", - "string_cache_codegen", "tendril", -] - -[[package]] -name = "match_token" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", + "web_atoms", ] [[package]] @@ -4528,19 +4492,20 @@ dependencies = [ [[package]] name = "phf" -version = "0.11.3" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" dependencies = [ "phf_macros", "phf_shared", + "serde", ] [[package]] name = "phf_codegen" -version = "0.11.3" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" dependencies = [ "phf_generator", "phf_shared", @@ -4548,19 +4513,19 @@ dependencies = [ [[package]] name = "phf_generator" -version = "0.11.3" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" dependencies = [ + "fastrand", "phf_shared", - "rand 0.8.5", ] [[package]] name = "phf_macros" -version = "0.11.3" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" dependencies = [ "phf_generator", "phf_shared", @@ -4571,9 +4536,9 @@ dependencies = [ [[package]] name = "phf_shared" -version = "0.11.3" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" dependencies = [ "siphasher", ] @@ -5614,9 +5579,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "scraper" -version = "0.22.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc3d051b884f40e309de6c149734eab57aa8cc1347992710dc80bcc1c2194c15" +checksum = "93cecd86d6259499c844440546d02f55f3e17bd286e529e48d1f9f67e92315cb" dependencies = [ "cssparser", "ego-tree", @@ -5665,19 +5630,19 @@ dependencies = [ [[package]] name = "selectors" -version = "0.26.0" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd568a4c9bb598e291a08244a5c1f5a8a6650bee243b5b0f8dbb3d9cc1d87fe8" +checksum = "feef350c36147532e1b79ea5c1f3791373e61cbd9a6a2615413b3807bb164fb7" dependencies = [ "bitflags 2.9.3", "cssparser", - "derive_more 0.99.20", - "fxhash", + "derive_more", "log", "new_debug_unreachable", "phf", "phf_codegen", "precomputed-hash", + "rustc-hash 2.1.1", "servo_arc", "smallvec", ] @@ -6115,22 +6080,21 @@ dependencies = [ [[package]] name = "string_cache" -version = "0.8.9" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901" dependencies = [ "new_debug_unreachable", "parking_lot", "phf_shared", "precomputed-hash", - "serde", ] [[package]] name = "string_cache_codegen" -version = "0.5.4" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" +checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" dependencies = [ "phf_generator", "phf_shared", @@ -7383,6 +7347,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web_atoms" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a9779e9f04d2ac1ce317aee707aa2f6b773afba7b931222bff6983843b1576" +dependencies = [ + "phf", + "phf_codegen", + "string_cache", + "string_cache_codegen", +] + [[package]] name = "webpki-roots" version = "1.0.2" diff --git a/Cargo.toml b/Cargo.toml index ab35c83773..3d2b010d0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -249,6 +249,7 @@ spirv-std = { git = "https://github.com/Firestar99/rust-gpu-new", rev = "c12f216 cargo-gpu = { git = "https://github.com/Firestar99/cargo-gpu", rev = "3952a22d16edbd38689f3a876e417899f21e1fe7", default-features = false } qrcodegen = "1.8" lzma-rust2 = { version = "0.16", default-features = false, features = ["std", "encoder", "optimization", "xz"] } +scraper = "0.25" [workspace.lints.rust] unexpected_cfgs = { level = "allow", check-cfg = ['cfg(target_arch, values("spirv"))'] } diff --git a/frontend/package.json b/frontend/package.json index 48da952460..6bcc0670c1 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -39,7 +39,6 @@ "source-sans-pro": "2.45.0" }, "devDependencies": { - "license-checker-rseidelsohn": "^4.4.2", "@eslint/compat": "^2.0.1", "@eslint/eslintrc": "^3.3.3", "@eslint/js": "^9.39.2", @@ -52,19 +51,20 @@ "eslint-plugin-prettier": "^5.5.5", "eslint-plugin-svelte": "^3.14.0", "globals": "^17.0.0", + "license-checker-rseidelsohn": "^4.4.2", "postcss": "^8.5.6", - "prettier": "^3.8.0", "prettier-plugin-svelte": "^3.4.1", + "prettier": "^3.8.0", "process": "^0.11.10", "sass": "^1.97.2", - "svelte": "5.47.1", "svelte-preprocess": "^6.0.3", + "svelte": "5.47.1", "tar": "^7.5.4", "ts-node": "^10.9.2", - "typescript": "^5.9.3", "typescript-eslint": "^8.53.1", - "vite": "^7.3.1", - "vite-multiple-assets": "2.2.6" + "typescript": "^5.9.3", + "vite-multiple-assets": "2.2.6", + "vite": "^7.3.1" }, "homepage": "https://graphite.art", "license": "Apache-2.0", diff --git a/tools/third-party-licenses/Cargo.toml b/tools/third-party-licenses/Cargo.toml index 23ade85f96..4a59967389 100644 --- a/tools/third-party-licenses/Cargo.toml +++ b/tools/third-party-licenses/Cargo.toml @@ -9,8 +9,11 @@ authors.workspace = true desktop = ["dep:cef-dll-sys", "dep:scraper"] [dependencies] +# Workspace dependencies serde = { workspace = true } serde_json = { workspace = true } lzma-rust2 = { workspace = true } + +# Optional workspace dependencies cef-dll-sys = { workspace = true, optional = true } -scraper = { version = "0.22", optional = true } +scraper = { workspace = true, optional = true } diff --git a/tools/third-party-licenses/src/cef.rs b/tools/third-party-licenses/src/cef.rs index 47fbc545e7..94fe45d08d 100644 --- a/tools/third-party-licenses/src/cef.rs +++ b/tools/third-party-licenses/src/cef.rs @@ -28,6 +28,42 @@ impl Hash for CefLicenseSource { } } +fn parse(html: &str) -> Vec { + let document = Html::parse_document(html); + + let product_sel = Selector::parse("div.product").unwrap(); + let title_sel = Selector::parse("span.title").unwrap(); + let homepage_sel = Selector::parse("span.homepage a").unwrap(); + let license_sel = Selector::parse("div.license pre").unwrap(); + + document + .select(&product_sel) + .filter_map(|product| { + let name: String = product.select(&title_sel).next().map(|el| el.text().collect()).unwrap_or_default(); + + if name.is_empty() { + return None; + } + + let homepage = product.select(&homepage_sel).next().and_then(|el| el.value().attr("href").map(String::from)); + + let license_text: String = product.select(&license_sel).next().map(|el| el.text().collect::()).unwrap_or_default().trim().to_string(); + + let pkg = Package { + name, + url: homepage, + authors: Vec::new(), + }; + + Some(LicenseEntry { + name: None, + text: license_text, + packages: vec![pkg], + }) + }) + .collect() +} + fn read() -> String { let cef_path = PathBuf::from(env!("CEF_PATH")); let cef_credits = std::fs::read_dir(&cef_path) @@ -67,39 +103,3 @@ fn read() -> String { }) } } - -fn parse(html: &str) -> Vec { - let document = Html::parse_document(html); - - let product_sel = Selector::parse("div.product").unwrap(); - let title_sel = Selector::parse("span.title").unwrap(); - let homepage_sel = Selector::parse("span.homepage a").unwrap(); - let license_sel = Selector::parse("div.license pre").unwrap(); - - document - .select(&product_sel) - .filter_map(|product| { - let name: String = product.select(&title_sel).next().map(|el| el.text().collect()).unwrap_or_default(); - - if name.is_empty() { - return None; - } - - let homepage = product.select(&homepage_sel).next().and_then(|el| el.value().attr("href").map(String::from)); - - let license_text: String = product.select(&license_sel).next().map(|el| el.text().collect::()).unwrap_or_default().trim().to_string(); - - let pkg = Package { - name, - url: homepage, - authors: Vec::new(), - }; - - Some(LicenseEntry { - name: None, - text: license_text, - packages: vec![pkg], - }) - }) - .collect() -} diff --git a/tools/third-party-licenses/src/main.rs b/tools/third-party-licenses/src/main.rs index 232b4fef0f..67ffa00a37 100644 --- a/tools/third-party-licenses/src/main.rs +++ b/tools/third-party-licenses/src/main.rs @@ -167,6 +167,7 @@ fn format_credits(licenses: &Vec) -> String { out.push_str(" "); out.push_str(line); } + out = out.trim_end().to_string(); out.push('\n'); } diff --git a/tools/third-party-licenses/src/npm.rs b/tools/third-party-licenses/src/npm.rs index 379a134c54..aac94f5cad 100644 --- a/tools/third-party-licenses/src/npm.rs +++ b/tools/third-party-licenses/src/npm.rs @@ -17,7 +17,7 @@ impl NpmLicenseSource { impl LicenceSource for NpmLicenseSource { fn licenses(&self) -> Vec { - parse(&run(&self.dir)) + parse(run(&self.dir)) } } @@ -28,6 +28,8 @@ impl std::hash::Hash for NpmLicenseSource { } } +type Output = HashMap; + #[derive(serde::Deserialize)] struct NpmEntry { licenses: Option, @@ -38,34 +40,8 @@ struct NpmEntry { email: Option, } -pub fn run(dir: &std::path::Path) -> String { - #[cfg(not(target_os = "windows"))] - let mut cmd = Command::new("npx"); - #[cfg(target_os = "windows")] - let mut cmd = Command::new("npx.cmd"); - cmd.args(["license-checker-rseidelsohn", "--production", "--json"]); - cmd.current_dir(dir); - - let output = cmd.output().unwrap_or_else(|e| { - eprintln!("Failed to run npx license-checker-rseidelsohn: {e}"); - process::exit(1); - }); - - if !output.status.success() { - eprintln!("npx license-checker-rseidelsohn failed:\n{}", String::from_utf8_lossy(&output.stderr)); - process::exit(1); - } - - String::from_utf8(output.stdout).expect("Invalid UTF-8 from license-checker") -} - -pub fn parse(json_str: &str) -> Vec { - let entries: HashMap = serde_json::from_str(json_str).unwrap_or_else(|e| { - eprintln!("Failed to parse license-checker JSON: {e}"); - process::exit(1) - }); - - entries +fn parse(parsed: Output) -> Vec { + parsed .iter() .map(|(name, entry)| { let publisher_info = entry.publisher.as_ref().map(|p| { @@ -89,3 +65,29 @@ pub fn parse(json_str: &str) -> Vec { }) .collect() } + +fn run(dir: &std::path::Path) -> Output { + #[cfg(not(target_os = "windows"))] + let mut cmd = Command::new("npx"); + #[cfg(target_os = "windows")] + let mut cmd = Command::new("npx.cmd"); + cmd.args(["license-checker-rseidelsohn", "--production", "--json"]); + cmd.current_dir(dir); + + let output = cmd.output().unwrap_or_else(|e| { + eprintln!("Failed to run npx license-checker-rseidelsohn: {e}"); + process::exit(1); + }); + + if !output.status.success() { + eprintln!("npx license-checker-rseidelsohn failed:\n{}", String::from_utf8_lossy(&output.stderr)); + process::exit(1); + } + + let json_str = String::from_utf8(output.stdout).expect("Invalid UTF-8 from license-checker"); + + serde_json::from_str(&json_str).unwrap_or_else(|e| { + eprintln!("Failed to parse license-checker JSON: {e}"); + process::exit(1) + }) +} From baa1ff0df286b67eddec7d87ca285f7d6ffd0b61 Mon Sep 17 00:00:00 2001 From: Timon Date: Wed, 25 Feb 2026 23:19:00 +0000 Subject: [PATCH 09/12] review --- tools/third-party-licenses/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/third-party-licenses/src/main.rs b/tools/third-party-licenses/src/main.rs index 67ffa00a37..afa1b501f0 100644 --- a/tools/third-party-licenses/src/main.rs +++ b/tools/third-party-licenses/src/main.rs @@ -167,7 +167,7 @@ fn format_credits(licenses: &Vec) -> String { out.push_str(" "); out.push_str(line); } - out = out.trim_end().to_string(); + out.truncate(out.trim_end().len()); out.push('\n'); } From fe2aba5e0dd6c05534f8247ac663b15ab5f7379e Mon Sep 17 00:00:00 2001 From: Timon Date: Wed, 25 Feb 2026 23:29:56 +0000 Subject: [PATCH 10/12] fixup --- Cargo.lock | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7b746a8ff4..6bf8977a92 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5568,7 +5568,7 @@ version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "feef350c36147532e1b79ea5c1f3791373e61cbd9a6a2615413b3807bb164fb7" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.11.0", "cssparser", "derive_more", "log", @@ -5685,15 +5685,12 @@ dependencies = [ ] [[package]] -name = "serde_urlencoded" -version = "0.7.1" +name = "servo_arc" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +checksum = "170fb83ab34de17dc69aa7c67482b22218ddb85da56546f9bd6b929e32a05930" dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", + "stable_deref_trait", ] [[package]] @@ -7247,6 +7244,27 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web_atoms" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a9779e9f04d2ac1ce317aee707aa2f6b773afba7b931222bff6983843b1576" +dependencies = [ + "phf", + "phf_codegen", + "string_cache", + "string_cache_codegen", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "webpki-roots" version = "1.0.2" From e681dce7a945b44fe41c8bde505e77083878c649 Mon Sep 17 00:00:00 2001 From: Timon Date: Thu, 26 Feb 2026 10:28:27 +0000 Subject: [PATCH 11/12] Review --- .github/workflows/build-win-bundle.yml | 2 +- desktop/src/app.rs | 6 +++--- desktop/wrapper/src/handle_desktop_wrapper_message.rs | 4 ++-- desktop/wrapper/src/messages.rs | 4 +++- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build-win-bundle.yml b/.github/workflows/build-win-bundle.yml index 33fff4f7e3..ee72140db8 100644 --- a/.github/workflows/build-win-bundle.yml +++ b/.github/workflows/build-win-bundle.yml @@ -70,7 +70,7 @@ jobs: cargo binstall --no-confirm --force "wasm-bindgen-cli@$env:WASM_BINDGEN_CLI_VERSION" - name: Build Windows Bundle - shell: bash # cargo about refuses to run in powershell + shell: bash # `cargo-about` refuses to run in powershell env: CARGO_TERM_COLOR: always run: npm run build-desktop diff --git a/desktop/src/app.rs b/desktop/src/app.rs index 5b8af9654e..414e0a40c5 100644 --- a/desktop/src/app.rs +++ b/desktop/src/app.rs @@ -420,13 +420,13 @@ impl App { DesktopFrontendMessage::LoadThirdPartyLicenses => { let compressed = include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/third-party-licenses.txt.xz")); let mut reader = lzma_rust2::XzReader::new(compressed.as_slice(), false); - let mut string = String::new(); - if let Err(e) = reader.read_to_string(&mut string) { + let mut text = String::new(); + if let Err(e) = reader.read_to_string(&mut text) { tracing::error!("Failed to decompress third-party licenses: {e}"); return; } - let message = DesktopWrapperMessage::LoadThirdPartyLicenses(string); + let message = DesktopWrapperMessage::LoadThirdPartyLicenses { text }; responses.push(message); } } diff --git a/desktop/wrapper/src/handle_desktop_wrapper_message.rs b/desktop/wrapper/src/handle_desktop_wrapper_message.rs index 9cd88a0da9..a62fe0b3f9 100644 --- a/desktop/wrapper/src/handle_desktop_wrapper_message.rs +++ b/desktop/wrapper/src/handle_desktop_wrapper_message.rs @@ -97,8 +97,8 @@ pub(super) fn handle_desktop_wrapper_message(dispatcher: &mut DesktopWrapperMess let message = AppWindowMessage::PointerLockMove { x, y }; dispatcher.queue_editor_message(message); } - DesktopWrapperMessage::LoadThirdPartyLicenses(string) => { - let message = DialogMessage::RequestLicensesThirdPartyDialogWithLicenseText { license_text: string }; + DesktopWrapperMessage::LoadThirdPartyLicenses { text } => { + let message = DialogMessage::RequestLicensesThirdPartyDialogWithLicenseText { license_text: text }; dispatcher.queue_editor_message(message); } } diff --git a/desktop/wrapper/src/messages.rs b/desktop/wrapper/src/messages.rs index 3b8ee1b0a5..087c1fde8a 100644 --- a/desktop/wrapper/src/messages.rs +++ b/desktop/wrapper/src/messages.rs @@ -127,7 +127,9 @@ pub enum DesktopWrapperMessage { x: f64, y: f64, }, - LoadThirdPartyLicenses(String), + LoadThirdPartyLicenses { + text: String, + }, } #[derive(Clone, serde::Serialize, serde::Deserialize, Debug)] From db25f4c90f0824f8061006c67302a8c71f5052a3 Mon Sep 17 00:00:00 2001 From: Timon Date: Thu, 26 Feb 2026 10:37:03 +0000 Subject: [PATCH 12/12] nix fmt --- .nix/pkgs/tools/third-party-licenses.nix | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/.nix/pkgs/tools/third-party-licenses.nix b/.nix/pkgs/tools/third-party-licenses.nix index 4dfb24834a..9559603377 100644 --- a/.nix/pkgs/tools/third-party-licenses.nix +++ b/.nix/pkgs/tools/third-party-licenses.nix @@ -21,9 +21,11 @@ let doCheck = false; }; in -deps.crane.lib.buildPackage common -// { - inherit cargoVendorDir; - cargoArtifacts = deps.crane.lib.buildDepsOnly common; - meta.mainProgram = "third-party-licenses"; -} +deps.crane.lib.buildPackage ( + common + // { + inherit cargoVendorDir; + cargoArtifacts = deps.crane.lib.buildDepsOnly common; + meta.mainProgram = "third-party-licenses"; + } +)