From 3d382ad7415c1a8d5945cb8524763515ba0e501a Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Tue, 10 Mar 2026 20:29:35 +0000 Subject: [PATCH 01/61] Improve docs and compare with alternatives --- README.md | 56 ++++++++++++++++++--- docs/advanced-ts.md | 4 +- src/lib.rs | 5 +- src/operation_transformation/edited_text.rs | 39 ++++++-------- src/operation_transformation/operation.rs | 23 ++++----- src/raw_operation.rs | 4 +- src/tokenizer.rs | 2 +- src/tokenizer/token.rs | 10 ++-- src/types/history.rs | 3 +- src/types/number_or_text.rs | 4 ++ src/types/span_with_history.rs | 3 +- src/types/text_with_cursors.rs | 5 +- src/utils/string_builder.rs | 7 ++- src/wasm.rs | 10 ++-- 14 files changed, 106 insertions(+), 69 deletions(-) diff --git a/README.md b/README.md index 7925ed5..bdb6c39 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Alternatively, add `reconcile-text` to your `Cargo.toml`: ```toml [dependencies] -reconcile-text = "0.5" +reconcile-text = "0.8" ``` Then start merging: @@ -52,7 +52,7 @@ let result = reconcile(parent, &left.into(), &right.into(), &*BuiltinTokenizer:: assert_eq!(result.apply().text(), "Hi beautiful world"); ``` -See the [merge-file example](examples/merge-file.rs) for another example or the [library's documentation](https://docs.rs/reconcile-text/latest/reconcile_text). +See the [merge-file example](examples/merge-file.rs) for another example, or the [library's documentation](https://docs.rs/reconcile-text/latest/reconcile_text). ### JavaScript/TypeScript @@ -77,7 +77,7 @@ const result = reconcile(parent, left, right); console.log(result.text); // "Hi beautiful world" ``` -See the [example website source](examples/website/src/index.ts) for a more complex example or the [advanced examples document](https://github.com/schmelczer/reconcile/blob/main/docs/advanced-ts.md). +See the [example website source](examples/website/src/index.ts) for a more complex example, or the [advanced examples document](https://github.com/schmelczer/reconcile/blob/main/docs/advanced-ts.md). ## Motivation @@ -87,13 +87,13 @@ This creates **Differential Synchronisation** scenarios ([2], [3]): we only know > **Note**: Some text domains require more careful handling. Legal contracts, for instance, could have unintended meaning changes from conflicting edits that create double negations. At the same time, semantic conflicts can still arise when merging code, even in the absence of syntactic conflicts. -Differential sync is implemented by [universal-sync](https://github.com/invisible-college/universal-sync) and my Obsidian plugin [vault-link](https://github.com/schmelczer/vault-link), and it requires a merging tool which creates conflict-free results for the best user experience. +Differential sync is implemented by [universal-sync](https://github.com/invisible-college/universal-sync) and my Obsidian plugin [vault-link](https://github.com/schmelczer/vault-link), and it requires a merging tool that creates conflict-free results for the best user experience. ## How it works `reconcile-text` starts off similarly to `diff3` ([4], [5]) but adds automated conflict resolution. Given a **parent** document and two modified versions (`left` and `right`), the following happens: -1. **Tokenisation** — Input texts get split into meaningful units (words, characters, etc.) for granular merging +1. **Tokenisation** — Input texts are split into meaningful units (words, characters, etc.) for granular merging 2. **Diff computation** — Myers' algorithm calculates differences between (parent ↔ left) and (parent ↔ right) 3. **Diff optimisation** — Operations are reordered and consolidated to maximise chained changes 4. **Operational Transformation** — Edits are woven together using OT principles, preserving all modifications and updating cursors @@ -102,6 +102,48 @@ Whilst the primary goal of `reconcile-text` isn't to implement OT, it provides a However, when only the end result of concurrent changes is observable, merge quality depends entirely on the quality of the underlying 2-way diffs. For instance, `move` operations cannot be supported because Myers' algorithm decomposes them into separate `insert` and `delete` operations, regardless of the merging algorithm used. +## Comparison with other approaches + +### Traditional 3-way merge (diff3, Git) + +Tools like `diff3` ([4]) and Git produce **conflict markers** (`<<<<<<<` / `=======` / `>>>>>>>`) when both sides modify the same region. This works for source code where a human must verify correctness, but breaks the reading flow for prose. `reconcile-text` uses the same diff3-like foundation but adds an OT-inspired resolution step that eliminates conflict markers entirely. Libraries like [diffy](https://crates.io/crates/diffy), [merge3](https://github.com/breezy-team/merge3-rs) (Rust), and [node-diff3](https://github.com/bhousel/node-diff3) (JavaScript) all fall into this category. + +### diff-match-patch + +[diff-match-patch](https://github.com/google/diff-match-patch) ([6]) is a widely-used library created by Neil Fraser at Google in 2006, providing character-level diffing (Myers' algorithm), fuzzy string matching (Bitap algorithm), and patch application. It powers Fraser's **Differential Synchronisation** protocol ([2]): compute a diff between two texts, apply the patch to a third text that may have drifted, and repeat until convergence. If a patch fails, the failure self-corrects in the next sync cycle. + +The key differences from `reconcile-text`: + +- **2-way vs 3-way** — diff-match-patch diffs two texts and applies the result as a patch. It has no concept of a common ancestor and cannot reason about "left changes" vs "right changes". `reconcile-text` performs true 3-way merging, understanding the intent behind each side's edits. + +- **Character-level only** — Word-level and line-level diffs require encoding tokens as single Unicode characters before diffing ([7]). `reconcile-text` supports word, character, line, and custom tokenisation natively. + +- **Patches can fail** — `patch_apply` returns a boolean array indicating success per patch; failed patches are silently dropped. In Differential Synchronisation, failures self-correct in the next cycle, but for one-shot merges edits can be lost. `reconcile-text` always produces a complete merged result. + +- **No cursor tracking or change provenance** — diff-match-patch does not reposition cursors or track which side made which edit. `reconcile-text` does both automatically. + +See the [comparison example](examples/compare-with-diff-match-patch.rs) for concrete cases where diff-match-patch garbles adjacent edits and silently drops an entire sentence, while `reconcile-text` merges both users' changes correctly. + +> **When to use diff-match-patch instead**: when you don't have a common ancestor—for example, synchronising texts that have diverged through an unknown sequence of edits. If you have a common ancestor (as in most version control and collaborative editing scenarios), `reconcile-text` produces more reliable results. + +### CRDTs (Yjs, Automerge, Loro, diamond-types) + +Conflict-free Replicated Data Types guarantee convergence by mathematical construction: every operation commutes, so the order of application doesn't matter. Libraries like [Yjs](https://github.com/yjs/yjs) (and its Rust port [Yrs](https://github.com/y-crdt/y-crdt)), [Automerge](https://github.com/automerge/automerge), [Loro](https://github.com/loro-dev/loro), [cola](https://github.com/nomad/cola), and [diamond-types](https://github.com/josephg/diamond-types) implement this approach. + +CRDTs capture every individual keystroke or operation, assigning each a unique identity. This makes them ideal when you control the complete editing infrastructure: the editor, the transport layer, and the storage format. They work peer-to-peer, handle arbitrary numbers of concurrent editors, and never lose an edit. + +The trade-off is that CRDTs require **maintaining document state over time**—an operation log or internal data structure that grows with the document's edit history. You cannot simply hand a CRDT library three plain strings and get a merged result. This makes them unsuitable for Differential Synchronisation scenarios where you only observe the final state of each document, which is exactly the niche `reconcile-text` fills. + +> **When to use CRDTs instead**: if you control the complete editing stack and can capture every operation as it happens, CRDTs provide stronger convergence guarantees. They also support more than two concurrent editors naturally, whereas `reconcile-text` merges exactly two forks at a time (though merges can be chained). + +### Operational Transformation (OT) + +OT libraries like [ot.js](https://ot.js.org/) and [ShareJS](https://github.com/josephg/ShareJS) transform concurrent operations against each other so that applying them in any order produces the same result. Like CRDTs, they capture individual operations and require infrastructure to coordinate them—typically a central server that determines the canonical operation order. + +`reconcile-text` borrows the *concept* of OT (transforming one side's edits against the other) but applies it to a different problem. Instead of transforming individual keystrokes in real time, it transforms the consolidated diff output of two complete edits. This means it doesn't need a server, doesn't need to capture operations as they happen, and works entirely offline. + +> **When to use OT instead**: if you need real-time collaboration with sub-second latency and can run a coordination server, dedicated OT libraries handle this well. `reconcile-text` is designed for merge points, not live keystroke-by-keystroke synchronisation. + ## Development Contributions are welcome! @@ -142,8 +184,10 @@ Install [rustup](https://rustup.rs): [MIT](./LICENSE) -[1]:https://marijnhaverbeke.nl/blog/collaborative-editing-cm.html +[1]: https://marijnhaverbeke.nl/blog/collaborative-editing-cm.html [2]: https://neil.fraser.name/writing/sync/ [3]: https://www.cis.upenn.edu/~bcpierce/papers/diff3-short.pdf [4]: https://blog.jcoglan.com/2017/05/08/merging-with-diff3/ [5]: https://static.googleusercontent.com/media/research.google.com/en//pubs/archive/35605.pdf +[6]: https://github.com/google/diff-match-patch +[7]: https://github.com/google/diff-match-patch/wiki/Line-or-Word-Diffs diff --git a/docs/advanced-ts.md b/docs/advanced-ts.md index 5ecf065..0480bd4 100644 --- a/docs/advanced-ts.md +++ b/docs/advanced-ts.md @@ -40,7 +40,7 @@ console.log(result.history); /* ## Tokenisation Strategies -Reconcile offers different approaches to split text for merging: +`reconcile-text` offers different approaches to split text for merging: - **Word tokeniser** (`"Word"`) — Splits on word boundaries (recommended for prose) - **Character tokeniser** (`"Character"`) — Individual characters (fine-grained control) @@ -48,7 +48,7 @@ Reconcile offers different approaches to split text for merging: ## Cursor Tracking -Reconcile automatically tracks cursor positions through merges, which is handy in collaborative editors. Selections can be tracked by providing them as a pair of cursors. +`reconcile-text` automatically tracks cursor positions through merges, which is useful for collaborative editors. Selections can be tracked by providing them as a pair of cursors. ```javascript const result = reconcile( diff --git a/src/lib.rs b/src/lib.rs index 654daa7..e759cc2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,7 +59,7 @@ //! //! For specialised use cases, such as structured languages, custom //! tokenisation logic can be implemented by providing a function with the -//! signature `Fn(&str) -> Vec>`:: +//! signature `Fn(&str) -> Vec>`: //! //! ``` //! use reconcile_text::{reconcile, Token, BuiltinTokenizer}; @@ -151,10 +151,11 @@ //! ] //! ); //! ``` +//! //! ## Efficiently serialize changes //! //! The edits can be serialized into a compact representation without the full -//! original text, making the size only depends on the changes made. +//! original text, making the size depend only on the changes made. //! //! ```rust //! # #[cfg(feature = "serde")] diff --git a/src/operation_transformation/edited_text.rs b/src/operation_transformation/edited_text.rs index 1f41cb1..bc30137 100644 --- a/src/operation_transformation/edited_text.rs +++ b/src/operation_transformation/edited_text.rs @@ -18,18 +18,16 @@ use crate::{ utils::string_builder::StringBuilder, }; -/// A text document and a sequence of operations that can be applied to the text -/// document. `EditedText` supports merging two sequences of operations using -/// the principles of Operational Transformation. +/// A text document with a sequence of operations derived from diffing it +/// against an updated version. Supports merging two `EditedText` instances +/// (from the same original) via Operational Transformation. /// -/// It's mainly created through the `from_strings` method, then merged with -/// another `EditedText` derived from the same original text and then applied to -/// the original text to get the reconciled text of concurrent edits. +/// Created via `from_strings`, `from_strings_with_tokenizer`, or `from_diff`, +/// then merged with another `EditedText` and applied to get the reconciled +/// text. /// -/// In addition to text and operations, it also keeps track of cursor positions -/// in the original text. The cursor positions are updated when the operations -/// are applied, so that the cursor positions can be used to restore the -/// cursor positions in the updated text. +/// Also tracks cursor positions from the updated text, repositioning them +/// when operations are applied. #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq, Default)] pub struct EditedText<'a, T> @@ -43,12 +41,8 @@ where } impl<'a> EditedText<'a, String> { - /// Create an `EditedText` from the given original (old) and updated (new) - /// strings. The returned `EditedText` represents the changes from the - /// original to the updated text. When the return value is applied to - /// the original text, it will result in the updated text. The default - /// word tokenizer is used to tokenize the text which splits the text on - /// whitespaces. + /// Create an `EditedText` from the given original and updated strings. + /// Uses the default word tokenizer (splits on word boundaries). #[must_use] pub fn from_strings(original: &'a str, updated: &TextWithCursors) -> Self { Self::from_strings_with_tokenizer(original, updated, &*BuiltinTokenizer::Word) @@ -59,11 +53,8 @@ impl<'a, T> EditedText<'a, T> where T: PartialEq + Clone + Debug, { - /// Create an `EditedText` from the given original (old) and updated (new) - /// strings. The returned `EditedText` represents the changes from the - /// original to the updated text. When the return value is applied to - /// the original text, it will result in the updated text. The tokenizer - /// function is used to tokenize the text. + /// Create an `EditedText` from the given original and updated strings + /// using the provided tokenizer. pub fn from_strings_with_tokenizer( original: &'a str, updated: &TextWithCursors, @@ -110,7 +101,7 @@ where /// /// # Panics /// - /// Panics if there's an integer overflow (in i64) when calculating new + /// Panics if there's an integer overflow (in isize) when calculating new /// cursor positions. #[must_use] #[allow(clippy::too_many_lines)] @@ -280,7 +271,7 @@ where /// Apply the operations to the text and return the resulting text in chunks /// together with the provenance describing where each chunk came from. /// - /// The result includes deleted spans as well. + /// Returns all spans including deletions (not present in the merged text). /// /// ``` /// use reconcile_text::{History, SpanWithHistory, BuiltinTokenizer, reconcile}; @@ -422,7 +413,7 @@ where result } - /// Deserialize an `EditedText` from a change list and the original text. + /// Reconstruct an `EditedText` from a diff and the original text. /// /// # Errors /// diff --git a/src/operation_transformation/operation.rs b/src/operation_transformation/operation.rs index 7a8f92a..823a50b 100644 --- a/src/operation_transformation/operation.rs +++ b/src/operation_transformation/operation.rs @@ -46,9 +46,8 @@ impl Operation where T: PartialEq + Clone + Debug, { - /// Creates an equal operation with the given index. - /// This operation is used to indicate that the text at the given index - /// is unchanged. + /// Creates an equal (retain) operation starting at the given character + /// offset in the original text. pub fn create_equal(order: usize, length: usize) -> Self { Operation::Equal { order, @@ -69,13 +68,14 @@ where } } - /// Creates an insert operation with the given index and text. + /// Creates an insert operation at the given character offset with the + /// given tokens. pub fn create_insert(order: usize, text: Vec>) -> Self { Operation::Insert { order, text } } - /// Creates a delete operation with the given index and number of - /// to-be-deleted characters. + /// Creates a delete operation at the given character offset for the + /// specified number of characters. pub fn create_delete(order: usize, deleted_character_count: usize) -> Self { Operation::Delete { order, @@ -179,8 +179,8 @@ where builder } - /// Returns the number of affected characters. It is always greater than 0 - /// because empty operations cannot be created. + /// Returns the number of affected characters. May be 0 after + /// `merge_operations`. pub fn len(&self) -> usize { match self { Operation::Equal { length, .. } => *length, @@ -192,10 +192,9 @@ where } } - /// Merges the operation with the given context, producing a new operation - /// and updating the context. This implements a comples FSM that handles - /// the merging of operations in a way that is consistent with the text. - /// The contexts are updated in-place. + /// Adjusts this operation based on `previous_operation` from the other side + /// to avoid duplicating or conflicting changes. Updates + /// `previous_operation` in-place. #[allow(clippy::too_many_lines)] pub fn merge_operations(self, previous_operation: &mut Option) -> Operation { let operation = self; diff --git a/src/raw_operation.rs b/src/raw_operation.rs index aa3ab8f..1572e88 100644 --- a/src/raw_operation.rs +++ b/src/raw_operation.rs @@ -2,9 +2,9 @@ use std::fmt::Debug; use crate::{tokenizer::token::Token, utils::myers_diff::myers_diff}; -/// Text editing operation containing the to-be-changed `Tokens`-s. +/// Text editing operation containing the affected tokens. /// -/// `RawOperations` can be joined together when the underlying tokens +/// `RawOperation`s can be joined together when the underlying tokens /// allow for joining subsequent operations. #[derive(Debug, Clone, PartialEq)] pub enum RawOperation diff --git a/src/tokenizer.rs b/src/tokenizer.rs index 62ab528..fabafcd 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -12,7 +12,7 @@ use wasm_bindgen::prelude::*; pub mod token; -/// A trait for tokenizers that take a string and return a list of tokens. +/// Type alias for tokenizer functions that split a string into tokens. pub type Tokenizer = dyn Fn(&str) -> Vec>; #[cfg_attr(feature = "wasm", wasm_bindgen)] diff --git a/src/tokenizer/token.rs b/src/tokenizer/token.rs index 58e6ab6..2f2dc82 100644 --- a/src/tokenizer/token.rs +++ b/src/tokenizer/token.rs @@ -3,13 +3,11 @@ use std::fmt::Debug; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; -/// A token is a string that has been normalized in some way. +/// A token with a normalized form (used for diffing) and an original form +/// (used when applying operations). Joinability flags control whether +/// adjacent insertions interleave or group. /// -/// A token consists of the normalized form is used for comparison, and the -/// original form used for subsequently applying `Operation`-s to a text -/// document. -/// -/// It's UTF-8 compatible. +/// UTF-8 compatible. #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone)] pub struct Token diff --git a/src/types/history.rs b/src/types/history.rs index e30ae90..431a481 100644 --- a/src/types/history.rs +++ b/src/types/history.rs @@ -15,8 +15,7 @@ pub enum History { RemovedFromRight = "RemovedFromRight", } -/// Simple enum for describing the result of `reconcile` in a flat list. -/// When compiled to WASM, the enum values are the same as their names. +/// Provenance label for each span returned by `apply_with_history`. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg(not(feature = "wasm"))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] diff --git a/src/types/number_or_text.rs b/src/types/number_or_text.rs index bb42bc7..fe17af4 100644 --- a/src/types/number_or_text.rs +++ b/src/types/number_or_text.rs @@ -27,6 +27,10 @@ impl TryFrom for NumberOrText { } if let Some(num) = value.clone().as_f64() { + if num.is_nan() { + return Err(DeserialisationError::new("NaN is not a valid number")); + } + if num.abs() > INTEGRAL_LIMIT { return Err(DeserialisationError::new( "Floating-point number exceeds safe integer limit, use BigInt instead", diff --git a/src/types/span_with_history.rs b/src/types/span_with_history.rs index 09f778f..1e4481c 100644 --- a/src/types/span_with_history.rs +++ b/src/types/span_with_history.rs @@ -5,8 +5,7 @@ use wasm_bindgen::prelude::*; use crate::types::history::History; -/// Wrapper type for `(String, History)` where History describes the origin of -/// `text`. +/// A text span annotated with its origin in a merge result. #[allow(clippy::unsafe_derive_deserialize)] #[cfg_attr(feature = "wasm", wasm_bindgen)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] diff --git a/src/types/text_with_cursors.rs b/src/types/text_with_cursors.rs index c9dec89..8f4af19 100644 --- a/src/types/text_with_cursors.rs +++ b/src/types/text_with_cursors.rs @@ -12,12 +12,15 @@ pub struct TextWithCursors { #[cfg_attr(feature = "wasm", wasm_bindgen)] impl TextWithCursors { + /// # Panics + /// + /// Panics if any cursor's `char_index` exceeds the text's character length. #[cfg_attr(feature = "wasm", wasm_bindgen(constructor))] #[must_use] pub fn new(text: String, cursors: Vec) -> Self { let length = text.chars().count(); for cursor in &cursors { - debug_assert!( + assert!( cursor.char_index <= length, // cursor.char_index == length means that the cursor is at the end "Cursor positions ({}) must be contained within the text (of length {length}) or \ diff --git a/src/utils/string_builder.rs b/src/utils/string_builder.rs index 34110d8..40928c3 100644 --- a/src/utils/string_builder.rs +++ b/src/utils/string_builder.rs @@ -1,9 +1,8 @@ use std::{fmt, iter::Iterator}; -/// A helper for building a string in-order based on an original string and a -/// series of insertions, deletions, and copies applied to it. It is safe to use -/// with UTF-8 strings as all operations are based on character indices. The -/// methods must be called in-order. +/// A helper for building a string sequentially from an original string via +/// insertions, deletions, and copies. All operations use character counts, +/// safe for UTF-8. Methods must be called in-order. pub struct StringBuilder<'a> { original: Box + 'a>, buffer: String, diff --git a/src/wasm.rs b/src/wasm.rs index 6fc02f2..959f1a6 100644 --- a/src/wasm.rs +++ b/src/wasm.rs @@ -22,7 +22,7 @@ pub fn reconcile( crate::reconcile(parent, left, right, &*tokenizer).apply() } -/// WASM wrapper around `crate::reconcile` for merging text. +/// WASM wrapper around `crate::reconcile` that also returns provenance history. #[wasm_bindgen(js_name = reconcileWithHistory)] #[must_use] pub fn reconcile_with_history( @@ -94,12 +94,12 @@ pub fn diff(parent: &str, changed: &TextWithCursors, tokenizer: BuiltinTokenizer .collect() } -/// Inverse of `diff`, applies a compact diff representation to a parent text +/// Inverse of `diff`, applies a compact diff representation to a parent text. /// -/// # Panics +/// # Errors /// -/// Panics if the diff format is invalid or there's an integer overflow when -/// applying the diff. +/// Returns a JS error if the diff format is invalid or references ranges +/// exceeding the original text length. #[wasm_bindgen(js_name = undiff)] #[must_use] pub fn undiff(parent: &str, diff: Vec, tokenizer: BuiltinTokenizer) -> String { From b012330a365bcd59b3b1233bf9bd2e8ec3b63182 Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Tue, 10 Mar 2026 20:31:31 +0000 Subject: [PATCH 02/61] Add comparison example --- Cargo.toml | 5 ++ examples/compare-with-diff-match-patch.rs | 92 +++++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 examples/compare-with-diff-match-patch.rs diff --git a/Cargo.toml b/Cargo.toml index 83aff23..ba89963 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,10 @@ crate-type = ["cdylib", "rlib"] name = "merge-file" path = "examples/merge-file.rs" +[[example]] +name = "compare-with-diff-match-patch" +path = "examples/compare-with-diff-match-patch.rs" + [dependencies] serde = { version = "1.0.219", optional = true, features = ["derive"] } thiserror = "2.0.17" @@ -48,6 +52,7 @@ serde = { version = "1.0.219", features = ["derive"] } serde_yaml = "0.9.34" test-case = "3.3.1" wasm-bindgen-test = "0.3.56" +diff-match-patch-rs = "0.5" [profile.release] codegen-units = 1 diff --git a/examples/compare-with-diff-match-patch.rs b/examples/compare-with-diff-match-patch.rs new file mode 100644 index 0000000..f748e6f --- /dev/null +++ b/examples/compare-with-diff-match-patch.rs @@ -0,0 +1,92 @@ +use std::panic; + +use diff_match_patch_rs::{Compat, DiffMatchPatch, PatchInput}; +use reconcile_text::{BuiltinTokenizer, reconcile}; + +fn dmp_merge(parent: &str, left: &str, right: &str) -> Option { + let parent = parent.to_owned(); + let left = left.to_owned(); + let right = right.to_owned(); + + // diff-match-patch-rs can panic on some inputs, so we catch that. + panic::catch_unwind(|| { + let dmp = DiffMatchPatch::new(); + let diffs = dmp.diff_main::(&parent, &left).ok()?; + let patches = dmp + .patch_make(PatchInput::new_text_diffs(&parent, &diffs)) + .ok()?; + let (result, _) = dmp.patch_apply(&patches, &right).ok()?; + Some(result) + }) + .ok() + .flatten() +} + +fn try_merge(parent: &str, left: &str, right: &str) { + let dmp_result = dmp_merge(parent, left, right); + + let reconcile_result = reconcile( + parent, + &left.into(), + &right.into(), + &*BuiltinTokenizer::Word, + ) + .apply() + .text(); + + println!("Parent: {parent:?}"); + println!("Left: {left:?}"); + println!("Right: {right:?}"); + println!(); + match dmp_result { + Some(r) => println!("diff-match-patch: {r:?}"), + None => println!("diff-match-patch: "), + } + println!("reconcile-text: {reconcile_result:?}"); + println!(); +} + +/// Demonstrates cases where diff-match-patch silently produces incorrect +/// output, while reconcile-text preserves both users' edits correctly. +/// +/// Run it with: +/// `cargo run --example compare-with-diff-match-patch` +fn main() { + // Example 1 + // Two users edit the same short phrase. Alice replaces "old(!)" with + // "new improved", Bob replaces "broken" with "working". These are + // independent changes to adjacent words. + // + // diff-match-patch has no common ancestor, so it diffs parent → left + // and applies the patch to right. The character-level patches overlap + // and produce garbled text ("impovind"). It reports success. + // + // reconcile-text sees both changes relative to the parent and merges + // them cleanly. + + println!("── Example 1: adjacent edits ──"); + try_merge( + "old(!) broken code", + "new improved code", + "old(!) working code", + ); + + // Example 2 + // Alice adds a sentence. Bob rewrites the surrounding text. Because + // diff-match-patch works without a common ancestor, Alice's entire + // sentence is silently lost. + + println!("── Example 2: sentence lost ──"); + // Alice adds a sentence in the middle of a paragraph. Bob rephrases + // the same paragraph. Because the patch context from Alice's edit no + // longer appears in Bob's version, diff-match-patch silently drops + // Alice's entire sentence. + // + // reconcile-text understands both edits relative to the common ancestor + // and keeps both. + try_merge( + "We used the existing parsing approach for processing. The output was saved to the database.", + "We used the existing parsing approach for processing. Always validate the schema! The output was saved to the database.", + "We adopted a brand new analysis pipeline for execution. The results were written to cloud storage.", + ); +} From 776571bc5e90aedf4c89852b8b0e3c3e6278291f Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Tue, 10 Mar 2026 20:34:07 +0000 Subject: [PATCH 03/61] Return result instead of panicking --- src/wasm.rs | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/wasm.rs b/src/wasm.rs index 959f1a6..809bf79 100644 --- a/src/wasm.rs +++ b/src/wasm.rs @@ -101,21 +101,22 @@ pub fn diff(parent: &str, changed: &TextWithCursors, tokenizer: BuiltinTokenizer /// Returns a JS error if the diff format is invalid or references ranges /// exceeding the original text length. #[wasm_bindgen(js_name = undiff)] -#[must_use] -pub fn undiff(parent: &str, diff: Vec, tokenizer: BuiltinTokenizer) -> String { +pub fn undiff( + parent: &str, + diff: Vec, + tokenizer: BuiltinTokenizer, +) -> Result { set_panic_hook(); - match EditedText::from_diff( - parent, - diff.into_iter() - .map(std::convert::TryInto::try_into) - .collect::>() - .expect("Invalid diff format"), - &*tokenizer, - ) { - Ok(edited_text) => edited_text.apply().text(), - Err(e) => panic!("{}", e), - } + let parsed_diff: Vec<_> = diff + .into_iter() + .map(std::convert::TryInto::try_into) + .collect::>() + .map_err(|e: crate::types::number_or_text::DeserialisationError| -> JsValue { e.into() })?; + + EditedText::from_diff(parent, parsed_diff, &*tokenizer) + .map(|edited_text| edited_text.apply().text()) + .map_err(|e| JsValue::from_str(&e.to_string())) } fn set_panic_hook() { From 3abc45cb867ba85400e324e09c32b2c805905daa Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Tue, 10 Mar 2026 20:38:07 +0000 Subject: [PATCH 04/61] Add wasm leak detector & fix leak --- reconcile-js/src/index.test.ts | 12 +++++ reconcile-js/src/index.ts | 18 ++++++-- reconcile-js/src/wasm-leak-detector.ts | 63 ++++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 5 deletions(-) create mode 100644 reconcile-js/src/wasm-leak-detector.ts diff --git a/reconcile-js/src/index.test.ts b/reconcile-js/src/index.test.ts index 1a4394f..0de924c 100644 --- a/reconcile-js/src/index.test.ts +++ b/reconcile-js/src/index.test.ts @@ -1,10 +1,22 @@ import { reconcile, reconcileWithHistory, diff, undiff } from './index'; +import { installWasmLeakDetector, checkForWasmLeaks } from './wasm-leak-detector'; import * as fs from 'fs'; import * as path from 'path'; import { fileURLToPath } from 'url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); +installWasmLeakDetector(); + +afterEach(() => { + const leaks = checkForWasmLeaks(); + if (leaks.length > 0) { + throw new Error( + `WASM memory leak: ${leaks.length} object(s) not freed:\n ${leaks.join('\n ')}` + ); + } +}); + describe('reconcile', () => { it('call reconcile without cursors', () => { expect(reconcile('Hello', 'Hello world', 'Hi world').text).toEqual('Hi world'); diff --git a/reconcile-js/src/index.ts b/reconcile-js/src/index.ts index 3fa998c..006e5d1 100644 --- a/reconcile-js/src/index.ts +++ b/reconcile-js/src/index.ts @@ -325,9 +325,15 @@ function toWasmCursorPosition({ id, position }: CursorPosition): wasmCursorPosit } function toTextWithCursors(textWithCursor: wasmTextWithCursors): TextWithCursors { + const wasmCursors = textWithCursor.cursors(); + const cursors = wasmCursors.map(toCursorPosition); + for (const cursor of wasmCursors) { + cursor.free(); + } + return { text: textWithCursor.text(), - cursors: textWithCursor.cursors().map(toCursorPosition), + cursors, }; } @@ -338,9 +344,11 @@ function toCursorPosition(cursor: wasmCursorPosition): CursorPosition { }; } -function toSpanWithHistory(textWithHistory: wasmSpanWithHistory): SpanWithHistory { - return { - text: textWithHistory.text(), - history: textWithHistory.history(), +function toSpanWithHistory(span: wasmSpanWithHistory): SpanWithHistory { + const result = { + text: span.text(), + history: span.history(), }; + span.free(); + return result; } diff --git a/reconcile-js/src/wasm-leak-detector.ts b/reconcile-js/src/wasm-leak-detector.ts new file mode 100644 index 0000000..a8bcec4 --- /dev/null +++ b/reconcile-js/src/wasm-leak-detector.ts @@ -0,0 +1,63 @@ +/** + * Test utility for detecting WASM memory leaks. + * + * wasm-bindgen registers every JS-side object with a `FinalizationRegistry`. + * This detector patches `FinalizationRegistry.prototype.register` to collect + * references to all WASM objects. After each test, {@link checkForWasmLeaks} + * inspects `__wbg_ptr` on every tracked object - a non-zero pointer means + * `.free()` was never called, i.e. a leak. + * + * Install once (before any WASM calls) and call {@link checkForWasmLeaks} + * in an `afterEach` hook. + */ + +let trackedObjects: object[] = []; +let originalRegister: Function | null = null; + +interface WasmBindgenObject { + __wbg_ptr: number; + constructor: { name?: string }; +} + +function isWasmBindgenObject(target: unknown): target is WasmBindgenObject { + return ( + target !== null && + typeof target === 'object' && + '__wbg_ptr' in (target as Record) + ); +} + +/** + * Patches `FinalizationRegistry.prototype.register` to track all wasm-bindgen + * objects. Safe to call multiple times (idempotent). + */ +export function installWasmLeakDetector(): void { + if (originalRegister) return; + + originalRegister = FinalizationRegistry.prototype.register; + + FinalizationRegistry.prototype.register = function ( + target: object, + heldValue: unknown, + unregisterToken?: object + ) { + if (isWasmBindgenObject(target)) { + trackedObjects.push(target); + } + return originalRegister!.call(this, target, heldValue, unregisterToken); + }; +} + +/** + * Returns any tracked WASM objects whose `__wbg_ptr` is still non-zero + * (i.e. `.free()` was never called). Clears the tracked set afterwards. + */ +export function checkForWasmLeaks(): string[] { + const leaks = trackedObjects + .filter(isWasmBindgenObject) + .filter((obj) => obj.__wbg_ptr !== 0) + .map((obj) => `${obj.constructor?.name ?? 'Unknown'} (ptr=${obj.__wbg_ptr})`); + + trackedObjects = []; + return leaks; +} From 6d1d5ca3bc6ba1712f84274de38ddc8bec246a46 Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Tue, 10 Mar 2026 20:38:42 +0000 Subject: [PATCH 05/61] Fix utf8 handling --- src/operation_transformation/edited_text.rs | 25 ++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/operation_transformation/edited_text.rs b/src/operation_transformation/edited_text.rs index bc30137..48c854a 100644 --- a/src/operation_transformation/edited_text.rs +++ b/src/operation_transformation/edited_text.rs @@ -329,7 +329,12 @@ where order, .. } => { - let deleted = self.text[*order..*order + *deleted_character_count].to_string(); + let deleted: String = self + .text + .chars() + .skip(*order) + .take(*deleted_character_count) + .collect(); match side { Side::Left => { history.push(SpanWithHistory::new(deleted, History::RemovedFromLeft)); @@ -592,6 +597,24 @@ mod tests { assert_eq!(serialized, expected); } + #[test] + fn test_apply_with_history_utf8() { + let parent = "こんにちは世界"; // "Hello World" in Japanese (7 chars, 21 bytes) + let left = "こんにちは宇宙"; // Changed 世界 to 宇宙 + let right = parent; + + let result = crate::reconcile( + parent, + &left.into(), + &right.into(), + &*BuiltinTokenizer::Word, + ); + + let history = result.apply_with_history(); + assert!(!history.is_empty()); + assert_eq!(result.apply().text(), "こんにちは宇宙"); + } + #[cfg(feature = "serde")] #[test] fn test_changes_serialization() { From 408ce5268fc2fbf73f214703c1476aef5f44bcb5 Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Tue, 10 Mar 2026 20:40:14 +0000 Subject: [PATCH 06/61] Mini perf optimisation --- src/operation_transformation/edited_text.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/operation_transformation/edited_text.rs b/src/operation_transformation/edited_text.rs index 48c854a..93779fb 100644 --- a/src/operation_transformation/edited_text.rs +++ b/src/operation_transformation/edited_text.rs @@ -435,6 +435,7 @@ where ) -> Result, DiffError> { let mut operations: Vec> = Vec::with_capacity(diff.len()); let mut order = 0; + let text_length = original_text.chars().count(); for item in diff { match item { @@ -443,7 +444,6 @@ where let length = usize::try_from(length).expect("length must fit in usize"); // Validate that the range doesn't exceed the original text - let text_length = original_text.chars().count(); if order + length > text_length { return Err(DiffError::LengthExceedsOriginal { position: order, @@ -466,7 +466,6 @@ where usize::try_from(-length).expect("negative length must fit in usize"); // Validate that the delete range doesn't exceed the original text - let text_length = original_text.chars().count(); if order + length > text_length { return Err(DiffError::LengthExceedsOriginal { position: order, From deffa195b3e1e2de341620029e3326d4bc810a75 Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Tue, 10 Mar 2026 20:42:09 +0000 Subject: [PATCH 07/61] Change style --- README.md | 34 +++++++++---------- docs/advanced-ts.md | 6 ++-- examples/compare-with-diff-match-patch.rs | 2 +- examples/website/src/index.html | 12 +++---- reconcile-js/src/index.ts | 19 +++++------ src/lib.rs | 2 +- src/operation_transformation.rs | 3 +- src/operation_transformation/edited_text.rs | 8 ++--- src/operation_transformation/operation.rs | 8 ++--- .../utils/cook_operations.rs | 2 +- src/tokenizer.rs | 2 +- src/tokenizer/character_tokenizer.rs | 2 +- src/tokenizer/line_tokenizer.rs | 2 +- src/tokenizer/token.rs | 10 +++--- src/tokenizer/word_tokenizer.rs | 2 +- src/types/cursor_position.rs | 2 +- src/types/history.rs | 2 +- src/types/side.rs | 2 +- src/types/span_with_history.rs | 2 +- .../find_longest_prefix_contained_within.rs | 2 +- src/utils/myers_diff.rs | 2 +- src/utils/string_builder.rs | 6 ++-- src/wasm.rs | 16 ++++----- 23 files changed, 72 insertions(+), 76 deletions(-) diff --git a/README.md b/README.md index bdb6c39..480087c 100644 --- a/README.md +++ b/README.md @@ -13,11 +13,11 @@ A Rust and TypeScript library for merging conflicting text edits without manual ## Key features -- **No conflict markers** — Clean, merged output without Git's `<<<<<<<` markers -- **Cursor tracking** — Automatically repositions cursors and selections throughout the merging process -- **Flexible tokenisation** — Word-level (default), character-level, line-level, or custom tokenisation strategies -- **Unicode support** — Full UTF-8 support with proper handling of complex scripts and grapheme clusters -- **Cross-platform** — Native Rust performance with WebAssembly bindings for JavaScript environments +- **No conflict markers** - Clean, merged output without Git's `<<<<<<<` markers +- **Cursor tracking** - Automatically repositions cursors and selections throughout the merging process +- **Flexible tokenisation** - Word-level (default), character-level, line-level, or custom tokenisation strategies +- **Unicode support** - Full UTF-8 support with proper handling of complex scripts and grapheme clusters +- **Cross-platform** - Native Rust performance with WebAssembly bindings for JavaScript environments ## Quick start @@ -93,12 +93,12 @@ Differential sync is implemented by [universal-sync](https://github.com/invisibl `reconcile-text` starts off similarly to `diff3` ([4], [5]) but adds automated conflict resolution. Given a **parent** document and two modified versions (`left` and `right`), the following happens: -1. **Tokenisation** — Input texts are split into meaningful units (words, characters, etc.) for granular merging -2. **Diff computation** — Myers' algorithm calculates differences between (parent ↔ left) and (parent ↔ right) -3. **Diff optimisation** — Operations are reordered and consolidated to maximise chained changes -4. **Operational Transformation** — Edits are woven together using OT principles, preserving all modifications and updating cursors +1. **Tokenisation** - Input texts are split into meaningful units (words, characters, etc.) for granular merging +2. **Diff computation** - Myers' algorithm calculates differences between (parent ↔ left) and (parent ↔ right) +3. **Diff optimisation** - Operations are reordered and consolidated to maximise chained changes +4. **Operational Transformation** - Edits are woven together using OT principles, preserving all modifications and updating cursors -Whilst the primary goal of `reconcile-text` isn't to implement OT, it provides an elegant way to merge Myers' diff outputs. (For a dedicated Rust OT implementation, see [operational-transform-rs](https://github.com/spebern/operational-transform-rs).) The same could be achieved with CRDTs, which many libraries implement well for text—see [Loro](https://github.com/loro-dev/loro/), [cola](https://github.com/nomad/cola), and [automerge](https://github.com/automerge/automerge) as excellent examples. +Whilst the primary goal of `reconcile-text` isn't to implement OT, it provides an elegant way to merge Myers' diff outputs. (For a dedicated Rust OT implementation, see [operational-transform-rs](https://github.com/spebern/operational-transform-rs).) The same could be achieved with CRDTs, which many libraries implement well for text (see [Loro](https://github.com/loro-dev/loro/), [cola](https://github.com/nomad/cola), and [automerge](https://github.com/automerge/automerge)). However, when only the end result of concurrent changes is observable, merge quality depends entirely on the quality of the underlying 2-way diffs. For instance, `move` operations cannot be supported because Myers' algorithm decomposes them into separate `insert` and `delete` operations, regardless of the merging algorithm used. @@ -114,17 +114,17 @@ Tools like `diff3` ([4]) and Git produce **conflict markers** (`<<<<<<<` / `==== The key differences from `reconcile-text`: -- **2-way vs 3-way** — diff-match-patch diffs two texts and applies the result as a patch. It has no concept of a common ancestor and cannot reason about "left changes" vs "right changes". `reconcile-text` performs true 3-way merging, understanding the intent behind each side's edits. +- **2-way vs 3-way** - diff-match-patch diffs two texts and applies the result as a patch. It has no concept of a common ancestor and cannot reason about "left changes" vs "right changes". `reconcile-text` performs true 3-way merging, understanding the intent behind each side's edits. -- **Character-level only** — Word-level and line-level diffs require encoding tokens as single Unicode characters before diffing ([7]). `reconcile-text` supports word, character, line, and custom tokenisation natively. +- **Character-level only** - Word-level and line-level diffs require encoding tokens as single Unicode characters before diffing ([7]). `reconcile-text` supports word, character, line, and custom tokenisation natively. -- **Patches can fail** — `patch_apply` returns a boolean array indicating success per patch; failed patches are silently dropped. In Differential Synchronisation, failures self-correct in the next cycle, but for one-shot merges edits can be lost. `reconcile-text` always produces a complete merged result. +- **Patches can fail** - `patch_apply` returns a boolean array indicating success per patch; failed patches are silently dropped. In Differential Synchronisation, failures self-correct in the next cycle, but for one-shot merges edits can be lost. `reconcile-text` always produces a complete merged result. -- **No cursor tracking or change provenance** — diff-match-patch does not reposition cursors or track which side made which edit. `reconcile-text` does both automatically. +- **No cursor tracking or change provenance** - diff-match-patch does not reposition cursors or track which side made which edit. `reconcile-text` does both automatically. See the [comparison example](examples/compare-with-diff-match-patch.rs) for concrete cases where diff-match-patch garbles adjacent edits and silently drops an entire sentence, while `reconcile-text` merges both users' changes correctly. -> **When to use diff-match-patch instead**: when you don't have a common ancestor—for example, synchronising texts that have diverged through an unknown sequence of edits. If you have a common ancestor (as in most version control and collaborative editing scenarios), `reconcile-text` produces more reliable results. +> **When to use diff-match-patch instead**: when you don't have a common ancestor, for example synchronising texts that have diverged through an unknown sequence of edits. If you have a common ancestor (as in most version control and collaborative editing scenarios), `reconcile-text` produces more reliable results. ### CRDTs (Yjs, Automerge, Loro, diamond-types) @@ -132,13 +132,13 @@ Conflict-free Replicated Data Types guarantee convergence by mathematical constr CRDTs capture every individual keystroke or operation, assigning each a unique identity. This makes them ideal when you control the complete editing infrastructure: the editor, the transport layer, and the storage format. They work peer-to-peer, handle arbitrary numbers of concurrent editors, and never lose an edit. -The trade-off is that CRDTs require **maintaining document state over time**—an operation log or internal data structure that grows with the document's edit history. You cannot simply hand a CRDT library three plain strings and get a merged result. This makes them unsuitable for Differential Synchronisation scenarios where you only observe the final state of each document, which is exactly the niche `reconcile-text` fills. +The trade-off is that CRDTs require **maintaining document state over time** - an operation log or internal data structure that grows with the document's edit history. You cannot simply hand a CRDT library three plain strings and get a merged result. This makes them unsuitable for Differential Synchronisation scenarios where you only observe the final state of each document, which is exactly the niche `reconcile-text` fills. > **When to use CRDTs instead**: if you control the complete editing stack and can capture every operation as it happens, CRDTs provide stronger convergence guarantees. They also support more than two concurrent editors naturally, whereas `reconcile-text` merges exactly two forks at a time (though merges can be chained). ### Operational Transformation (OT) -OT libraries like [ot.js](https://ot.js.org/) and [ShareJS](https://github.com/josephg/ShareJS) transform concurrent operations against each other so that applying them in any order produces the same result. Like CRDTs, they capture individual operations and require infrastructure to coordinate them—typically a central server that determines the canonical operation order. +OT libraries like [ot.js](https://ot.js.org/) and [ShareJS](https://github.com/josephg/ShareJS) transform concurrent operations against each other so that applying them in any order produces the same result. Like CRDTs, they capture individual operations and require infrastructure to coordinate them, typically a central server that determines the canonical operation order. `reconcile-text` borrows the *concept* of OT (transforming one side's edits against the other) but applies it to a different problem. Instead of transforming individual keystrokes in real time, it transforms the consolidated diff output of two complete edits. This means it doesn't need a server, doesn't need to capture operations as they happen, and works entirely offline. diff --git a/docs/advanced-ts.md b/docs/advanced-ts.md index 0480bd4..7e53bf5 100644 --- a/docs/advanced-ts.md +++ b/docs/advanced-ts.md @@ -42,9 +42,9 @@ console.log(result.history); /* `reconcile-text` offers different approaches to split text for merging: -- **Word tokeniser** (`"Word"`) — Splits on word boundaries (recommended for prose) -- **Character tokeniser** (`"Character"`) — Individual characters (fine-grained control) -- **Line tokeniser** (`"Line"`) — Line-by-line (similar to `git merge` or more precisely [`git merge-file`](https://git-scm.com/docs/git-merge-file)) +- **Word tokeniser** (`"Word"`) - Splits on word boundaries (recommended for prose) +- **Character tokeniser** (`"Character"`) - Individual characters (fine-grained control) +- **Line tokeniser** (`"Line"`) - Line-by-line (similar to `git merge` or more precisely [`git merge-file`](https://git-scm.com/docs/git-merge-file)) ## Cursor Tracking diff --git a/examples/compare-with-diff-match-patch.rs b/examples/compare-with-diff-match-patch.rs index f748e6f..4e1eede 100644 --- a/examples/compare-with-diff-match-patch.rs +++ b/examples/compare-with-diff-match-patch.rs @@ -47,7 +47,7 @@ fn try_merge(parent: &str, left: &str, right: &str) { } /// Demonstrates cases where diff-match-patch silently produces incorrect -/// output, while reconcile-text preserves both users' edits correctly. +/// output, while reconcile-text preserves both users' edits correctly /// /// Run it with: /// `cargo run --example compare-with-diff-match-patch` diff --git a/examples/website/src/index.html b/examples/website/src/index.html index 71d5cbc..488d99c 100644 --- a/examples/website/src/index.html +++ b/examples/website/src/index.html @@ -8,12 +8,12 @@ /> @@ -85,7 +85,7 @@ >documentation or try editing the text boxes below to see reconcile-text in - action. Use the tokenisation options to experiment with different approaches— + action. Use the tokenisation options to experiment with different approaches - the Rust library also supports custom tokenisers.

@@ -145,7 +145,7 @@
diff --git a/examples/website/src/index.ts b/examples/website/src/index.ts index 9ce99a5..01edfd8 100644 --- a/examples/website/src/index.ts +++ b/examples/website/src/index.ts @@ -10,7 +10,11 @@ const tokenizerRadios = document.querySelectorAll( 'input[name="tokenizer"]' ) as NodeListOf; -const sampleText = `The "reconcile-text" Rust library is embedded on this page as a WASM module and powers these text boxes. Experiment with changing the "Original", "First user's edit", and "Second user's edit" text boxes to see competing changes get merged in real-time within the "Merged result" box. Here, you will see color-coded tokens marking the origin of each token, including ones that got deleted. The result highly depends on the tokenisation strategy, for example, deciding how casing or whitespace is taken into account.`; +const sampleText = `The reconcile-text library is embedded on this page as a WASM module and powers these text boxes. Experiment with changing the "Original", "First user's edit", and "Second user's edit" text boxes to see competing changes get merged in real-time within the "Merged result" box. + +Here, you will see color-coded tokens marking the origin of each token, including ones that got deleted. The result highly depends on the tokenisation strategy which may be: +- Character-based +- Word-based`; let pendingUpdate: number | null = null; function scheduleUpdate(): void { @@ -52,10 +56,10 @@ function loadSample(): void { originalTextArea.value = sampleText; leftTextArea.value = sampleText.replace('color', 'colour') + - " Check out what's the most complex conflict you can come up with!"; - rightTextArea.value = sampleText - .replace(', for example,', ' such as') - .replace('WASM', 'WebAssembly'); + "\n- Line-based\n\nCheck out what's the most complex conflict you can come up with!"; + rightTextArea.value = + sampleText.replace(', for example,', ' such as').replace('WASM', 'WebAssembly') + + '\n- Or your custom tokeniser'; } function updateMergedText(): void { @@ -191,7 +195,7 @@ function createSelectionOverlay(isLeft: boolean, isSelection: boolean): HTMLSpan function getSelectedTokenizer(): BuiltinTokenizer { const selectedRadio = Array.from(tokenizerRadios).find((radio) => radio.checked); - return (selectedRadio?.value ?? 'Word') as BuiltinTokenizer; + return (selectedRadio?.value ?? 'Markdown') as BuiltinTokenizer; } function resizeTextAreas(): void { diff --git a/reconcile-js/src/index.ts b/reconcile-js/src/index.ts index 08edfc4..d00051c 100644 --- a/reconcile-js/src/index.ts +++ b/reconcile-js/src/index.ts @@ -12,7 +12,7 @@ import { import wasmBytes from 'reconcile-text/reconcile_text_bg.wasm'; // Define the enum values as const arrays to avoid duplication -const BUILTIN_TOKENIZERS = ['Character', 'Line', 'Word'] as const; +const BUILTIN_TOKENIZERS = ['Character', 'Line', 'Markdown', 'Word'] as const; const HISTORY_VALUES = [ 'Unchanged', 'AddedFromLeft', From 1a984427abdd5bd84da5705d62d72d11269d5d47 Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Wed, 11 Mar 2026 20:43:34 +0000 Subject: [PATCH 17/61] Minimise allocations --- src/operation_transformation/edited_text.rs | 177 ++++++++++++-------- src/operation_transformation/operation.rs | 148 ++++++++++------ 2 files changed, 211 insertions(+), 114 deletions(-) diff --git a/src/operation_transformation/edited_text.rs b/src/operation_transformation/edited_text.rs index 602ae74..2a98259 100644 --- a/src/operation_transformation/edited_text.rs +++ b/src/operation_transformation/edited_text.rs @@ -1,10 +1,10 @@ -use std::{fmt::Debug, vec}; +use std::fmt::Debug; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use crate::{ - BuiltinTokenizer, CursorPosition, TextWithCursors, + BuiltinTokenizer, CursorPosition, TextWithCursors, Token, operation_transformation::{ DiffError, Operation, utils::{cook_operations::cook_operations, elongate_operations::elongate_operations}, @@ -55,6 +55,7 @@ where { /// Create an `EditedText` from the given original and updated strings /// using the provided tokenizer + #[must_use] pub fn from_strings_with_tokenizer( original: &'a str, updated: &TextWithCursors, @@ -134,24 +135,21 @@ where let mut last_right_op = None; loop { - let (side, operation, mut last_other_op) = - match (maybe_left_op.clone(), maybe_right_op.clone()) { - (Some(left_op), Some(right_op)) => { - if left_op - .get_sort_key(seen_left_length) - .partial_cmp(&right_op.get_sort_key(seen_right_length)) - == Some(std::cmp::Ordering::Less) - { - (Side::Left, left_op, last_right_op.clone()) - } else { - (Side::Right, right_op, last_left_op.clone()) - } + let (side, operation) = match (maybe_left_op.as_ref(), maybe_right_op.as_ref()) { + (Some(left_op), Some(right_op)) => { + if left_op.cmp_priority(seen_left_length, right_op, seen_right_length) + == std::cmp::Ordering::Less + { + (Side::Left, maybe_left_op.take().unwrap()) + } else { + (Side::Right, maybe_right_op.take().unwrap()) } + } - (Some(left_op), None) => (Side::Left, left_op, last_right_op.clone()), - (None, Some(right_op)) => (Side::Right, right_op, last_left_op.clone()), - (None, None) => break, - }; + (Some(_), None) => (Side::Left, maybe_left_op.take().unwrap()), + (None, Some(_)) => (Side::Right, maybe_right_op.take().unwrap()), + (None, None) => break, + }; let is_advancing_operation = matches!( operation, @@ -161,7 +159,7 @@ where let original_length = operation.len(); let (side, result) = match side { Side::Left => { - let result = operation.merge_operations(&mut last_other_op); + let result = operation.merge_operations(last_right_op.as_ref()); if let ref op @ (Operation::Insert { .. } | Operation::Equal { .. }) = result { let merged_length_signed = isize::try_from(merged_length) @@ -195,7 +193,7 @@ where (Side::Left, result) } Side::Right => { - let result = operation.merge_operations(&mut last_other_op); + let result = operation.merge_operations(last_left_op.as_ref()); if let ref op @ (Operation::Insert { .. } | Operation::Equal { .. }) = result { let merged_length_signed = isize::try_from(merged_length) @@ -304,6 +302,7 @@ where /// ``` #[must_use] pub fn apply_with_history(&self) -> Vec { + let chars: Vec = self.text.chars().collect(); let mut builder: StringBuilder<'_> = StringBuilder::new(self.text); let mut history = Vec::with_capacity(self.operations.len()); @@ -315,34 +314,26 @@ where Operation::Equal { .. } => { history.push(SpanWithHistory::new(builder.take(), History::Unchanged)); } - Operation::Insert { .. } => match side { - Side::Left => { - history.push(SpanWithHistory::new(builder.take(), History::AddedFromLeft)); - } - Side::Right => history.push(SpanWithHistory::new( - builder.take(), - History::AddedFromRight, - )), - }, + Operation::Insert { .. } => { + let h = match side { + Side::Left => History::AddedFromLeft, + Side::Right => History::AddedFromRight, + }; + history.push(SpanWithHistory::new(builder.take(), h)); + } Operation::Delete { deleted_character_count, order, .. } => { - let deleted: String = self - .text - .chars() - .skip(*order) - .take(*deleted_character_count) + let deleted: String = chars[*order..*order + *deleted_character_count] + .iter() .collect(); - match side { - Side::Left => { - history.push(SpanWithHistory::new(deleted, History::RemovedFromLeft)); - } - Side::Right => { - history.push(SpanWithHistory::new(deleted, History::RemovedFromRight)); - } - } + let h = match side { + Side::Left => History::RemovedFromLeft, + Side::Right => History::RemovedFromRight, + }; + history.push(SpanWithHistory::new(deleted, h)); } } } @@ -350,6 +341,56 @@ where history } + /// Apply the operations and return both the merged text with cursors and + /// the provenance history in a single pass + #[must_use] + pub fn apply_with_all(&self) -> (TextWithCursors, Vec) { + let chars: Vec = self.text.chars().collect(); + let mut builder: StringBuilder<'_> = StringBuilder::new(self.text); + let mut history = Vec::with_capacity(self.operations.len()); + let mut full_text = String::new(); + + for (operation, side) in self.operations.iter().zip(self.operation_sides.iter()) { + builder = operation.apply(builder); + + match operation { + Operation::Equal { .. } => { + let span = builder.take(); + full_text.push_str(&span); + history.push(SpanWithHistory::new(span, History::Unchanged)); + } + Operation::Insert { .. } => { + let span = builder.take(); + full_text.push_str(&span); + let h = match side { + Side::Left => History::AddedFromLeft, + Side::Right => History::AddedFromRight, + }; + history.push(SpanWithHistory::new(span, h)); + } + Operation::Delete { + deleted_character_count, + order, + .. + } => { + let deleted: String = chars[*order..*order + *deleted_character_count] + .iter() + .collect(); + let h = match side { + Side::Left => History::RemovedFromLeft, + Side::Right => History::RemovedFromRight, + }; + history.push(SpanWithHistory::new(deleted, h)); + } + } + } + + ( + TextWithCursors::new(full_text, self.cursors.clone()), + history, + ) + } + /// Convert the `EditedText` into a terse representation ready for /// serialization. The result omits cursor positions and the original text. /// This is useful for sending text diffs over the network if there's a @@ -358,11 +399,11 @@ where /// Inserts are strings, deletes are negative integers (character count), /// and retained spans are positive integers (character count). /// - /// # Panics + /// # Errors /// - /// Panics if there's an integer overflow in i64. - #[must_use] - pub fn to_diff(&self) -> Vec { + /// Returns `DiffError::IntegerOverflow` if a character count exceeds + /// `i64::MAX`. + pub fn to_diff(&self) -> Result, DiffError> { let mut result: Vec = Vec::with_capacity(self.operations.len()); let mut previous_equal: Option = None; @@ -378,16 +419,14 @@ where Operation::Insert { text, .. } => { if let Some(prev_length) = previous_equal { - result.push(NumberOrText::Number( - i64::try_from(prev_length).expect("prev_length must fit in i64"), - )); + result + .push(NumberOrText::Number(i64::try_from(prev_length).map_err( + |_| DiffError::IntegerOverflow { value: prev_length }, + )?)); previous_equal = None; } - let text: String = text - .iter() - .map(super::super::tokenizer::token::Token::original) - .collect(); + let text: String = text.iter().map(Token::original).collect(); result.push(NumberOrText::Text(text)); } @@ -396,26 +435,31 @@ where .. } => { if let Some(prev_length) = previous_equal { - result.push(NumberOrText::Number( - i64::try_from(prev_length).expect("prev_length must fit in i64"), - )); + result + .push(NumberOrText::Number(i64::try_from(prev_length).map_err( + |_| DiffError::IntegerOverflow { value: prev_length }, + )?)); previous_equal = None; } - let count = i64::try_from(*deleted_character_count) - .expect("deleted_character_count must fit in i64"); + let count = i64::try_from(*deleted_character_count).map_err(|_| { + DiffError::IntegerOverflow { + value: *deleted_character_count, + } + })?; result.push(NumberOrText::Number(-count)); } } } if let Some(prev_length) = previous_equal { - result.push(NumberOrText::Number( - i64::try_from(prev_length).expect("prev_length must fit in i64"), - )); + result + .push(NumberOrText::Number(i64::try_from(prev_length).map_err( + |_| DiffError::IntegerOverflow { value: prev_length }, + )?)); } - result + Ok(result) } /// Reconstruct an `EditedText` from a diff and the original text. @@ -435,7 +479,8 @@ where ) -> Result, DiffError> { let mut operations: Vec> = Vec::with_capacity(diff.len()); let mut order = 0; - let text_length = original_text.chars().count(); + let chars: Vec = original_text.chars().collect(); + let text_length = chars.len(); for item in diff { match item { @@ -453,7 +498,7 @@ where } let original_characters: String = - original_text.chars().skip(order).take(length).collect(); + chars[order..order + length].iter().collect(); let original_tokens = tokenizer(&original_characters); for token in original_tokens { @@ -590,7 +635,7 @@ mod tests { let original = "Merging text is hard!"; let changes = "Merging text is easy with reconcile!"; let result = EditedText::from_strings(original, &changes.into()); - let serialized = serde_yaml::to_string(&result.to_diff()).unwrap(); + let serialized = serde_yaml::to_string(&result.to_diff().unwrap()).unwrap(); let expected = concat!("- 15\n", "- -6\n", "- ' easy with reconcile!'\n",); assert_eq!(serialized, expected); @@ -622,7 +667,7 @@ mod tests { let edited_text = EditedText::from_strings(original, &updated.into()); - let changes = edited_text.to_diff(); + let changes = edited_text.to_diff().unwrap(); let deserialized_edited_text = EditedText::from_diff(original, changes, &*BuiltinTokenizer::Word).unwrap(); diff --git a/src/operation_transformation/operation.rs b/src/operation_transformation/operation.rs index 28409f7..9d06639 100644 --- a/src/operation_transformation/operation.rs +++ b/src/operation_transformation/operation.rs @@ -104,28 +104,55 @@ where } } - pub fn get_sort_key(&self, insertion_index: usize) -> (usize, usize, usize, String) { - ( - self.order(), - match self { - Operation::Delete { .. } => 1, - Operation::Insert { .. } => 2, - Operation::Equal { .. } => 3, - }, - insertion_index, - // Make sure that the ordering is deterministic regardless of which text - // is left or right. - match self { - Operation::Equal { length, .. } => length.to_string(), - Operation::Insert { text, .. } => { - text.iter().map(Token::original).collect::() - } + fn type_priority(&self) -> u8 { + match self { + Operation::Delete { .. } => 1, + Operation::Insert { .. } => 2, + Operation::Equal { .. } => 3, + } + } + + /// Compare two operations for processing order during merging. Uses + /// (order, type, `insertion_index`) with a deterministic content + /// tiebreaker that avoids allocating. + pub fn cmp_priority( + &self, + self_index: usize, + other: &Self, + other_index: usize, + ) -> std::cmp::Ordering { + self.order() + .cmp(&other.order()) + .then_with(|| self.type_priority().cmp(&other.type_priority())) + .then_with(|| self_index.cmp(&other_index)) + .then_with(|| self.deterministic_content_cmp(other)) + } + + /// Deterministic tiebreaker based on operation content, so that merge + /// results are identical regardless of which side is left vs right + fn deterministic_content_cmp(&self, other: &Self) -> std::cmp::Ordering { + match (self, other) { + (Operation::Insert { text: t1, .. }, Operation::Insert { text: t2, .. }) => { + let s1 = t1.iter().flat_map(|t| t.original().chars()); + let s2 = t2.iter().flat_map(|t| t.original().chars()); + s1.cmp(s2) + } + (Operation::Equal { length: l1, .. }, Operation::Equal { length: l2, .. }) => { + l1.cmp(l2) + } + ( Operation::Delete { - deleted_character_count, + deleted_character_count: c1, .. - } => deleted_character_count.to_string(), - }, - ) + }, + Operation::Delete { + deleted_character_count: c2, + .. + }, + ) => c1.cmp(c2), + // Different types are already ordered by type_priority + _ => std::cmp::Ordering::Equal, + } } /// Applies the operation to the given `StringBuilder`, returning the @@ -193,10 +220,9 @@ where } /// Adjusts this operation based on `previous_operation` from the other side - /// to avoid duplicating or conflicting changes. Updates - /// `previous_operation` in-place. + /// to avoid duplicating or conflicting changes #[allow(clippy::too_many_lines)] - pub fn merge_operations(self, previous_operation: &mut Option) -> Operation { + pub fn merge_operations(self, previous_operation: Option<&Self>) -> Operation { let operation = self; match (operation, previous_operation) { @@ -295,14 +321,36 @@ where } ( - ref operation @ Operation::Equal { ref order, .. }, + ref operation @ Operation::Equal { + ref order, + #[cfg(debug_assertions)] + ref text, + .. + }, Some(Operation::Equal { order: last_equal_order, length: last_equal_length, + #[cfg(debug_assertions)] + text: last_equal_text, .. }), ) => { if operation.len() == *last_equal_length && *order == *last_equal_order { + // Both sides retained the same span from the original text, + // so we deduplicate by zeroing one out. This is safe because + // both EditedTexts are derived from the same original, and + // matching (order, length) means they cover the same substring + #[cfg(debug_assertions)] + debug_assert_eq!( + text, last_equal_text, + "Equal operations with same order and length should have the same text, \ + but got {operation:?} vs {:?}", + Operation::::Equal { + order: *last_equal_order, + length: *last_equal_length, + text: last_equal_text.clone(), + }, + ); Operation::create_equal(*order, 0) } else { operation.clone() @@ -329,18 +377,20 @@ where .. } => { #[cfg(debug_assertions)] - write!( - f, - "", - text.as_ref() - .map(|text| format!("'{}'", text.replace('\n', "\\n"))) - .unwrap_or(format!("{length} characters")), - )?; + { + write!( + f, + "", + text.as_ref() + .map(|text| format!("'{}'", text.replace('\n', "\\n"))) + .unwrap_or(format!("{length} characters")), + ) + } #[cfg(not(debug_assertions))] - write!(f, "")?; - - Ok(()) + { + write!(f, "") + } } Operation::Insert { order, text, .. } => { write!( @@ -361,22 +411,24 @@ where .. } => { #[cfg(debug_assertions)] - write!( - f, - "", - deleted_text - .as_ref() - .map(|text| format!("'{}'", text.replace('\n', "\\n"))) - .unwrap_or(format!("{deleted_character_count} characters")), - )?; + { + write!( + f, + "", + deleted_text + .as_ref() + .map(|text| format!("'{}'", text.replace('\n', "\\n"))) + .unwrap_or(format!("{deleted_character_count} characters")), + ) + } #[cfg(not(debug_assertions))] - write!( - f, - "", - )?; - - Ok(()) + { + write!( + f, + "", + ) + } } } } From 7b81034625a13f9d36c9a25e6bc361966bc71164 Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Wed, 11 Mar 2026 20:43:41 +0000 Subject: [PATCH 18/61] Update tests --- ...kenizer__tests__with_snapshots-11.snap.new | 37 +++++++++++++++++++ ...down_tokenizer__tests__headings-2.snap.new | 25 +++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 src/tokenizer/snapshots/reconcile_text__tokenizer__line_tokenizer__tests__with_snapshots-11.snap.new create mode 100644 src/tokenizer/snapshots/reconcile_text__tokenizer__markdown_tokenizer__tests__headings-2.snap.new diff --git a/src/tokenizer/snapshots/reconcile_text__tokenizer__line_tokenizer__tests__with_snapshots-11.snap.new b/src/tokenizer/snapshots/reconcile_text__tokenizer__line_tokenizer__tests__with_snapshots-11.snap.new new file mode 100644 index 0000000..36f12f9 --- /dev/null +++ b/src/tokenizer/snapshots/reconcile_text__tokenizer__line_tokenizer__tests__with_snapshots-11.snap.new @@ -0,0 +1,37 @@ +--- +source: src/tokenizer/line_tokenizer.rs +assertion_line: 78 +expression: "line_tokenizer(\"Mixed\\r\\nand\\rbare\")" +--- +[ + Token { + normalized: "Mixed", + original: "Mixed", + is_left_joinable: true, + is_right_joinable: true, + }, + Token { + normalized: "\r\n", + original: "\r\n", + is_left_joinable: true, + is_right_joinable: true, + }, + Token { + normalized: "and", + original: "and", + is_left_joinable: true, + is_right_joinable: true, + }, + Token { + normalized: "\r", + original: "\r", + is_left_joinable: true, + is_right_joinable: true, + }, + Token { + normalized: "bare", + original: "bare", + is_left_joinable: true, + is_right_joinable: true, + }, +] diff --git a/src/tokenizer/snapshots/reconcile_text__tokenizer__markdown_tokenizer__tests__headings-2.snap.new b/src/tokenizer/snapshots/reconcile_text__tokenizer__markdown_tokenizer__tests__headings-2.snap.new new file mode 100644 index 0000000..0f35315 --- /dev/null +++ b/src/tokenizer/snapshots/reconcile_text__tokenizer__markdown_tokenizer__tests__headings-2.snap.new @@ -0,0 +1,25 @@ +--- +source: src/tokenizer/markdown_tokenizer.rs +assertion_line: 199 +expression: "markdown_tokenizer(\"## Sub heading\")" +--- +[ + Token { + normalized: "## Sub", + original: "## Sub", + is_left_joinable: false, + is_right_joinable: true, + }, + Token { + normalized: " heading", + original: " ", + is_left_joinable: true, + is_right_joinable: true, + }, + Token { + normalized: "heading", + original: "heading", + is_left_joinable: true, + is_right_joinable: true, + }, +] From 545be141d80f242d6ec107bc6fe6155c0ba89e54 Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Wed, 11 Mar 2026 20:48:44 +0000 Subject: [PATCH 19/61] Add Python bindings --- .gitignore | 3 + Cargo.toml | 2 +- README.md | 41 ++- docs/advanced-python.md | 88 ++++++ reconcile-js/package-lock.json | 214 +++++++++----- reconcile-python/.gitignore | 9 + reconcile-python/Cargo.lock | 208 +++++++++++++ reconcile-python/Cargo.toml | 16 + reconcile-python/pyproject.toml | 52 ++++ .../python/reconcile_text/__init__.py | 165 +++++++++++ .../python/reconcile_text/_native.pyi | 24 ++ .../python/reconcile_text/py.typed | 0 reconcile-python/src/lib.rs | 235 +++++++++++++++ reconcile-python/tests/test_reconcile.py | 132 +++++++++ reconcile-python/uv.lock | 279 ++++++++++++++++++ scripts/bump-version.sh | 5 + scripts/lint.sh | 7 + scripts/test.sh | 5 + 18 files changed, 1406 insertions(+), 79 deletions(-) create mode 100644 docs/advanced-python.md create mode 100644 reconcile-python/.gitignore create mode 100644 reconcile-python/Cargo.lock create mode 100644 reconcile-python/Cargo.toml create mode 100644 reconcile-python/pyproject.toml create mode 100644 reconcile-python/python/reconcile_text/__init__.py create mode 100644 reconcile-python/python/reconcile_text/_native.pyi create mode 100644 reconcile-python/python/reconcile_text/py.typed create mode 100644 reconcile-python/src/lib.rs create mode 100644 reconcile-python/tests/test_reconcile.py create mode 100644 reconcile-python/uv.lock diff --git a/.gitignore b/.gitignore index cac139a..0957a69 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,6 @@ node_modules # WebPack build output dist + +# Python virtual environment +.venv diff --git a/Cargo.toml b/Cargo.toml index 7b657d4..4b5e11c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ repository = "https://github.com/schmelczer/reconcile" homepage = "https://schmelczer.dev/reconcile" keywords = ["merge", "OT", "CRDT", "3-way", "diff"] categories = ["wasm", "text-processing", "text-editors", "algorithms", "data-structures"] -exclude = ["reconcile-js", ".*", "examples/website"] +exclude = ["reconcile-js", "reconcile-python", ".*", "examples/website"] [lib] crate-type = ["cdylib", "rlib"] diff --git a/README.md b/README.md index e035caf..8ee4dc7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # `reconcile-text`: conflict-free 3-way text merging -A Rust and TypeScript library for merging conflicting text edits without manual intervention. Unlike traditional 3-way merge tools that produce conflict markers, `reconcile-text` automatically resolves conflicts by applying both sets of changes (while updating cursor positions) using an algorithm inspired by Operational Transformation. +A Rust, TypeScript, and Python library for merging conflicting text edits without manual intervention. Unlike traditional 3-way merge tools that produce conflict markers, `reconcile-text` automatically resolves conflicts by applying both sets of changes (while updating cursor positions) using an algorithm inspired by Operational Transformation. ## Try it @@ -10,6 +10,7 @@ A Rust and TypeScript library for merging conflicting text edits without manual - `cargo add reconcile-text` ([reconcile-text on crates.io][9]) - `npm install reconcile-text` ([reconcile-text on NPM][10]) +- `uv add reconcile-text` or `pip install reconcile-text` ([reconcile-text on PyPI][27]) ## Key features @@ -17,7 +18,7 @@ A Rust and TypeScript library for merging conflicting text edits without manual - **Cursor tracking** - Automatically repositions cursors and selections throughout the merging process - **Flexible tokenisation** - Word-level (default), character-level, line-level, or custom tokenisation strategies - **Unicode support** - Full UTF-8 support with proper handling of complex scripts and grapheme clusters -- **Cross-platform** - Native Rust performance with WebAssembly bindings for JavaScript environments +- **Cross-platform** - Native Rust performance with WebAssembly bindings for JavaScript and native bindings for Python ## Quick start @@ -79,6 +80,32 @@ console.log(result.text); // "Hi beautiful world" See the [example website source](examples/website/src/index.ts) for a more complex example, or the [advanced examples document](docs/advanced-ts.md). +### Python + +Install via uv or pip: + +```sh +uv add reconcile-text +# or: pip install reconcile-text +``` + +Then use it in your application: + +```python +from reconcile_text import reconcile + +# Start with the original text +parent = "Hello world" +# Two users edit simultaneously +left = "Hello beautiful world" +right = "Hi world" + +result = reconcile(parent, left, right) +print(result["text"]) # "Hi beautiful world" +``` + +See the [advanced Python examples](docs/advanced-python.md) for cursor tracking, change provenance, and compact diffs. + ## Motivation Collaborative editing presents the challenge of merging conflicting changes when multiple users edit documents simultaneously or asynchronously whilst offline. Traditional solutions like Conflict-free Replicated Data Types (CRDTs) or Operational Transformation (OT) work well when you control the complete editing infrastructure and can capture every individual operation ([1]). However, many workflows involve users editing with various tools, for example, Obsidian users editing Markdown files with various editors ranging from Vim to VS Code. @@ -150,6 +177,15 @@ Contributions are welcome! ### Environment +#### Python setup + +Install [uv](https://docs.astral.sh/uv/getting-started/installation/) and build the extension for development: + +```sh +cd reconcile-python +uv run maturin develop +``` + #### Node.js setup 1. Install [nvm][25]: @@ -210,3 +246,4 @@ Install [rustup][26]: [24]: https://github.com/josephg/ShareJS [25]: https://github.com/nvm-sh/nvm [26]: https://rustup.rs +[27]: https://pypi.org/project/reconcile-text/ diff --git a/docs/advanced-python.md b/docs/advanced-python.md new file mode 100644 index 0000000..1e21907 --- /dev/null +++ b/docs/advanced-python.md @@ -0,0 +1,88 @@ +# Advanced Usage (Python) + +## Edit Provenance + +Track which changes came from where using `reconcile_with_history`: + +```python +from reconcile_text import reconcile_with_history + +result = reconcile_with_history( + "Hello world", + "Hello beautiful world", + "Hi world", +) + +print(result["text"]) # "Hi beautiful world" +print(result["history"]) # +# [ +# {"text": "Hello", "history": "RemovedFromRight"}, +# {"text": "Hi", "history": "AddedFromRight"}, +# {"text": " beautiful", "history": "AddedFromLeft"}, +# {"text": " ", "history": "Unchanged"}, +# {"text": "world", "history": "Unchanged"}, +# ] +``` + +## Tokenization Strategies + +`reconcile-text` offers different approaches to split text for merging: + +- **Word tokenizer** (`"Word"`) - Splits on word boundaries (recommended for prose) +- **Character tokenizer** (`"Character"`) - Individual characters (fine-grained control) +- **Line tokenizer** (`"Line"`) - Line-by-line (similar to `git merge` or more precisely [`git merge-file`](https://git-scm.com/docs/git-merge-file)) +- **Markdown tokenizer** (`"Markdown"`) - Splits on Markdown structural boundaries (headings, list items, paragraphs) + +```python +from reconcile_text import reconcile + +result = reconcile("abc", "axc", "abyc", "Character") +print(result["text"]) # "axyc" +``` + +## Cursor Tracking + +`reconcile-text` automatically tracks cursor positions through merges, which is useful for collaborative editors. Selections can be tracked by providing them as a pair of cursors. + +```python +from reconcile_text import reconcile + +result = reconcile( + "Hello world", + { + "text": "Hello beautiful world", + "cursors": [{"id": 1, "position": 6}], # After "Hello " + }, + { + "text": "Hi world", + "cursors": [{"id": 2, "position": 0}], # At the beginning + }, +) + +# Result: "Hi beautiful world" with repositioned cursors +print(result["text"]) # "Hi beautiful world" +print(result["cursors"]) # [{"id": 2, "position": 0}, {"id": 1, "position": 3}] +``` + +> The `cursors` list is sorted by character position (not IDs). + +## Compact Diffs + +Generate and apply compact diff representations: + +```python +from reconcile_text import diff, undiff + +original = "Hello world" +changed = "Hello beautiful world" + +# Generate a compact diff +d = diff(original, changed) +print(d) # [5, ' beautiful world'] + +# Reconstruct the changed text from the diff +reconstructed = undiff(original, d) +assert reconstructed == changed +``` + +Diff entries are positive integers (retain N characters), negative integers (delete N characters), and strings (insert text). diff --git a/reconcile-js/package-lock.json b/reconcile-js/package-lock.json index a2fbafa..cbb37dd 100644 --- a/reconcile-js/package-lock.json +++ b/reconcile-js/package-lock.json @@ -852,7 +852,9 @@ } }, "node_modules/@jridgewell/source-map": { - "version": "0.3.10", + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", "dev": true, "license": "MIT", "dependencies": { @@ -1248,7 +1250,9 @@ "license": "Apache-2.0" }, "node_modules/acorn": { - "version": "8.15.0", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", "bin": { @@ -1258,8 +1262,23 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "acorn": "^8.14.0" + } + }, "node_modules/ajv": { - "version": "8.17.1", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", "dependencies": { @@ -1275,6 +1294,8 @@ }, "node_modules/ajv-formats": { "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "dev": true, "license": "MIT", "dependencies": { @@ -1291,6 +1312,8 @@ }, "node_modules/ajv-keywords": { "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "dev": true, "license": "MIT", "dependencies": { @@ -1457,6 +1480,19 @@ "dev": true, "license": "MIT" }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", + "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/brace-expansion": { "version": "1.1.12", "dev": true, @@ -1478,7 +1514,9 @@ } }, "node_modules/browserslist": { - "version": "4.25.1", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "dev": true, "funding": [ { @@ -1496,10 +1534,11 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001726", - "electron-to-chromium": "^1.5.173", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" @@ -1549,7 +1588,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001726", + "version": "1.0.30001777", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001777.tgz", + "integrity": "sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ==", "dev": true, "funding": [ { @@ -1744,6 +1785,8 @@ }, "node_modules/commander": { "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true, "license": "MIT" }, @@ -1835,7 +1878,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.179", + "version": "1.5.307", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.307.tgz", + "integrity": "sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==", "dev": true, "license": "ISC" }, @@ -1856,12 +1901,14 @@ "license": "MIT" }, "node_modules/enhanced-resolve": { - "version": "5.18.2", + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz", + "integrity": "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==", "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" + "tapable": "^2.3.0" }, "engines": { "node": ">=10.13.0" @@ -1887,7 +1934,9 @@ } }, "node_modules/es-module-lexer": { - "version": "1.7.0", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", "dev": true, "license": "MIT" }, @@ -2014,6 +2063,8 @@ }, "node_modules/fast-deep-equal": { "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true, "license": "MIT" }, @@ -2023,7 +2074,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.0.6", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", "dev": true, "funding": [ { @@ -2070,7 +2123,9 @@ } }, "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.6", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", "dev": true, "license": "ISC", "dependencies": { @@ -2206,6 +2261,8 @@ }, "node_modules/glob-to-regexp": { "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", "dev": true, "license": "BSD-2-Clause" }, @@ -2218,11 +2275,13 @@ } }, "node_modules/glob/node_modules/minimatch": { - "version": "9.0.5", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -3068,6 +3127,8 @@ }, "node_modules/jest-worker": { "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "dev": true, "license": "MIT", "dependencies": { @@ -3114,6 +3175,8 @@ }, "node_modules/json-schema-traverse": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true, "license": "MIT" }, @@ -3150,11 +3213,17 @@ "license": "MIT" }, "node_modules/loader-runner": { - "version": "4.3.0", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", "dev": true, "license": "MIT", "engines": { "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/locate-path": { @@ -3264,7 +3333,9 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -3317,7 +3388,9 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.19", + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", "dev": true, "license": "MIT" }, @@ -3568,14 +3641,6 @@ ], "license": "MIT" }, - "node_modules/randombytes": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, "node_modules/react-is": { "version": "18.3.1", "dev": true, @@ -3606,6 +3671,8 @@ }, "node_modules/require-from-string": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true, "license": "MIT", "engines": { @@ -3650,27 +3717,10 @@ "node": ">=8" } }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/schema-utils": { - "version": "4.3.2", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "dev": true, "license": "MIT", "dependencies": { @@ -3695,14 +3745,6 @@ "semver": "bin/semver.js" } }, - "node_modules/serialize-javascript": { - "version": "6.0.2", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" - } - }, "node_modules/shallow-clone": { "version": "3.0.1", "dev": true, @@ -3756,6 +3798,8 @@ }, "node_modules/source-map-support": { "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, "license": "MIT", "dependencies": { @@ -3965,20 +4009,28 @@ } }, "node_modules/tapable": { - "version": "2.2.2", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", "dev": true, "license": "MIT", "engines": { "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/terser": { - "version": "5.43.1", + "version": "5.46.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz", + "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.14.0", + "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, @@ -3990,14 +4042,15 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.14", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.4.0.tgz", + "integrity": "sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==", "dev": true, "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", "schema-utils": "^4.3.0", - "serialize-javascript": "^6.0.2", "terser": "^5.31.1" }, "engines": { @@ -4256,7 +4309,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.3", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "dev": true, "funding": [ { @@ -4306,7 +4361,9 @@ } }, "node_modules/watchpack": { - "version": "2.4.4", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", + "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", "dev": true, "license": "MIT", "dependencies": { @@ -4318,34 +4375,37 @@ } }, "node_modules/webpack": { - "version": "5.99.9", + "version": "5.105.4", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.4.tgz", + "integrity": "sha512-jTywjboN9aHxFlToqb0K0Zs9SbBoW4zRUlGzI2tYNxVYcEi/IPpn+Xi4ye5jTLvX2YeLuic/IvxNot+Q1jMoOw==", "dev": true, "license": "MIT", "dependencies": { "@types/eslint-scope": "^3.7.7", - "@types/estree": "^1.0.6", + "@types/estree": "^1.0.8", "@types/json-schema": "^7.0.15", "@webassemblyjs/ast": "^1.14.1", "@webassemblyjs/wasm-edit": "^1.14.1", "@webassemblyjs/wasm-parser": "^1.14.1", - "acorn": "^8.14.0", - "browserslist": "^4.24.0", + "acorn": "^8.16.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.1", - "es-module-lexer": "^1.2.1", + "enhanced-resolve": "^5.20.0", + "es-module-lexer": "^2.0.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", + "loader-runner": "^4.3.1", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^4.3.2", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.11", - "watchpack": "^2.4.1", - "webpack-sources": "^3.2.3" + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.3.17", + "watchpack": "^2.5.1", + "webpack-sources": "^3.3.4" }, "bin": { "webpack": "bin/webpack.js" @@ -4426,7 +4486,9 @@ } }, "node_modules/webpack-sources": { - "version": "3.3.3", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.4.tgz", + "integrity": "sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==", "dev": true, "license": "MIT", "engines": { diff --git a/reconcile-python/.gitignore b/reconcile-python/.gitignore new file mode 100644 index 0000000..772a74a --- /dev/null +++ b/reconcile-python/.gitignore @@ -0,0 +1,9 @@ +.venv/ +.pytest_cache/ +.ruff_cache/ +__pycache__/ +*.egg-info/ +*.so +*.dylib +*.dSYM/ +dist/ diff --git a/reconcile-python/Cargo.lock b/reconcile-python/Cargo.lock new file mode 100644 index 0000000..ed40d8b --- /dev/null +++ b/reconcile-python/Cargo.lock @@ -0,0 +1,208 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "indoc" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] + +[[package]] +name = "libc" +version = "0.2.183" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pyo3" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5203598f366b11a02b13aa20cab591229ff0a89fd121a308a5df751d5fc9219" +dependencies = [ + "cfg-if", + "indoc", + "libc", + "memoffset", + "once_cell", + "portable-atomic", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99636d423fa2ca130fa5acde3059308006d46f98caac629418e53f7ebb1e9999" +dependencies = [ + "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78f9cf92ba9c409279bc3305b5409d90db2d2c22392d443a87df3a1adad59e33" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b999cb1a6ce21f9a6b147dcf1be9ffedf02e0043aec74dc390f3007047cecd9" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "822ece1c7e1012745607d5cf0bcb2874769f0f7cb34c4cde03b9358eb9ef911a" +dependencies = [ + "heck", + "proc-macro2", + "pyo3-build-config", + "quote", + "syn", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "reconcile-text" +version = "0.8.0" +dependencies = [ + "thiserror", +] + +[[package]] +name = "reconcile-text-python" +version = "0.8.0" +dependencies = [ + "pyo3", + "reconcile-text", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-lexicon" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unindent" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" diff --git a/reconcile-python/Cargo.toml b/reconcile-python/Cargo.toml new file mode 100644 index 0000000..48ab072 --- /dev/null +++ b/reconcile-python/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "reconcile-text-python" +version = "0.8.0" +edition = "2024" +rust-version = "1.85" +authors = ["Andras Schmelczer "] +license = "MIT" +publish = false + +[lib] +name = "_native" +crate-type = ["cdylib"] + +[dependencies] +reconcile-text = { path = ".." } +pyo3 = { version = "0.24", features = ["extension-module"] } diff --git a/reconcile-python/pyproject.toml b/reconcile-python/pyproject.toml new file mode 100644 index 0000000..92f185d --- /dev/null +++ b/reconcile-python/pyproject.toml @@ -0,0 +1,52 @@ +[build-system] +requires = ["maturin>=1.0,<2.0"] +build-backend = "maturin" + +[project] +name = "reconcile-text" +version = "0.8.0" +description = "Intelligent 3-way text merging with automated conflict resolution" +readme = "../README.md" +license = { text = "MIT" } +authors = [{ name = "Andras Schmelczer", email = "andras@schmelczer.dev" }] +requires-python = ">=3.9" +classifiers = [ + "Programming Language :: Rust", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Typing :: Typed", +] +keywords = ["merge", "OT", "CRDT", "3-way", "diff", "text"] + +[dependency-groups] +dev = ["maturin>=1.0,<2.0", "pytest>=8", "ruff>=0.15", "pyright>=1"] + +[project.urls] +Homepage = "https://schmelczer.dev/reconcile" +Repository = "https://github.com/schmelczer/reconcile" +Issues = "https://github.com/schmelczer/reconcile/issues" + +[tool.maturin] +manifest-path = "Cargo.toml" +module-name = "reconcile_text._native" +python-source = "python" + +[tool.pytest.ini_options] +testpaths = ["tests"] + +[tool.ruff] +target-version = "py39" +line-length = 100 + +[tool.ruff.lint] +select = ["E", "F", "W", "I", "UP", "B", "SIM", "RUF"] + +[tool.ruff.lint.isort] +known-first-party = ["reconcile_text"] + +[tool.pyright] +pythonVersion = "3.9" +typeCheckingMode = "strict" +include = ["python", "tests"] diff --git a/reconcile-python/python/reconcile_text/__init__.py b/reconcile-python/python/reconcile_text/__init__.py new file mode 100644 index 0000000..fca9c56 --- /dev/null +++ b/reconcile-python/python/reconcile_text/__init__.py @@ -0,0 +1,165 @@ +"""Intelligent 3-way text merging with automated conflict resolution.""" + +from __future__ import annotations + +from typing import Literal, TypedDict, Union + +from reconcile_text._native import diff as _diff +from reconcile_text._native import reconcile as _reconcile +from reconcile_text._native import reconcile_with_history as _reconcile_with_history +from reconcile_text._native import undiff as _undiff + +BuiltinTokenizer = Literal["Character", "Line", "Markdown", "Word"] +"""Tokenization strategy for text merging.""" + +History = Literal[ + "Unchanged", "AddedFromLeft", "AddedFromRight", "RemovedFromLeft", "RemovedFromRight" +] +"""Provenance label for each span in a merge result.""" + + +class CursorPosition(TypedDict): + """A cursor position within a text document.""" + + id: int + """Unique identifier for the cursor.""" + position: int + """Character position in the text (0-based).""" + + +class TextWithCursors(TypedDict): + """A text document with associated cursor positions.""" + + text: str + """The document content.""" + cursors: list[CursorPosition] + """Cursor positions within the text.""" + + +class SpanWithHistory(TypedDict): + """A text span annotated with its origin in a merge result.""" + + text: str + """The text content of this span.""" + history: History + """Which source this span came from.""" + + +class TextWithCursorsAndHistory(TypedDict): + """A merged text document with cursor positions and change provenance.""" + + text: str + """The merged document content.""" + cursors: list[CursorPosition] + """Repositioned cursor positions.""" + history: list[SpanWithHistory] + """Provenance information for each text span.""" + + +TextInput = Union[str, TextWithCursors] +"""Input type for text arguments: either a plain string or a dict with text and cursors.""" + + +def reconcile( + parent: str, + left: TextInput, + right: TextInput, + tokenizer: BuiltinTokenizer = "Word", +) -> TextWithCursors: + """Merge three versions of text using conflict-free resolution. + + Takes a parent text and two concurrent edits (left and right), returning + the merged result with automatically repositioned cursors. + + Args: + parent: The original text that both sides diverged from. + left: The left edit (string or dict with "text" and "cursors"). + right: The right edit (string or dict with "text" and "cursors"). + tokenizer: Tokenization strategy. Defaults to "Word". + + Returns: + A dict with "text" (merged string) and "cursors" (repositioned cursor list). + """ + return _reconcile(parent, left, right, tokenizer) # type: ignore[return-value] + + +def reconcile_with_history( + parent: str, + left: TextInput, + right: TextInput, + tokenizer: BuiltinTokenizer = "Word", +) -> TextWithCursorsAndHistory: + """Merge three versions of text and return provenance history. + + Like `reconcile`, but also returns which source each text span came from. + + Args: + parent: The original text that both sides diverged from. + left: The left edit (string or dict with "text" and "cursors"). + right: The right edit (string or dict with "text" and "cursors"). + tokenizer: Tokenization strategy. Defaults to "Word". + + Returns: + A dict with "text", "cursors", and "history". + """ + return _reconcile_with_history(parent, left, right, tokenizer) # type: ignore[return-value] + + +def diff( + parent: str, + changed: TextInput, + tokenizer: BuiltinTokenizer = "Word", +) -> list[int | str]: + """Generate a compact diff between two texts. + + Returns retain counts (positive ints), delete counts (negative ints), + and inserted strings. + + Args: + parent: The original text. + changed: The modified text (string or dict with "text" and "cursors"). + tokenizer: Tokenization strategy. Defaults to "Word". + + Returns: + A list of ints and strings representing the diff. + + Raises: + ValueError: If the diff computation overflows. + """ + return _diff(parent, changed, tokenizer) # type: ignore[return-value] + + +def undiff( + parent: str, + diff: list[int | str], + tokenizer: BuiltinTokenizer = "Word", +) -> str: + """Apply a compact diff to reconstruct the changed text. + + Args: + parent: The original text. + diff: A list of ints and strings (as produced by `diff`). + tokenizer: Tokenization strategy. Defaults to "Word". + + Returns: + The reconstructed text. + + Raises: + ValueError: If the diff format is invalid. + """ + return _undiff(parent, diff, tokenizer) + + +__all__ = [ + "BuiltinTokenizer", + "CursorPosition", + "History", + "SpanWithHistory", + "TextInput", + "TextWithCursors", + "TextWithCursorsAndHistory", + "diff", + "reconcile", + "reconcile_with_history", + "undiff", +] diff --git a/reconcile-python/python/reconcile_text/_native.pyi b/reconcile-python/python/reconcile_text/_native.pyi new file mode 100644 index 0000000..897513d --- /dev/null +++ b/reconcile-python/python/reconcile_text/_native.pyi @@ -0,0 +1,24 @@ +from typing import Any + +def reconcile( + parent: str, + left: Any, + right: Any, + tokenizer: str = "Word", +) -> dict[str, Any]: ... +def reconcile_with_history( + parent: str, + left: Any, + right: Any, + tokenizer: str = "Word", +) -> dict[str, Any]: ... +def diff( + parent: str, + changed: Any, + tokenizer: str = "Word", +) -> list[int | str]: ... +def undiff( + parent: str, + diff: list[int | str], + tokenizer: str = "Word", +) -> str: ... diff --git a/reconcile-python/python/reconcile_text/py.typed b/reconcile-python/python/reconcile_text/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/reconcile-python/src/lib.rs b/reconcile-python/src/lib.rs new file mode 100644 index 0000000..809e0b0 --- /dev/null +++ b/reconcile-python/src/lib.rs @@ -0,0 +1,235 @@ +use pyo3::prelude::*; +use pyo3::types::{PyDict, PyList}; +use reconcile_text::{ + BuiltinTokenizer, CursorPosition, EditedText, NumberOrText, TextWithCursors, +}; + +fn parse_tokenizer(tokenizer: &str) -> PyResult { + match tokenizer { + "Character" => Ok(BuiltinTokenizer::Character), + "Line" => Ok(BuiltinTokenizer::Line), + "Markdown" => Ok(BuiltinTokenizer::Markdown), + "Word" => Ok(BuiltinTokenizer::Word), + _ => Err(pyo3::exceptions::PyValueError::new_err(format!( + "Unknown tokenizer '{tokenizer}', expected Character, Line, Markdown, or Word" + ))), + } +} + +fn extract_text_with_cursors(input: &Bound<'_, PyAny>) -> PyResult { + if let Ok(text) = input.extract::() { + return Ok(TextWithCursors::from(text)); + } + + let dict = input.downcast::()?; + + let text: String = dict + .get_item("text")? + .ok_or_else(|| pyo3::exceptions::PyKeyError::new_err("text"))? + .extract()?; + + let cursors = match dict.get_item("cursors")? { + Some(obj) if !obj.is_none() => { + let list = obj.downcast::()?; + let mut cursors = Vec::with_capacity(list.len()); + for item in list { + let cursor_dict = item.downcast::()?; + let id: usize = cursor_dict + .get_item("id")? + .ok_or_else(|| pyo3::exceptions::PyKeyError::new_err("id"))? + .extract()?; + let position: usize = cursor_dict + .get_item("position")? + .ok_or_else(|| pyo3::exceptions::PyKeyError::new_err("position"))? + .extract()?; + cursors.push(CursorPosition::new(id, position)); + } + cursors + } + _ => Vec::new(), + }; + + Ok(TextWithCursors::new(text, cursors)) +} + +fn text_with_cursors_to_dict<'py>( + py: Python<'py>, + twc: &TextWithCursors, +) -> PyResult> { + let dict = PyDict::new(py); + dict.set_item("text", twc.text())?; + + let cursors = PyList::new( + py, + twc.cursors().iter().map(|c| { + let d = PyDict::new(py); + d.set_item("id", c.id()).unwrap(); + d.set_item("position", c.char_index()).unwrap(); + d + }), + )?; + dict.set_item("cursors", cursors)?; + + Ok(dict) +} + +/// Merge three versions of text using conflict-free resolution. +/// +/// Takes a parent text and two concurrent edits (left and right), returning +/// the merged result with automatically repositioned cursors. +/// +/// Args: +/// parent: The original text that both sides diverged from. +/// left: The left edit, either a string or a dict with "text" and "cursors" keys. +/// right: The right edit, either a string or a dict with "text" and "cursors" keys. +/// tokenizer: Tokenization strategy - "Word" (default), "Character", "Line", or "Markdown". +/// +/// Returns: +/// A dict with "text" (merged string) and "cursors" (list of repositioned cursors). +#[pyfunction] +#[pyo3(signature = (parent, left, right, tokenizer = "Word"))] +fn reconcile<'py>( + py: Python<'py>, + parent: &str, + left: &Bound<'py, PyAny>, + right: &Bound<'py, PyAny>, + tokenizer: &str, +) -> PyResult> { + let tokenizer = parse_tokenizer(tokenizer)?; + let left = extract_text_with_cursors(left)?; + let right = extract_text_with_cursors(right)?; + + let result = reconcile_text::reconcile(parent, &left, &right, &*tokenizer).apply(); + text_with_cursors_to_dict(py, &result) +} + +/// Merge three versions of text and return provenance history. +/// +/// Like `reconcile`, but also returns which source each text span came from. +/// +/// Args: +/// parent: The original text that both sides diverged from. +/// left: The left edit, either a string or a dict with "text" and "cursors" keys. +/// right: The right edit, either a string or a dict with "text" and "cursors" keys. +/// tokenizer: Tokenization strategy - "Word" (default), "Character", "Line", or "Markdown". +/// +/// Returns: +/// A dict with "text", "cursors", and "history" (list of dicts with "text" and "history" keys). +#[pyfunction] +#[pyo3(signature = (parent, left, right, tokenizer = "Word"))] +fn reconcile_with_history<'py>( + py: Python<'py>, + parent: &str, + left: &Bound<'py, PyAny>, + right: &Bound<'py, PyAny>, + tokenizer: &str, +) -> PyResult> { + let tokenizer = parse_tokenizer(tokenizer)?; + let left = extract_text_with_cursors(left)?; + let right = extract_text_with_cursors(right)?; + + let reconciled = reconcile_text::reconcile(parent, &left, &right, &*tokenizer); + let (text_with_cursors, history_spans) = reconciled.apply_with_all(); + + let dict = text_with_cursors_to_dict(py, &text_with_cursors)?; + + let history = PyList::new( + py, + history_spans.iter().map(|span| { + let d = PyDict::new(py); + d.set_item("text", span.text()).unwrap(); + d.set_item("history", format!("{:?}", span.history())) + .unwrap(); + d + }), + )?; + dict.set_item("history", history)?; + + Ok(dict) +} + +/// Generate a compact diff between two texts. +/// +/// Returns a list of retain counts (positive ints), delete counts (negative ints), +/// and inserted strings. +/// +/// Args: +/// parent: The original text. +/// changed: The modified text, either a string or a dict with "text" and "cursors" keys. +/// tokenizer: Tokenization strategy - "Word" (default), "Character", "Line", or "Markdown". +/// +/// Returns: +/// A list of ints and strings representing the diff. +/// +/// Raises: +/// ValueError: If the diff computation overflows. +#[pyfunction] +#[pyo3(signature = (parent, changed, tokenizer = "Word"))] +fn diff<'py>( + py: Python<'py>, + parent: &str, + changed: &Bound<'py, PyAny>, + tokenizer: &str, +) -> PyResult> { + let tokenizer = parse_tokenizer(tokenizer)?; + let changed = extract_text_with_cursors(changed)?; + + let edited = EditedText::from_strings_with_tokenizer(parent, &changed, &*tokenizer); + let diff_result = edited + .to_diff() + .map_err(|e| pyo3::exceptions::PyValueError::new_err(e.to_string()))?; + + let list = PyList::empty(py); + for item in diff_result { + match item { + NumberOrText::Number(n) => list.append(n)?, + NumberOrText::Text(s) => list.append(s)?, + } + } + + Ok(list) +} + +/// Apply a compact diff to a parent text to reconstruct the changed version. +/// +/// Args: +/// parent: The original text. +/// diff: A list of ints and strings (as produced by `diff`). +/// tokenizer: Tokenization strategy - "Word" (default), "Character", "Line", or "Markdown". +/// +/// Returns: +/// The reconstructed text. +/// +/// Raises: +/// ValueError: If the diff format is invalid. +#[pyfunction] +#[pyo3(signature = (parent, diff, tokenizer = "Word"))] +fn undiff(parent: &str, diff: &Bound<'_, PyList>, tokenizer: &str) -> PyResult { + let tokenizer = parse_tokenizer(tokenizer)?; + + let mut parsed: Vec = Vec::with_capacity(diff.len()); + for item in diff { + if let Ok(n) = item.extract::() { + parsed.push(NumberOrText::Number(n)); + } else if let Ok(s) = item.extract::() { + parsed.push(NumberOrText::Text(s)); + } else { + return Err(pyo3::exceptions::PyTypeError::new_err( + "Diff items must be int or str", + )); + } + } + + EditedText::from_diff(parent, parsed, &*tokenizer) + .map(|edited| edited.apply().text()) + .map_err(|e| pyo3::exceptions::PyValueError::new_err(e.to_string())) +} + +#[pymodule] +fn _native(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_function(wrap_pyfunction!(reconcile, m)?)?; + m.add_function(wrap_pyfunction!(reconcile_with_history, m)?)?; + m.add_function(wrap_pyfunction!(diff, m)?)?; + m.add_function(wrap_pyfunction!(undiff, m)?)?; + Ok(()) +} diff --git a/reconcile-python/tests/test_reconcile.py b/reconcile-python/tests/test_reconcile.py new file mode 100644 index 0000000..0d3f964 --- /dev/null +++ b/reconcile-python/tests/test_reconcile.py @@ -0,0 +1,132 @@ +from __future__ import annotations + +from pathlib import Path + +import pytest + +from reconcile_text import diff, reconcile, reconcile_with_history, undiff + +RESOURCES_DIR = Path(__file__).resolve().parent.parent.parent / "tests" / "resources" + +FILES = ["pride_and_prejudice.txt", "room_with_a_view.txt", "blns.txt"] + + +class TestReconcile: + def test_basic_merge(self) -> None: + result = reconcile("Hello", "Hello world", "Hi world") + assert result["text"] == "Hi world" + + def test_three_way_merge(self) -> None: + parent = "Merging text is hard!" + left = "Merging text is easy!" + right = "With reconcile, merging documents is hard!" + + result = reconcile(parent, left, right) + assert result["text"] == "With reconcile, merging documents is easy!" + + def test_with_cursors(self) -> None: + result = reconcile( + "Hello", + {"text": "Hello world", "cursors": [{"id": 3, "position": 2}]}, + { + "text": "Hi world", + "cursors": [{"id": 4, "position": 0}, {"id": 5, "position": 3}], + }, + ) + + assert result["text"] == "Hi world" + assert result["cursors"] == [ + {"id": 3, "position": 0}, + {"id": 4, "position": 0}, + {"id": 5, "position": 3}, + ] + + def test_character_tokenizer(self) -> None: + result = reconcile("abc", "axc", "abyc", "Character") + assert result["text"] == "axyc" + + def test_line_tokenizer(self) -> None: + parent = "line1\nline2\nline3\n" + left = "line1\nmodified\nline3\n" + right = "line1\nline2\nnew line\n" + + result = reconcile(parent, left, right, "Line") + assert result["text"] == "line1\nmodified\nnew line\n" + + def test_empty_texts(self) -> None: + result = reconcile("", "", "") + assert result["text"] == "" + assert result["cursors"] == [] + + def test_invalid_tokenizer(self) -> None: + with pytest.raises(ValueError, match="Unknown tokenizer"): + reconcile("a", "b", "c", "Invalid") # type: ignore[arg-type] + + +class TestReconcileWithHistory: + def test_returns_history(self) -> None: + result = reconcile_with_history( + "Merging text is hard!", + "Merging text is easy!", + "With reconcile, merging documents is hard!", + ) + + assert result["text"] == "With reconcile, merging documents is easy!" + assert len(result["history"]) > 0 + assert all("text" in span and "history" in span for span in result["history"]) + + def test_history_values(self) -> None: + valid_histories = { + "Unchanged", + "AddedFromLeft", + "AddedFromRight", + "RemovedFromLeft", + "RemovedFromRight", + } + result = reconcile_with_history("Hello", "Hello world", "Hi") + for span in result["history"]: + assert span["history"] in valid_histories + + +class TestDiff: + def test_basic_diff(self) -> None: + result = diff("Hello world", "Hello beautiful world") + assert isinstance(result, list) + assert all(isinstance(item, (int, str)) for item in result) + + def test_no_change(self) -> None: + result = diff("same text", "same text") + # A retain-only diff + assert all(isinstance(item, int) and item > 0 for item in result) + + +class TestUndiff: + def test_roundtrip(self) -> None: + original = "Hello world" + changed = "Hello beautiful world" + + d = diff(original, changed) + reconstructed = undiff(original, d) + assert reconstructed == changed + + def test_empty_roundtrip(self) -> None: + d = diff("", "") + assert undiff("", d) == "" + + def test_invalid_diff(self) -> None: + with pytest.raises(ValueError): + undiff("short", [100]) + + +class TestDiffUndiffInverse: + """Verify diff/undiff roundtrip across large real-world texts.""" + + @pytest.mark.parametrize("file1", FILES) + @pytest.mark.parametrize("file2", FILES) + def test_roundtrip_files(self, file1: str, file2: str) -> None: + content1 = (RESOURCES_DIR / file1).read_text()[:50000] + content2 = (RESOURCES_DIR / file2).read_text()[:50000] + + changes = diff(content1, content2) + actual = undiff(content1, changes) + assert actual == content2 diff --git a/reconcile-python/uv.lock b/reconcile-python/uv.lock new file mode 100644 index 0000000..02387e3 --- /dev/null +++ b/reconcile-python/uv.lock @@ -0,0 +1,279 @@ +version = 1 +revision = 3 +requires-python = ">=3.9" +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version < '3.10'", +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "maturin" +version = "1.12.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0c/18/8b2eebd3ea086a5ec73d7081f95ec64918ceda1900075902fc296ea3ad55/maturin-1.12.6.tar.gz", hash = "sha256:d37be3a811a7f2ee28a0fa0964187efa50e90f21da0c6135c27787fa0b6a89db", size = 269165, upload-time = "2026-03-01T14:54:04.21Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/8b/9ddfde8a485489e3ebdc50ee3042ef1c854f00dfea776b951068f6ffe451/maturin-1.12.6-py3-none-linux_armv6l.whl", hash = "sha256:6892b4176992fcc143f9d1c1c874a816e9a041248eef46433db87b0f0aff4278", size = 9789847, upload-time = "2026-03-01T14:54:09.172Z" }, + { url = "https://files.pythonhosted.org/packages/ef/e8/5f7fd3763f214a77ac0388dbcc71cc30aec5490016bd0c8e6bd729fc7b0a/maturin-1.12.6-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:c0c742beeeef7fb93b6a81bd53e75507887e396fd1003c45117658d063812dad", size = 19023833, upload-time = "2026-03-01T14:53:46.743Z" }, + { url = "https://files.pythonhosted.org/packages/e0/7f/706ff3839c8b2046436d4c2bc97596c558728264d18abc298a1ad862a4be/maturin-1.12.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:2cb41139295eed6411d3cdafc7430738094c2721f34b7eeb44f33cac516115dc", size = 9821620, upload-time = "2026-03-01T14:54:12.04Z" }, + { url = "https://files.pythonhosted.org/packages/0e/9c/70917fb123c8dd6b595e913616c9c72d730cbf4a2b6cac8077dc02a12586/maturin-1.12.6-py3-none-manylinux_2_12_i686.manylinux2010_i686.musllinux_1_1_i686.whl", hash = "sha256:351f3af1488a7cbdcff3b6d8482c17164273ac981378a13a4a9937a49aec7d71", size = 9849107, upload-time = "2026-03-01T14:53:48.971Z" }, + { url = "https://files.pythonhosted.org/packages/59/ea/f1d6ad95c0a12fbe761a7c28a57540341f188564dbe8ad730a4d1788cd32/maturin-1.12.6-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.musllinux_1_1_x86_64.whl", hash = "sha256:6dbddfe4dc7ddee60bbac854870bd7cfec660acb54d015d24597d59a1c828f61", size = 10242855, upload-time = "2026-03-01T14:53:44.605Z" }, + { url = "https://files.pythonhosted.org/packages/93/1b/2419843a4f1d2fb4747f3dc3d9c4a2881cd97a3274dd94738fcdf0835e79/maturin-1.12.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:8fdb0f63e77ee3df0f027a120e9af78dbc31edf0eb0f263d55783c250c33b728", size = 9674972, upload-time = "2026-03-01T14:53:52.763Z" }, + { url = "https://files.pythonhosted.org/packages/71/46/b60ab2fc996d904b40e55bd475599dcdccd8f7ad3e649bf95e87970df466/maturin-1.12.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:fa84b7493a2e80759cacc2e668fa5b444d55b9994e90707c42904f55d6322c1e", size = 9645755, upload-time = "2026-03-01T14:53:58.497Z" }, + { url = "https://files.pythonhosted.org/packages/a4/96/03f2b55a8c226805115232fc23c4a4f33f0c9d39e11efab8166dc440f80d/maturin-1.12.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.musllinux_1_1_ppc64le.whl", hash = "sha256:e90dc12bc6a38e9495692a36c9e231c4d7e0c9bfde60719468ab7d8673db3c45", size = 12737612, upload-time = "2026-03-01T14:54:05.393Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c2/648667022c5b53cdccefa67c245e8a984970f3045820f00c2e23bdb2aff4/maturin-1.12.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:06fc8d089f98623ce924c669b70911dfed30f9a29956c362945f727f9abc546b", size = 10455028, upload-time = "2026-03-01T14:54:07.349Z" }, + { url = "https://files.pythonhosted.org/packages/63/d6/5b5efe3ca0c043357ed3f8d2b2d556169fdbf1ff75e50e8e597708a359d2/maturin-1.12.6-py3-none-manylinux_2_31_riscv64.musllinux_1_1_riscv64.whl", hash = "sha256:75133e56274d43b9227fd49dca9a86e32f1fd56a7b55544910c4ce978c2bb5aa", size = 10014531, upload-time = "2026-03-01T14:53:54.548Z" }, + { url = "https://files.pythonhosted.org/packages/68/d5/39c594c27b1a8b32a0cb95fff9ad60b888c4352d1d1c389ac1bd20dc1e16/maturin-1.12.6-py3-none-win32.whl", hash = "sha256:3f32e0a3720b81423c9d35c14e728cb1f954678124749776dc72d533ea1115e8", size = 8553012, upload-time = "2026-03-01T14:53:50.706Z" }, + { url = "https://files.pythonhosted.org/packages/94/66/b262832a91747e04051e21f986bd01a8af81fbffafacc7d66a11e79aab5f/maturin-1.12.6-py3-none-win_amd64.whl", hash = "sha256:977290159d252db946054a0555263c59b3d0c7957135c69e690f4b1558ee9983", size = 9890470, upload-time = "2026-03-01T14:53:56.659Z" }, + { url = "https://files.pythonhosted.org/packages/e3/47/76b8ca470ddc8d7d36aa8c15f5a6aed1841806bb93a0f4ead8ee61e9a088/maturin-1.12.6-py3-none-win_arm64.whl", hash = "sha256:bae91976cdc8148038e13c881e1e844e5c63e58e026e8b9945aa2d19b3b4ae89", size = 8606158, upload-time = "2026-03-01T14:54:02.423Z" }, +] + +[[package]] +name = "nodeenv" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, +] + +[[package]] +name = "packaging" +version = "26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pyright" +version = "1.1.408" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nodeenv" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/74/b2/5db700e52554b8f025faa9c3c624c59f1f6c8841ba81ab97641b54322f16/pyright-1.1.408.tar.gz", hash = "sha256:f28f2321f96852fa50b5829ea492f6adb0e6954568d1caa3f3af3a5f555eb684", size = 4400578, upload-time = "2026-01-08T08:07:38.795Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/82/a2c93e32800940d9573fb28c346772a14778b84ba7524e691b324620ab89/pyright-1.1.408-py3-none-any.whl", hash = "sha256:090b32865f4fdb1e0e6cd82bf5618480d48eecd2eb2e70f960982a3d9a4c17c1", size = 6399144, upload-time = "2026-01-08T08:07:37.082Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.10'" }, + { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "packaging", marker = "python_full_version < '3.10'" }, + { name = "pluggy", marker = "python_full_version < '3.10'" }, + { name = "pygments", marker = "python_full_version < '3.10'" }, + { name = "tomli", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" }, + { name = "iniconfig", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "packaging", marker = "python_full_version >= '3.10'" }, + { name = "pluggy", marker = "python_full_version >= '3.10'" }, + { name = "pygments", marker = "python_full_version >= '3.10'" }, + { name = "tomli", marker = "python_full_version == '3.10.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, +] + +[[package]] +name = "reconcile-text" +version = "0.8.0" +source = { editable = "." } + +[package.dev-dependencies] +dev = [ + { name = "maturin" }, + { name = "pyright" }, + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pytest", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "ruff" }, +] + +[package.metadata] + +[package.metadata.requires-dev] +dev = [ + { name = "maturin", specifier = ">=1.0,<2.0" }, + { name = "pyright", specifier = ">=1" }, + { name = "pytest", specifier = ">=8" }, + { name = "ruff", specifier = ">=0.15" }, +] + +[[package]] +name = "ruff" +version = "0.15.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/77/9b/840e0039e65fcf12758adf684d2289024d6140cde9268cc59887dc55189c/ruff-0.15.5.tar.gz", hash = "sha256:7c3601d3b6d76dce18c5c824fc8d06f4eef33d6df0c21ec7799510cde0f159a2", size = 4574214, upload-time = "2026-03-05T20:06:34.946Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/20/5369c3ce21588c708bcbe517a8fbe1a8dfdb5dfd5137e14790b1da71612c/ruff-0.15.5-py3-none-linux_armv6l.whl", hash = "sha256:4ae44c42281f42e3b06b988e442d344a5b9b72450ff3c892e30d11b29a96a57c", size = 10478185, upload-time = "2026-03-05T20:06:29.093Z" }, + { url = "https://files.pythonhosted.org/packages/44/ed/e81dd668547da281e5dce710cf0bc60193f8d3d43833e8241d006720e42b/ruff-0.15.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6edd3792d408ebcf61adabc01822da687579a1a023f297618ac27a5b51ef0080", size = 10859201, upload-time = "2026-03-05T20:06:32.632Z" }, + { url = "https://files.pythonhosted.org/packages/c4/8f/533075f00aaf19b07c5cd6aa6e5d89424b06b3b3f4583bfa9c640a079059/ruff-0.15.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:89f463f7c8205a9f8dea9d658d59eff49db05f88f89cc3047fb1a02d9f344010", size = 10184752, upload-time = "2026-03-05T20:06:40.312Z" }, + { url = "https://files.pythonhosted.org/packages/66/0e/ba49e2c3fa0395b3152bad634c7432f7edfc509c133b8f4529053ff024fb/ruff-0.15.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba786a8295c6574c1116704cf0b9e6563de3432ac888d8f83685654fe528fd65", size = 10534857, upload-time = "2026-03-05T20:06:19.581Z" }, + { url = "https://files.pythonhosted.org/packages/59/71/39234440f27a226475a0659561adb0d784b4d247dfe7f43ffc12dd02e288/ruff-0.15.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd4b801e57955fe9f02b31d20375ab3a5c4415f2e5105b79fb94cf2642c91440", size = 10309120, upload-time = "2026-03-05T20:06:00.435Z" }, + { url = "https://files.pythonhosted.org/packages/f5/87/4140aa86a93df032156982b726f4952aaec4a883bb98cb6ef73c347da253/ruff-0.15.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:391f7c73388f3d8c11b794dbbc2959a5b5afe66642c142a6effa90b45f6f5204", size = 11047428, upload-time = "2026-03-05T20:05:51.867Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f7/4953e7e3287676f78fbe85e3a0ca414c5ca81237b7575bdadc00229ac240/ruff-0.15.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dc18f30302e379fe1e998548b0f5e9f4dff907f52f73ad6da419ea9c19d66c8", size = 11914251, upload-time = "2026-03-05T20:06:22.887Z" }, + { url = "https://files.pythonhosted.org/packages/77/46/0f7c865c10cf896ccf5a939c3e84e1cfaeed608ff5249584799a74d33835/ruff-0.15.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cc6e7f90087e2d27f98dc34ed1b3ab7c8f0d273cc5431415454e22c0bd2a681", size = 11333801, upload-time = "2026-03-05T20:05:57.168Z" }, + { url = "https://files.pythonhosted.org/packages/d3/01/a10fe54b653061585e655f5286c2662ebddb68831ed3eaebfb0eb08c0a16/ruff-0.15.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1cb7169f53c1ddb06e71a9aebd7e98fc0fea936b39afb36d8e86d36ecc2636a", size = 11206821, upload-time = "2026-03-05T20:06:03.441Z" }, + { url = "https://files.pythonhosted.org/packages/7a/0d/2132ceaf20c5e8699aa83da2706ecb5c5dcdf78b453f77edca7fb70f8a93/ruff-0.15.5-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9b037924500a31ee17389b5c8c4d88874cc6ea8e42f12e9c61a3d754ff72f1ca", size = 11133326, upload-time = "2026-03-05T20:06:25.655Z" }, + { url = "https://files.pythonhosted.org/packages/72/cb/2e5259a7eb2a0f87c08c0fe5bf5825a1e4b90883a52685524596bfc93072/ruff-0.15.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:65bb414e5b4eadd95a8c1e4804f6772bbe8995889f203a01f77ddf2d790929dd", size = 10510820, upload-time = "2026-03-05T20:06:37.79Z" }, + { url = "https://files.pythonhosted.org/packages/ff/20/b67ce78f9e6c59ffbdb5b4503d0090e749b5f2d31b599b554698a80d861c/ruff-0.15.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d20aa469ae3b57033519c559e9bc9cd9e782842e39be05b50e852c7c981fa01d", size = 10302395, upload-time = "2026-03-05T20:05:54.504Z" }, + { url = "https://files.pythonhosted.org/packages/5f/e5/719f1acccd31b720d477751558ed74e9c88134adcc377e5e886af89d3072/ruff-0.15.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:15388dd28c9161cdb8eda68993533acc870aa4e646a0a277aa166de9ad5a8752", size = 10754069, upload-time = "2026-03-05T20:06:06.422Z" }, + { url = "https://files.pythonhosted.org/packages/c3/9c/d1db14469e32d98f3ca27079dbd30b7b44dbb5317d06ab36718dee3baf03/ruff-0.15.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b30da330cbd03bed0c21420b6b953158f60c74c54c5f4c1dabbdf3a57bf355d2", size = 11304315, upload-time = "2026-03-05T20:06:10.867Z" }, + { url = "https://files.pythonhosted.org/packages/28/3a/950367aee7c69027f4f422059227b290ed780366b6aecee5de5039d50fa8/ruff-0.15.5-py3-none-win32.whl", hash = "sha256:732e5ee1f98ba5b3679029989a06ca39a950cced52143a0ea82a2102cb592b74", size = 10551676, upload-time = "2026-03-05T20:06:13.705Z" }, + { url = "https://files.pythonhosted.org/packages/b8/00/bf077a505b4e649bdd3c47ff8ec967735ce2544c8e4a43aba42ee9bf935d/ruff-0.15.5-py3-none-win_amd64.whl", hash = "sha256:821d41c5fa9e19117616c35eaa3f4b75046ec76c65e7ae20a333e9a8696bc7fe", size = 11678972, upload-time = "2026-03-05T20:06:45.379Z" }, + { url = "https://files.pythonhosted.org/packages/fe/4e/cd76eca6db6115604b7626668e891c9dd03330384082e33662fb0f113614/ruff-0.15.5-py3-none-win_arm64.whl", hash = "sha256:b498d1c60d2fe5c10c45ec3f698901065772730b411f164ae270bb6bfcc4740b", size = 10965572, upload-time = "2026-03-05T20:06:16.984Z" }, +] + +[[package]] +name = "tomli" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/30/31573e9457673ab10aa432461bee537ce6cef177667deca369efb79df071/tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c", size = 17477, upload-time = "2026-01-11T11:22:38.165Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/d9/3dc2289e1f3b32eb19b9785b6a006b28ee99acb37d1d47f78d4c10e28bf8/tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867", size = 153663, upload-time = "2026-01-11T11:21:45.27Z" }, + { url = "https://files.pythonhosted.org/packages/51/32/ef9f6845e6b9ca392cd3f64f9ec185cc6f09f0a2df3db08cbe8809d1d435/tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9", size = 148469, upload-time = "2026-01-11T11:21:46.873Z" }, + { url = "https://files.pythonhosted.org/packages/d6/c2/506e44cce89a8b1b1e047d64bd495c22c9f71f21e05f380f1a950dd9c217/tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95", size = 236039, upload-time = "2026-01-11T11:21:48.503Z" }, + { url = "https://files.pythonhosted.org/packages/b3/40/e1b65986dbc861b7e986e8ec394598187fa8aee85b1650b01dd925ca0be8/tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76", size = 243007, upload-time = "2026-01-11T11:21:49.456Z" }, + { url = "https://files.pythonhosted.org/packages/9c/6f/6e39ce66b58a5b7ae572a0f4352ff40c71e8573633deda43f6a379d56b3e/tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d", size = 240875, upload-time = "2026-01-11T11:21:50.755Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ad/cb089cb190487caa80204d503c7fd0f4d443f90b95cf4ef5cf5aa0f439b0/tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576", size = 246271, upload-time = "2026-01-11T11:21:51.81Z" }, + { url = "https://files.pythonhosted.org/packages/0b/63/69125220e47fd7a3a27fd0de0c6398c89432fec41bc739823bcc66506af6/tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a", size = 96770, upload-time = "2026-01-11T11:21:52.647Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0d/a22bb6c83f83386b0008425a6cd1fa1c14b5f3dd4bad05e98cf3dbbf4a64/tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa", size = 107626, upload-time = "2026-01-11T11:21:53.459Z" }, + { url = "https://files.pythonhosted.org/packages/2f/6d/77be674a3485e75cacbf2ddba2b146911477bd887dda9d8c9dfb2f15e871/tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614", size = 94842, upload-time = "2026-01-11T11:21:54.831Z" }, + { url = "https://files.pythonhosted.org/packages/3c/43/7389a1869f2f26dba52404e1ef13b4784b6b37dac93bac53457e3ff24ca3/tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1", size = 154894, upload-time = "2026-01-11T11:21:56.07Z" }, + { url = "https://files.pythonhosted.org/packages/e9/05/2f9bf110b5294132b2edf13fe6ca6ae456204f3d749f623307cbb7a946f2/tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8", size = 149053, upload-time = "2026-01-11T11:21:57.467Z" }, + { url = "https://files.pythonhosted.org/packages/e8/41/1eda3ca1abc6f6154a8db4d714a4d35c4ad90adc0bcf700657291593fbf3/tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a", size = 243481, upload-time = "2026-01-11T11:21:58.661Z" }, + { url = "https://files.pythonhosted.org/packages/d2/6d/02ff5ab6c8868b41e7d4b987ce2b5f6a51d3335a70aa144edd999e055a01/tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1", size = 251720, upload-time = "2026-01-11T11:22:00.178Z" }, + { url = "https://files.pythonhosted.org/packages/7b/57/0405c59a909c45d5b6f146107c6d997825aa87568b042042f7a9c0afed34/tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b", size = 247014, upload-time = "2026-01-11T11:22:01.238Z" }, + { url = "https://files.pythonhosted.org/packages/2c/0e/2e37568edd944b4165735687cbaf2fe3648129e440c26d02223672ee0630/tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51", size = 251820, upload-time = "2026-01-11T11:22:02.727Z" }, + { url = "https://files.pythonhosted.org/packages/5a/1c/ee3b707fdac82aeeb92d1a113f803cf6d0f37bdca0849cb489553e1f417a/tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729", size = 97712, upload-time = "2026-01-11T11:22:03.777Z" }, + { url = "https://files.pythonhosted.org/packages/69/13/c07a9177d0b3bab7913299b9278845fc6eaaca14a02667c6be0b0a2270c8/tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da", size = 108296, upload-time = "2026-01-11T11:22:04.86Z" }, + { url = "https://files.pythonhosted.org/packages/18/27/e267a60bbeeee343bcc279bb9e8fbed0cbe224bc7b2a3dc2975f22809a09/tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3", size = 94553, upload-time = "2026-01-11T11:22:05.854Z" }, + { url = "https://files.pythonhosted.org/packages/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0", size = 154915, upload-time = "2026-01-11T11:22:06.703Z" }, + { url = "https://files.pythonhosted.org/packages/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e", size = 149038, upload-time = "2026-01-11T11:22:07.56Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8a/6d38870bd3d52c8d1505ce054469a73f73a0fe62c0eaf5dddf61447e32fa/tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4", size = 242245, upload-time = "2026-01-11T11:22:08.344Z" }, + { url = "https://files.pythonhosted.org/packages/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e", size = 250335, upload-time = "2026-01-11T11:22:09.951Z" }, + { url = "https://files.pythonhosted.org/packages/a5/3d/4cdb6f791682b2ea916af2de96121b3cb1284d7c203d97d92d6003e91c8d/tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c", size = 245962, upload-time = "2026-01-11T11:22:11.27Z" }, + { url = "https://files.pythonhosted.org/packages/f2/4a/5f25789f9a460bd858ba9756ff52d0830d825b458e13f754952dd15fb7bb/tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f", size = 250396, upload-time = "2026-01-11T11:22:12.325Z" }, + { url = "https://files.pythonhosted.org/packages/aa/2f/b73a36fea58dfa08e8b3a268750e6853a6aac2a349241a905ebd86f3047a/tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86", size = 97530, upload-time = "2026-01-11T11:22:13.865Z" }, + { url = "https://files.pythonhosted.org/packages/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87", size = 108227, upload-time = "2026-01-11T11:22:15.224Z" }, + { url = "https://files.pythonhosted.org/packages/22/c3/b386b832f209fee8073c8138ec50f27b4460db2fdae9ffe022df89a57f9b/tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132", size = 94748, upload-time = "2026-01-11T11:22:16.009Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c4/84047a97eb1004418bc10bdbcfebda209fca6338002eba2dc27cc6d13563/tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6", size = 154725, upload-time = "2026-01-11T11:22:17.269Z" }, + { url = "https://files.pythonhosted.org/packages/a8/5d/d39038e646060b9d76274078cddf146ced86dc2b9e8bbf737ad5983609a0/tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc", size = 148901, upload-time = "2026-01-11T11:22:18.287Z" }, + { url = "https://files.pythonhosted.org/packages/73/e5/383be1724cb30f4ce44983d249645684a48c435e1cd4f8b5cded8a816d3c/tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66", size = 243375, upload-time = "2026-01-11T11:22:19.154Z" }, + { url = "https://files.pythonhosted.org/packages/31/f0/bea80c17971c8d16d3cc109dc3585b0f2ce1036b5f4a8a183789023574f2/tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d", size = 250639, upload-time = "2026-01-11T11:22:20.168Z" }, + { url = "https://files.pythonhosted.org/packages/2c/8f/2853c36abbb7608e3f945d8a74e32ed3a74ee3a1f468f1ffc7d1cb3abba6/tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702", size = 246897, upload-time = "2026-01-11T11:22:21.544Z" }, + { url = "https://files.pythonhosted.org/packages/49/f0/6c05e3196ed5337b9fe7ea003e95fd3819a840b7a0f2bf5a408ef1dad8ed/tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8", size = 254697, upload-time = "2026-01-11T11:22:23.058Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f5/2922ef29c9f2951883525def7429967fc4d8208494e5ab524234f06b688b/tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776", size = 98567, upload-time = "2026-01-11T11:22:24.033Z" }, + { url = "https://files.pythonhosted.org/packages/7b/31/22b52e2e06dd2a5fdbc3ee73226d763b184ff21fc24e20316a44ccc4d96b/tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475", size = 108556, upload-time = "2026-01-11T11:22:25.378Z" }, + { url = "https://files.pythonhosted.org/packages/48/3d/5058dff3255a3d01b705413f64f4306a141a8fd7a251e5a495e3f192a998/tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2", size = 96014, upload-time = "2026-01-11T11:22:26.138Z" }, + { url = "https://files.pythonhosted.org/packages/b8/4e/75dab8586e268424202d3a1997ef6014919c941b50642a1682df43204c22/tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9", size = 163339, upload-time = "2026-01-11T11:22:27.143Z" }, + { url = "https://files.pythonhosted.org/packages/06/e3/b904d9ab1016829a776d97f163f183a48be6a4deb87304d1e0116a349519/tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0", size = 159490, upload-time = "2026-01-11T11:22:28.399Z" }, + { url = "https://files.pythonhosted.org/packages/e3/5a/fc3622c8b1ad823e8ea98a35e3c632ee316d48f66f80f9708ceb4f2a0322/tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df", size = 269398, upload-time = "2026-01-11T11:22:29.345Z" }, + { url = "https://files.pythonhosted.org/packages/fd/33/62bd6152c8bdd4c305ad9faca48f51d3acb2df1f8791b1477d46ff86e7f8/tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d", size = 276515, upload-time = "2026-01-11T11:22:30.327Z" }, + { url = "https://files.pythonhosted.org/packages/4b/ff/ae53619499f5235ee4211e62a8d7982ba9e439a0fb4f2f351a93d67c1dd2/tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f", size = 273806, upload-time = "2026-01-11T11:22:32.56Z" }, + { url = "https://files.pythonhosted.org/packages/47/71/cbca7787fa68d4d0a9f7072821980b39fbb1b6faeb5f5cf02f4a5559fa28/tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b", size = 281340, upload-time = "2026-01-11T11:22:33.505Z" }, + { url = "https://files.pythonhosted.org/packages/f5/00/d595c120963ad42474cf6ee7771ad0d0e8a49d0f01e29576ee9195d9ecdf/tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087", size = 108106, upload-time = "2026-01-11T11:22:34.451Z" }, + { url = "https://files.pythonhosted.org/packages/de/69/9aa0c6a505c2f80e519b43764f8b4ba93b5a0bbd2d9a9de6e2b24271b9a5/tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd", size = 120504, upload-time = "2026-01-11T11:22:35.764Z" }, + { url = "https://files.pythonhosted.org/packages/b3/9f/f1668c281c58cfae01482f7114a4b88d345e4c140386241a1a24dcc9e7bc/tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4", size = 99561, upload-time = "2026-01-11T11:22:36.624Z" }, + { url = "https://files.pythonhosted.org/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", size = 14477, upload-time = "2026-01-11T11:22:37.446Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] diff --git a/scripts/bump-version.sh b/scripts/bump-version.sh index 4163c85..4e03849 100755 --- a/scripts/bump-version.sh +++ b/scripts/bump-version.sh @@ -37,6 +37,11 @@ cd reconcile-js npm version $1 npm install +NEWVER=$(grep '^version = ' ../Cargo.toml | head -1 | sed 's/version = "\(.*\)"/\1/') +cd ../reconcile-python +sed -i '' "s/^version = \".*\"/version = \"$NEWVER\"/" Cargo.toml +sed -i '' "s/^version = \".*\"/version = \"$NEWVER\"/" pyproject.toml + cd ../examples/website npm install diff --git a/scripts/lint.sh b/scripts/lint.sh index fdffb20..84c096c 100755 --- a/scripts/lint.sh +++ b/scripts/lint.sh @@ -19,4 +19,11 @@ cd ../examples/website npm ci npm run format +cd ../../reconcile-python +uv run maturin develop -q +uv run ruff check python/ tests/ +uv run ruff format python/ tests/ +uv run pyright python/ tests/ +cd - + echo "Success!" diff --git a/scripts/test.sh b/scripts/test.sh index b7678ee..31b3c78 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -27,4 +27,9 @@ npm run build npm run test cd - +cd reconcile-python +uv run maturin develop +uv run pytest -v +cd - + echo "Success!" From 92e0697b05f072fd5f9c7fb6b94df893fc9caa7d Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Wed, 11 Mar 2026 21:04:55 +0000 Subject: [PATCH 20/61] Add example --- README.md | 2 +- docs/advanced-python.md | 4 ++ examples/merge_file.py | 38 +++++++++++++++++++ reconcile-python/tests/test_reconcile.py | 47 ++++++++++++++++++++++++ 4 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 examples/merge_file.py diff --git a/README.md b/README.md index 8ee4dc7..c25ef92 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,7 @@ result = reconcile(parent, left, right) print(result["text"]) # "Hi beautiful world" ``` -See the [advanced Python examples](docs/advanced-python.md) for cursor tracking, change provenance, and compact diffs. +See the [merge-file example](examples/merge_file.py) for a file-merging CLI, or the [advanced examples document](docs/advanced-python.md) for cursor tracking, change provenance, and compact diffs. ## Motivation diff --git a/docs/advanced-python.md b/docs/advanced-python.md index 1e21907..ed2a8e9 100644 --- a/docs/advanced-python.md +++ b/docs/advanced-python.md @@ -86,3 +86,7 @@ assert reconstructed == changed ``` Diff entries are positive integers (retain N characters), negative integers (delete N characters), and strings (insert text). + +## File Merging Example + +For a complete file-merging CLI (a trivial `git merge-file`), see [`examples/merge_file.py`](../examples/merge_file.py). diff --git a/examples/merge_file.py b/examples/merge_file.py new file mode 100644 index 0000000..23e8555 --- /dev/null +++ b/examples/merge_file.py @@ -0,0 +1,38 @@ +"""Merge three versions of a file: mine, base, and theirs. + +A trivial version of git merge-file (https://git-scm.com/docs/git-merge-file). + +Run it with: + uv run --directory reconcile-python \ + python ../examples/merge_file.py my.txt base.txt their.txt [output.txt] +""" + +from __future__ import annotations + +import sys +from pathlib import Path + +from reconcile_text import reconcile + + +def main() -> None: + args = sys.argv[1:] + + if len(args) < 3 or len(args) > 4: + print("Usage: merge_file.py [output]", file=sys.stderr) + sys.exit(1) + + mine = Path(args[0]).read_text() + base = Path(args[1]).read_text() + theirs = Path(args[2]).read_text() + + result = reconcile(base, mine, theirs) + + if len(args) == 4: + Path(args[3]).write_text(result["text"]) + else: + print(result["text"], end="") + + +if __name__ == "__main__": + main() diff --git a/reconcile-python/tests/test_reconcile.py b/reconcile-python/tests/test_reconcile.py index 0d3f964..196e808 100644 --- a/reconcile-python/tests/test_reconcile.py +++ b/reconcile-python/tests/test_reconcile.py @@ -1,11 +1,14 @@ from __future__ import annotations +import subprocess +import sys from pathlib import Path import pytest from reconcile_text import diff, reconcile, reconcile_with_history, undiff +EXAMPLES_DIR = Path(__file__).resolve().parent.parent.parent / "examples" RESOURCES_DIR = Path(__file__).resolve().parent.parent.parent / "tests" / "resources" FILES = ["pride_and_prejudice.txt", "room_with_a_view.txt", "blns.txt"] @@ -118,6 +121,50 @@ class TestUndiff: undiff("short", [100]) +class TestExamples: + def test_merge_file_stdout(self, tmp_path: Path) -> None: + (tmp_path / "base.txt").write_text("Hello world") + (tmp_path / "mine.txt").write_text("Hello beautiful world") + (tmp_path / "theirs.txt").write_text("Hi world") + + result = subprocess.run( + [ + sys.executable, + str(EXAMPLES_DIR / "merge_file.py"), + str(tmp_path / "mine.txt"), + str(tmp_path / "base.txt"), + str(tmp_path / "theirs.txt"), + ], + capture_output=True, + text=True, + check=True, + ) + + assert result.stdout == "Hi beautiful world" + + def test_merge_file_output_file(self, tmp_path: Path) -> None: + (tmp_path / "base.txt").write_text("Hello world") + (tmp_path / "mine.txt").write_text("Hello beautiful world") + (tmp_path / "theirs.txt").write_text("Hi world") + output = tmp_path / "output.txt" + + subprocess.run( + [ + sys.executable, + str(EXAMPLES_DIR / "merge_file.py"), + str(tmp_path / "mine.txt"), + str(tmp_path / "base.txt"), + str(tmp_path / "theirs.txt"), + str(output), + ], + capture_output=True, + text=True, + check=True, + ) + + assert output.read_text() == "Hi beautiful world" + + class TestDiffUndiffInverse: """Verify diff/undiff roundtrip across large real-world texts.""" From 1db4cd02f9f224231f05cfc4e468f3a7d9b154a4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Mar 2026 19:05:52 +0000 Subject: [PATCH 21/61] Bump actions/setup-node from 6.1.0 to 6.3.0 Bumps [actions/setup-node](https://github.com/actions/setup-node) from 6.1.0 to 6.3.0. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v6.1.0...v6.3.0) --- updated-dependencies: - dependency-name: actions/setup-node dependency-version: 6.3.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/check.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 4561abc..1827833 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v6 - name: Setup Node.js environment - uses: actions/setup-node@v6.1.0 + uses: actions/setup-node@v6.3.0 with: node-version: '22.x' check-latest: true @@ -76,7 +76,7 @@ jobs: - uses: actions/checkout@v6 - name: Setup Node.js environment - uses: actions/setup-node@v6.1.0 + uses: actions/setup-node@v6.3.0 with: node-version: '22.x' check-latest: true From 5a0e82b3e1bc1b5a23194efcf7f7c3261a20321c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Jan 2026 20:37:44 +0000 Subject: [PATCH 22/61] Bump thiserror from 2.0.17 to 2.0.18 Bumps [thiserror](https://github.com/dtolnay/thiserror) from 2.0.17 to 2.0.18. - [Release notes](https://github.com/dtolnay/thiserror/releases) - [Commits](https://github.com/dtolnay/thiserror/compare/2.0.17...2.0.18) --- updated-dependencies: - dependency-name: thiserror dependency-version: 2.0.18 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 8 ++++---- Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5b62f15..1967419 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -448,18 +448,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 4b5e11c..292cf57 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,7 @@ path = "examples/compare-with-diff-match-patch.rs" [dependencies] serde = { version = "1.0.219", optional = true, features = ["derive"] } -thiserror = "2.0.17" +thiserror = "2.0.18" wasm-bindgen = { version = "0.2.99", optional = true } From 31993762dea7bc9f2c9bfe81fcb933e70b6f25e5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Dec 2025 19:16:09 +0000 Subject: [PATCH 23/61] Bump actions/cache from 4 to 5 Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/cache dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/check.yml | 8 ++++---- .github/workflows/gh-pages.yml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 1827833..c87edb5 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -25,7 +25,7 @@ jobs: check-latest: true - name: Cache Rust dependencies - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: | ~/.cargo/bin/ @@ -52,7 +52,7 @@ jobs: - uses: actions/checkout@v6 - name: Cache Rust dependencies - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: | ~/.cargo/bin/ @@ -83,7 +83,7 @@ jobs: registry-url: 'https://registry.npmjs.org' - name: Cache Rust dependencies - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: | ~/.cargo/bin/ @@ -96,7 +96,7 @@ jobs: ${{ runner.os }}-cargo- - name: Cache npm dependencies - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: | reconcile-js/node_modules diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index abfdda4..7ac04ea 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -28,7 +28,7 @@ jobs: uses: actions/checkout@v6 - name: Cache Rust dependencies - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: | ~/.cargo/bin/ @@ -41,7 +41,7 @@ jobs: ${{ runner.os }}-cargo- - name: Cache npm dependencies - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: | reconcile-js/node_modules From c2144a263450572dcedf903ba87ed52d7741472e Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Wed, 11 Mar 2026 21:07:23 +0000 Subject: [PATCH 24/61] Use stable rust --- Cargo.toml | 4 ++-- reconcile-python/Cargo.toml | 2 +- rust-toolchain.toml | 2 +- rustfmt.toml | 7 ------ src/operation_transformation/operation.rs | 9 ++++--- src/raw_operation.rs | 4 +++- ..._tokenizer__tests__with_snapshots-11.snap} | 1 - ...arkdown_tokenizer__tests__headings-2.snap} | 1 - ...markdown_tokenizer__tests__headings-3.snap | 24 +++++++++++++++++++ src/tokenizer/token.rs | 24 ++++++++++++++----- src/types/cursor_position.rs | 12 +++++++--- src/types/number_or_text.rs | 20 ++++++++++++---- src/types/span_with_history.rs | 12 +++++++--- src/types/text_with_cursors.rs | 12 +++++++--- src/utils/myers_diff.rs | 4 +++- src/utils/string_builder.rs | 8 +++++-- src/wasm.rs | 12 +++++++--- tests/example_document.rs | 4 +++- 18 files changed, 118 insertions(+), 44 deletions(-) rename src/tokenizer/snapshots/{reconcile_text__tokenizer__line_tokenizer__tests__with_snapshots-11.snap.new => reconcile_text__tokenizer__line_tokenizer__tests__with_snapshots-11.snap} (97%) rename src/tokenizer/snapshots/{reconcile_text__tokenizer__markdown_tokenizer__tests__headings-2.snap.new => reconcile_text__tokenizer__markdown_tokenizer__tests__headings-2.snap} (96%) create mode 100644 src/tokenizer/snapshots/reconcile_text__tokenizer__markdown_tokenizer__tests__headings-3.snap diff --git a/Cargo.toml b/Cargo.toml index 292cf57..3616ebc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "reconcile-text" description = "Intelligent 3-way text merging with automated conflict resolution" version = "0.8.0" -rust-version = "1.85" +rust-version = "1.94" authors = ["Andras Schmelczer "] edition = "2024" license = "MIT" @@ -69,7 +69,7 @@ missing_debug_implementations = "warn" [lints.clippy] await_holding_lock = "warn" dbg_macro = "warn" -empty_enum = "warn" +empty_enums = "warn" enum_glob_use = "warn" exit = "warn" filter_map_next = "warn" diff --git a/reconcile-python/Cargo.toml b/reconcile-python/Cargo.toml index 48ab072..0f8d7e1 100644 --- a/reconcile-python/Cargo.toml +++ b/reconcile-python/Cargo.toml @@ -2,7 +2,7 @@ name = "reconcile-text-python" version = "0.8.0" edition = "2024" -rust-version = "1.85" +rust-version = "1.94" authors = ["Andras Schmelczer "] license = "MIT" publish = false diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 0d5c610..d9db229 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "nightly-2025-06-06" +channel = "1.94.0" targets = [ "x86_64-unknown-linux-gnu", "x86_64-unknown-linux-musl" ] profile = "default" diff --git a/rustfmt.toml b/rustfmt.toml index 6640f54..8a1751f 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,8 +1 @@ -imports_granularity = "crate" -condense_wildcard_suffixes = true -fn_single_line = true -format_strings = true -reorder_impl_items = true -group_imports = "StdExternalCrate" use_field_init_shorthand = true -wrap_comments=true diff --git a/src/operation_transformation/operation.rs b/src/operation_transformation/operation.rs index 9d06639..a6e2522 100644 --- a/src/operation_transformation/operation.rs +++ b/src/operation_transformation/operation.rs @@ -331,7 +331,7 @@ where order: last_equal_order, length: last_equal_length, #[cfg(debug_assertions)] - text: last_equal_text, + text: last_equal_text, .. }), ) => { @@ -342,7 +342,8 @@ where // matching (order, length) means they cover the same substring #[cfg(debug_assertions)] debug_assert_eq!( - text, last_equal_text, + text, + last_equal_text, "Equal operations with same order and length should have the same text, \ but got {operation:?} vs {:?}", Operation::::Equal { @@ -438,7 +439,9 @@ impl Debug for Operation where T: PartialEq + Clone + Debug, { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "{self}") } + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{self}") + } } #[cfg(test)] diff --git a/src/raw_operation.rs b/src/raw_operation.rs index bde18c9..331ae81 100644 --- a/src/raw_operation.rs +++ b/src/raw_operation.rs @@ -20,7 +20,9 @@ impl RawOperation where T: PartialEq + Clone + Debug, { - pub fn vec_from(left: &[Token], right: &[Token]) -> Vec { myers_diff(left, right) } + pub fn vec_from(left: &[Token], right: &[Token]) -> Vec { + myers_diff(left, right) + } pub fn tokens(&self) -> &[Token] { match self { diff --git a/src/tokenizer/snapshots/reconcile_text__tokenizer__line_tokenizer__tests__with_snapshots-11.snap.new b/src/tokenizer/snapshots/reconcile_text__tokenizer__line_tokenizer__tests__with_snapshots-11.snap similarity index 97% rename from src/tokenizer/snapshots/reconcile_text__tokenizer__line_tokenizer__tests__with_snapshots-11.snap.new rename to src/tokenizer/snapshots/reconcile_text__tokenizer__line_tokenizer__tests__with_snapshots-11.snap index 36f12f9..c48bca5 100644 --- a/src/tokenizer/snapshots/reconcile_text__tokenizer__line_tokenizer__tests__with_snapshots-11.snap.new +++ b/src/tokenizer/snapshots/reconcile_text__tokenizer__line_tokenizer__tests__with_snapshots-11.snap @@ -1,6 +1,5 @@ --- source: src/tokenizer/line_tokenizer.rs -assertion_line: 78 expression: "line_tokenizer(\"Mixed\\r\\nand\\rbare\")" --- [ diff --git a/src/tokenizer/snapshots/reconcile_text__tokenizer__markdown_tokenizer__tests__headings-2.snap.new b/src/tokenizer/snapshots/reconcile_text__tokenizer__markdown_tokenizer__tests__headings-2.snap similarity index 96% rename from src/tokenizer/snapshots/reconcile_text__tokenizer__markdown_tokenizer__tests__headings-2.snap.new rename to src/tokenizer/snapshots/reconcile_text__tokenizer__markdown_tokenizer__tests__headings-2.snap index 0f35315..e255319 100644 --- a/src/tokenizer/snapshots/reconcile_text__tokenizer__markdown_tokenizer__tests__headings-2.snap.new +++ b/src/tokenizer/snapshots/reconcile_text__tokenizer__markdown_tokenizer__tests__headings-2.snap @@ -1,6 +1,5 @@ --- source: src/tokenizer/markdown_tokenizer.rs -assertion_line: 199 expression: "markdown_tokenizer(\"## Sub heading\")" --- [ diff --git a/src/tokenizer/snapshots/reconcile_text__tokenizer__markdown_tokenizer__tests__headings-3.snap b/src/tokenizer/snapshots/reconcile_text__tokenizer__markdown_tokenizer__tests__headings-3.snap new file mode 100644 index 0000000..aa4387c --- /dev/null +++ b/src/tokenizer/snapshots/reconcile_text__tokenizer__markdown_tokenizer__tests__headings-3.snap @@ -0,0 +1,24 @@ +--- +source: src/tokenizer/markdown_tokenizer.rs +expression: "markdown_tokenizer(\"###### Deep heading\")" +--- +[ + Token { + normalized: "###### Deep", + original: "###### Deep", + is_left_joinable: false, + is_right_joinable: true, + }, + Token { + normalized: " heading", + original: " ", + is_left_joinable: true, + is_right_joinable: true, + }, + Token { + normalized: "heading", + original: "heading", + is_left_joinable: true, + is_right_joinable: true, + }, +] diff --git a/src/tokenizer/token.rs b/src/tokenizer/token.rs index 5346717..5d82eb5 100644 --- a/src/tokenizer/token.rs +++ b/src/tokenizer/token.rs @@ -30,7 +30,9 @@ where /// Trivial implementation of Token when the normalized form is the same as the /// original string impl From<&str> for Token { - fn from(text: &str) -> Self { Token::new(text.to_owned(), text.to_owned(), true, true) } + fn from(text: &str) -> Self { + Token::new(text.to_owned(), text.to_owned(), true, true) + } } impl Token @@ -51,18 +53,28 @@ where } } - pub fn original(&self) -> &str { &self.original } + pub fn original(&self) -> &str { + &self.original + } - pub fn set_normalized(&mut self, normalized: T) { self.normalized = normalized; } + pub fn set_normalized(&mut self, normalized: T) { + self.normalized = normalized; + } - pub fn normalized(&self) -> &T { &self.normalized } + pub fn normalized(&self) -> &T { + &self.normalized + } - pub fn get_original_length(&self) -> usize { self.original.chars().count() } + pub fn get_original_length(&self) -> usize { + self.original.chars().count() + } } impl PartialEq for Token where T: PartialEq + Clone + Debug, { - fn eq(&self, other: &Self) -> bool { self.normalized == other.normalized } + fn eq(&self, other: &Self) -> bool { + self.normalized == other.normalized + } } diff --git a/src/types/cursor_position.rs b/src/types/cursor_position.rs index d69a1b2..c109b5d 100644 --- a/src/types/cursor_position.rs +++ b/src/types/cursor_position.rs @@ -18,7 +18,9 @@ pub struct CursorPosition { impl CursorPosition { #[cfg_attr(feature = "wasm", wasm_bindgen(constructor))] #[must_use] - pub fn new(id: usize, char_index: usize) -> Self { Self { id, char_index } } + pub fn new(id: usize, char_index: usize) -> Self { + Self { id, char_index } + } #[must_use] pub fn with_index(&self, index: usize) -> Self { @@ -29,9 +31,13 @@ impl CursorPosition { } #[must_use] - pub fn id(&self) -> usize { self.id } + pub fn id(&self) -> usize { + self.id + } #[cfg_attr(feature = "wasm", wasm_bindgen(js_name = characterIndex))] #[must_use] - pub fn char_index(&self) -> usize { self.char_index } + pub fn char_index(&self) -> usize { + self.char_index + } } diff --git a/src/types/number_or_text.rs b/src/types/number_or_text.rs index fe17af4..63b7a18 100644 --- a/src/types/number_or_text.rs +++ b/src/types/number_or_text.rs @@ -62,19 +62,27 @@ impl From for JsValue { } impl From for NumberOrText { - fn from(value: i64) -> Self { NumberOrText::Number(value) } + fn from(value: i64) -> Self { + NumberOrText::Number(value) + } } impl From for NumberOrText { - fn from(value: String) -> Self { NumberOrText::Text(value) } + fn from(value: String) -> Self { + NumberOrText::Text(value) + } } impl From<&str> for NumberOrText { - fn from(value: &str) -> Self { NumberOrText::Text(value.to_owned()) } + fn from(value: &str) -> Self { + NumberOrText::Text(value.to_owned()) + } } impl<'a> From> for NumberOrText { - fn from(value: Cow<'a, str>) -> Self { NumberOrText::Text(value.into_owned()) } + fn from(value: Cow<'a, str>) -> Self { + NumberOrText::Text(value.into_owned()) + } } /// Error type for deserialisation failures @@ -105,5 +113,7 @@ impl std::error::Error for DeserialisationError {} #[cfg(feature = "wasm")] impl From for JsValue { - fn from(error: DeserialisationError) -> Self { JsValue::from_str(&error.message) } + fn from(error: DeserialisationError) -> Self { + JsValue::from_str(&error.message) + } } diff --git a/src/types/span_with_history.rs b/src/types/span_with_history.rs index 7dafa81..1481218 100644 --- a/src/types/span_with_history.rs +++ b/src/types/span_with_history.rs @@ -18,11 +18,17 @@ pub struct SpanWithHistory { #[cfg_attr(feature = "wasm", wasm_bindgen)] impl SpanWithHistory { #[must_use] - pub fn new(text: String, history: History) -> Self { SpanWithHistory { text, history } } + pub fn new(text: String, history: History) -> Self { + SpanWithHistory { text, history } + } #[must_use] - pub fn history(&self) -> History { self.history } + pub fn history(&self) -> History { + self.history + } #[must_use] - pub fn text(&self) -> String { self.text.clone() } + pub fn text(&self) -> String { + self.text.clone() + } } diff --git a/src/types/text_with_cursors.rs b/src/types/text_with_cursors.rs index f58a34e..d796335 100644 --- a/src/types/text_with_cursors.rs +++ b/src/types/text_with_cursors.rs @@ -33,15 +33,21 @@ impl TextWithCursors { } #[must_use] - pub fn text(&self) -> String { self.text.to_string() } + pub fn text(&self) -> String { + self.text.clone() + } #[must_use] - pub fn cursors(&self) -> Vec { self.cursors.clone() } + pub fn cursors(&self) -> Vec { + self.cursors.clone() + } } impl TextWithCursors { #[must_use] - pub fn text_ref(&self) -> &str { &self.text } + pub fn text_ref(&self) -> &str { + &self.text + } } impl<'a> From<&'a str> for TextWithCursors { diff --git a/src/utils/myers_diff.rs b/src/utils/myers_diff.rs index dd9f961..b9a8b25 100644 --- a/src/utils/myers_diff.rs +++ b/src/utils/myers_diff.rs @@ -94,7 +94,9 @@ impl V { } } - fn len(&self) -> usize { self.v.len() } + fn len(&self) -> usize { + self.v.len() + } } impl Index for V { diff --git a/src/utils/string_builder.rs b/src/utils/string_builder.rs index abe372c..3fe42c3 100644 --- a/src/utils/string_builder.rs +++ b/src/utils/string_builder.rs @@ -35,7 +35,9 @@ impl StringBuilder<'_> { } /// Insert a string at the end of the built buffer - pub fn insert(&mut self, text: &str) { self.buffer.push_str(text); } + pub fn insert(&mut self, text: &str) { + self.buffer.push_str(text); + } /// Skip copying `length` characters from the original string to the built /// buffer @@ -64,7 +66,9 @@ impl StringBuilder<'_> { /// Returns the currently built buffer and clears it to allow consuming /// the result incrementally. - pub fn take(&mut self) -> String { std::mem::take(&mut self.buffer) } + pub fn take(&mut self) -> String { + std::mem::take(&mut self.buffer) + } /// Get a slice of the remaining original string. The slice starts from /// where the next delete/retain operation would start and is of length diff --git a/src/wasm.rs b/src/wasm.rs index c15a97f..06cccbe 100644 --- a/src/wasm.rs +++ b/src/wasm.rs @@ -139,13 +139,19 @@ pub struct TextWithCursorsAndHistory { #[wasm_bindgen] impl TextWithCursorsAndHistory { #[must_use] - pub fn text(&self) -> String { self.text_with_cursors.text() } + pub fn text(&self) -> String { + self.text_with_cursors.text() + } #[must_use] - pub fn cursors(&self) -> Vec { self.text_with_cursors.cursors() } + pub fn cursors(&self) -> Vec { + self.text_with_cursors.cursors() + } #[must_use] - pub fn history(&self) -> Vec { self.history.clone() } + pub fn history(&self) -> Vec { + self.history.clone() + } } /// Returns the UTF8 parsed string if it's a text, or `None` if it's likely diff --git a/tests/example_document.rs b/tests/example_document.rs index 6d8f370..b16cd44 100644 --- a/tests/example_document.rs +++ b/tests/example_document.rs @@ -18,7 +18,9 @@ pub struct ExampleDocument { impl ExampleDocument { #[must_use] - pub fn parent(&self) -> String { self.parent.clone() } + pub fn parent(&self) -> String { + self.parent.clone() + } #[must_use] pub fn left(&self) -> TextWithCursors { From cc16505ef9e59c4e8e5f8e14368cf47b2abae8de Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Wed, 11 Mar 2026 21:24:53 +0000 Subject: [PATCH 25/61] Set up publishing --- .github/dependabot.yml | 5 +++ .github/workflows/check.yml | 70 +++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index caff100..f5af792 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -19,3 +19,8 @@ updates: directories: ['/reconcile-js', '/examples/website'] schedule: interval: 'daily' + + - package-ecosystem: 'pip' + directories: ['/reconcile-python'] + schedule: + interval: 'daily' diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index c87edb5..282fcf1 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -24,6 +24,9 @@ jobs: node-version: '22.x' check-latest: true + - name: Install uv + uses: astral-sh/setup-uv@v6 + - name: Cache Rust dependencies uses: actions/cache@v5 with: @@ -115,3 +118,70 @@ jobs: npm publish env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + build-python-wheels: + needs: build + if: startsWith(github.ref, 'refs/tags/') + strategy: + matrix: + include: + - os: ubuntu-latest + target: x86_64 + - os: ubuntu-latest + target: aarch64 + - os: macos-latest + target: x86_64 + - os: macos-latest + target: aarch64 + - os: windows-latest + target: x86_64 + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v6 + + - uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.target }} + args: --release --out dist + manylinux: auto + working-directory: reconcile-python + + - uses: actions/upload-artifact@v4 + with: + name: wheels-${{ matrix.os }}-${{ matrix.target }} + path: reconcile-python/dist/*.whl + + build-python-sdist: + needs: build + if: startsWith(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + + - uses: PyO3/maturin-action@v1 + with: + command: sdist + args: --out dist + working-directory: reconcile-python + + - uses: actions/upload-artifact@v4 + with: + name: sdist + path: reconcile-python/dist/*.tar.gz + + publish-pypi: + needs: [build-python-wheels, build-python-sdist] + runs-on: ubuntu-latest + permissions: + id-token: write + + steps: + - uses: actions/download-artifact@v4 + with: + pattern: '{wheels-*,sdist}' + merge-multiple: true + path: dist + + - uses: pypa/gh-action-pypi-publish@release/v1 From abe1feef099b8be2ee5867826e0d398966d6e030 Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Wed, 11 Mar 2026 21:26:28 +0000 Subject: [PATCH 26/61] Bump versions to 0.9.0 --- Cargo.lock | 2 +- Cargo.toml | 2 +- examples/website/package-lock.json | 2 +- reconcile-js/package-lock.json | 6 +++--- reconcile-js/package.json | 2 +- reconcile-python/Cargo.toml | 2 +- reconcile-python/pyproject.toml | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1967419..fec337b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -299,7 +299,7 @@ dependencies = [ [[package]] name = "reconcile-text" -version = "0.8.0" +version = "0.9.0" dependencies = [ "console_error_panic_hook", "diff-match-patch-rs", diff --git a/Cargo.toml b/Cargo.toml index 3616ebc..b2ce90f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "reconcile-text" description = "Intelligent 3-way text merging with automated conflict resolution" -version = "0.8.0" +version = "0.9.0" rust-version = "1.94" authors = ["Andras Schmelczer "] edition = "2024" diff --git a/examples/website/package-lock.json b/examples/website/package-lock.json index 97002d9..a3120c4 100644 --- a/examples/website/package-lock.json +++ b/examples/website/package-lock.json @@ -28,7 +28,7 @@ }, "../../reconcile-js": { "name": "reconcile-text", - "version": "0.8.0", + "version": "0.9.0", "dev": true, "license": "MIT", "devDependencies": { diff --git a/reconcile-js/package-lock.json b/reconcile-js/package-lock.json index cbb37dd..a8177a1 100644 --- a/reconcile-js/package-lock.json +++ b/reconcile-js/package-lock.json @@ -1,12 +1,12 @@ { "name": "reconcile-text", - "version": "0.8.0", + "version": "0.9.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "reconcile-text", - "version": "0.8.0", + "version": "0.9.0", "license": "MIT", "devDependencies": { "@types/jest": "^30.0.0", @@ -24,7 +24,7 @@ }, "../pkg": { "name": "reconcile-text", - "version": "0.8.0", + "version": "0.9.0", "dev": true, "license": "MIT" }, diff --git a/reconcile-js/package.json b/reconcile-js/package.json index 451d6fe..f1e9021 100644 --- a/reconcile-js/package.json +++ b/reconcile-js/package.json @@ -1,6 +1,6 @@ { "name": "reconcile-text", - "version": "0.8.0", + "version": "0.9.0", "description": "Intelligent 3-way text merging with automated conflict resolution", "main": "dist/reconcile.node.js", "browser": "dist/reconcile.web.js", diff --git a/reconcile-python/Cargo.toml b/reconcile-python/Cargo.toml index 0f8d7e1..5515c79 100644 --- a/reconcile-python/Cargo.toml +++ b/reconcile-python/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reconcile-text-python" -version = "0.8.0" +version = "0.9.0" edition = "2024" rust-version = "1.94" authors = ["Andras Schmelczer "] diff --git a/reconcile-python/pyproject.toml b/reconcile-python/pyproject.toml index 92f185d..a256177 100644 --- a/reconcile-python/pyproject.toml +++ b/reconcile-python/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "reconcile-text" -version = "0.8.0" +version = "0.9.0" description = "Intelligent 3-way text merging with automated conflict resolution" readme = "../README.md" license = { text = "MIT" } From 0ce211177ce63f27a786c85e04185fe3f668a426 Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Wed, 11 Mar 2026 21:59:54 +0000 Subject: [PATCH 27/61] Don't focus on open --- examples/website/src/index.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/examples/website/src/index.ts b/examples/website/src/index.ts index 01edfd8..232e843 100644 --- a/examples/website/src/index.ts +++ b/examples/website/src/index.ts @@ -48,7 +48,6 @@ async function main(): Promise { loadSample(); updateMergedText(); - focusTextArea(leftTextArea); } // Edit the instructions to generate example edits @@ -212,12 +211,6 @@ function autoResize(textarea: HTMLTextAreaElement): void { textarea.style.height = textarea.scrollHeight + 'px'; } -function focusTextArea(textarea: HTMLTextAreaElement): void { - textarea.focus(); - textarea.selectionStart = 0; - textarea.selectionEnd = 0; -} - main().catch((error) => { document.body.textContent = 'Failed to load the application. Please ensure your browser supports WebAssembly.'; From 87fc848bfc3a754cb7c8cd06f4fcca061cb0dfbc Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Wed, 11 Mar 2026 22:00:18 +0000 Subject: [PATCH 28/61] Bump dependencies --- Cargo.lock | 447 +++++++-- Cargo.toml | 12 +- examples/website/package-lock.json | 765 +++++++++----- examples/website/package.json | 24 +- reconcile-js/package-lock.json | 1508 ++++++++++++++++++++-------- reconcile-js/package.json | 12 +- reconcile-python/Cargo.lock | 71 +- reconcile-python/Cargo.toml | 2 +- 8 files changed, 2050 insertions(+), 791 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fec337b..e72b02d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,12 @@ dependencies = [ "libc", ] +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + [[package]] name = "async-trait" version = "0.1.89" @@ -29,10 +35,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] -name = "bumpalo" -version = "3.19.0" +name = "bitflags" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "cast" @@ -42,9 +54,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.46" +version = "1.2.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" dependencies = [ "find-msvc-tools", "shlex", @@ -125,17 +137,91 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + [[package]] name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "foldhash" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", + "wasip3", +] [[package]] name = "hashbrown" -version = "0.16.0" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "iana-time-zone" @@ -162,53 +248,74 @@ dependencies = [ ] [[package]] -name = "indexmap" -version = "2.12.0" +name = "id-arena" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.16.1", + "serde", + "serde_core", ] [[package]] name = "insta" -version = "1.44.3" +version = "1.46.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5c943d4415edd8153251b6f197de5eb1640e56d84e8d9159bea190421c73698" +checksum = "e82db8c87c7f1ccecb34ce0c24399b8a73081427f3c7c50a5d597925356115e4" dependencies = [ "console", "once_cell", "similar", + "tempfile", ] [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "js-sys" -version = "0.3.83" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" dependencies = [ "once_cell", "wasm-bindgen", ] [[package]] -name = "libc" -version = "0.2.177" +name = "leb128fmt" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.183" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "libm" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "log" @@ -218,15 +325,15 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "minicov" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f27fe9f1cc3c22e1687f9446c2083c4c5fc7f0bcf1c7a86bdbded14985895b4b" +checksum = "4869b6a491569605d66d3952bcdf03df789e5b536e5f0cf7758a7f08a55ae24d" dependencies = [ "cc", "walkdir", @@ -269,6 +376,12 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + [[package]] name = "pretty_assertions" version = "1.4.1" @@ -280,23 +393,39 @@ dependencies = [ ] [[package]] -name = "proc-macro2" -version = "1.0.103" +name = "prettyplease" +version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.42" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "reconcile-text" version = "0.9.0" @@ -313,6 +442,19 @@ dependencies = [ "wasm-bindgen-test", ] +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + [[package]] name = "rustversion" version = "1.0.22" @@ -321,9 +463,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "same-file" @@ -334,6 +476,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + [[package]] name = "serde" version = "1.0.228" @@ -366,15 +514,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -403,16 +551,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" [[package]] -name = "syn" -version = "2.0.110" +name = "slab" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + [[package]] name = "test-case" version = "3.3.1" @@ -468,9 +635,15 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "unsafe-libyaml" @@ -489,10 +662,28 @@ dependencies = [ ] [[package]] -name = "wasm-bindgen" -version = "0.2.106" +name = "wasip2" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" dependencies = [ "cfg-if", "once_cell", @@ -503,11 +694,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.56" +version = "0.4.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" +checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" dependencies = [ "cfg-if", + "futures-util", "js-sys", "once_cell", "wasm-bindgen", @@ -516,9 +708,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.106" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -526,9 +718,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.106" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" dependencies = [ "bumpalo", "proc-macro2", @@ -539,18 +731,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.106" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" dependencies = [ "unicode-ident", ] [[package]] name = "wasm-bindgen-test" -version = "0.3.56" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25e90e66d265d3a1efc0e72a54809ab90b9c0c515915c67cdf658689d2c22c6c" +checksum = "6311c867385cc7d5602463b31825d454d0837a3aba7cdb5e56d5201792a3f7fe" dependencies = [ "async-trait", "cast", @@ -565,13 +757,14 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test-macro", + "wasm-bindgen-test-shared", ] [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.56" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7150335716dce6028bead2b848e72f47b45e7b9422f64cccdc23bedca89affc1" +checksum = "67008cdde4769831958536b0f11b3bdd0380bde882be17fff9c2f34bb4549abd" dependencies = [ "proc-macro2", "quote", @@ -579,10 +772,50 @@ dependencies = [ ] [[package]] -name = "web-sys" -version = "0.3.83" +name = "wasm-bindgen-test-shared" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +checksum = "cfe29135b180b72b04c74aa97b2b4a2ef275161eff9a6c7955ea9eaedc7e1d4e" + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" dependencies = [ "js-sys", "wasm-bindgen", @@ -738,8 +971,102 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + [[package]] name = "yansi" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index b2ce90f..a4bef26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,10 +25,10 @@ name = "compare-with-diff-match-patch" path = "examples/compare-with-diff-match-patch.rs" [dependencies] -serde = { version = "1.0.219", optional = true, features = ["derive"] } +serde = { version = "1.0.228", optional = true, features = ["derive"] } thiserror = "2.0.18" -wasm-bindgen = { version = "0.2.99", optional = true } +wasm-bindgen = { version = "0.2.114", optional = true } # The `console_error_panic_hook` crate provides better debugging of panics by # logging them with `console.error`. This is great for development, but requires @@ -44,13 +44,13 @@ console_error_panic_hook = [ "dep:console_error_panic_hook" ] all = [ "wasm", "serde" ] [dev-dependencies] -insta = "1.44.3" +insta = "1.46.3" pretty_assertions = "1.4.1" -serde = { version = "1.0.219", features = ["derive"] } +serde = { version = "1.0.228", features = ["derive"] } serde_yaml = "0.9.34" test-case = "3.3.1" -wasm-bindgen-test = "0.3.56" -diff-match-patch-rs = "0.5" +wasm-bindgen-test = "0.3.64" +diff-match-patch-rs = "0.5.1" [profile.release] codegen-units = 1 diff --git a/examples/website/package-lock.json b/examples/website/package-lock.json index a3120c4..a7abd88 100644 --- a/examples/website/package-lock.json +++ b/examples/website/package-lock.json @@ -7,23 +7,23 @@ "name": "reconcile-example-website", "license": "GPL-3.0-or-later", "devDependencies": { - "copy-webpack-plugin": "^13.0.0", - "css-loader": "^7.1.2", - "html-webpack-plugin": "^5.6.3", + "copy-webpack-plugin": "^14.0.0", + "css-loader": "^7.1.4", + "html-webpack-plugin": "^5.6.6", "inline-source-webpack-plugin": "^3.0.1", - "mini-css-extract-plugin": "^2.9.2", - "prettier": "^3.6.2", + "mini-css-extract-plugin": "^2.10.1", + "prettier": "^3.8.1", "reconcile-text": "file:../../reconcile-js", "resolve-url-loader": "^5.0.0", - "sass": "^1.89.2", - "sass-loader": "^16.0.5", + "sass": "^1.98.0", + "sass-loader": "^16.0.7", "svg-inline-loader": "^0.8.2", - "terser-webpack-plugin": "^5.3.14", - "ts-loader": "^9.5.2", - "typescript": "^5.8.3", - "webpack": "^5.99.9", + "terser-webpack-plugin": "^5.4.0", + "ts-loader": "^9.5.4", + "typescript": "^5.9.3", + "webpack": "^5.105.4", "webpack-cli": "^6.0.1", - "webpack-dev-server": "^5.2.2" + "webpack-dev-server": "^5.2.3" } }, "../../reconcile-js": { @@ -169,6 +169,19 @@ "dev": true, "license": "MIT" }, + "node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@parcel/watcher": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", @@ -479,6 +492,165 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/@peculiar/asn1-cms": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-cms/-/asn1-cms-2.6.1.tgz", + "integrity": "sha512-vdG4fBF6Lkirkcl53q6eOdn3XYKt+kJTG59edgRZORlg/3atWWEReRCx5rYE1ZzTTX6vLK5zDMjHh7vbrcXGtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "@peculiar/asn1-x509-attr": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-csr": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-csr/-/asn1-csr-2.6.1.tgz", + "integrity": "sha512-WRWnKfIocHyzFYQTka8O/tXCiBquAPSrRjXbOkHbO4qdmS6loffCEGs+rby6WxxGdJCuunnhS2duHURhjyio6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-ecc": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.6.1.tgz", + "integrity": "sha512-+Vqw8WFxrtDIN5ehUdvlN2m73exS2JVG0UAyfVB31gIfor3zWEAQPD+K9ydCxaj3MLen9k0JhKpu9LqviuCE1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pfx": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pfx/-/asn1-pfx-2.6.1.tgz", + "integrity": "sha512-nB5jVQy3MAAWvq0KY0R2JUZG8bO/bTLpnwyOzXyEh/e54ynGTatAR+csOnXkkVD9AFZ2uL8Z7EV918+qB1qDvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.6.1", + "@peculiar/asn1-pkcs8": "^2.6.1", + "@peculiar/asn1-rsa": "^2.6.1", + "@peculiar/asn1-schema": "^2.6.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pkcs8": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs8/-/asn1-pkcs8-2.6.1.tgz", + "integrity": "sha512-JB5iQ9Izn5yGMw3ZG4Nw3Xn/hb/G38GYF3lf7WmJb8JZUydhVGEjK/ZlFSWhnlB7K/4oqEs8HnfFIKklhR58Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pkcs9": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs9/-/asn1-pkcs9-2.6.1.tgz", + "integrity": "sha512-5EV8nZoMSxeWmcxWmmcolg22ojZRgJg+Y9MX2fnE2bGRo5KQLqV5IL9kdSQDZxlHz95tHvIq9F//bvL1OeNILw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.6.1", + "@peculiar/asn1-pfx": "^2.6.1", + "@peculiar/asn1-pkcs8": "^2.6.1", + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "@peculiar/asn1-x509-attr": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-rsa": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.6.1.tgz", + "integrity": "sha512-1nVMEh46SElUt5CB3RUTV4EG/z7iYc7EoaDY5ECwganibQPkZ/Y2eMsTKB/LeyrUJ+W/tKoD9WUqIy8vB+CEdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-schema": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.6.0.tgz", + "integrity": "sha512-xNLYLBFTBKkCzEZIw842BxytQQATQv+lDTCEMZ8C196iJcJJMBUZxrhSTxLaohMyKK8QlzRNTRkUmanucnDSqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "asn1js": "^3.0.6", + "pvtsutils": "^1.3.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-x509": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.6.1.tgz", + "integrity": "sha512-O9jT5F1A2+t3r7C4VT7LYGXqkGLK7Kj1xFpz7U0isPrubwU5PbDoyYtx6MiGst29yq7pXN5vZbQFKRCP+lLZlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "asn1js": "^3.0.6", + "pvtsutils": "^1.3.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-x509-attr": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509-attr/-/asn1-x509-attr-2.6.1.tgz", + "integrity": "sha512-tlW6cxoHwgcQghnJwv3YS+9OO1737zgPogZ+CgWRUK4roEwIPzRH4JEiG770xe5HX2ATfCpmX60gurfWIF9dcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/x509": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/@peculiar/x509/-/x509-1.14.3.tgz", + "integrity": "sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.6.0", + "@peculiar/asn1-csr": "^2.6.0", + "@peculiar/asn1-ecc": "^2.6.0", + "@peculiar/asn1-pkcs9": "^2.6.0", + "@peculiar/asn1-rsa": "^2.6.0", + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "pvtsutils": "^1.3.6", + "reflect-metadata": "^0.2.2", + "tslib": "^2.8.1", + "tsyringe": "^4.10.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@trysound/sax": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", @@ -561,16 +733,16 @@ "license": "MIT" }, "node_modules/@types/express": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz", - "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==", + "version": "4.17.25", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", + "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", "dev": true, "license": "MIT", "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", "@types/qs": "*", - "@types/serve-static": "*" + "@types/serve-static": "^1" } }, "node_modules/@types/express-serve-static-core": { @@ -647,16 +819,6 @@ "undici-types": "~7.8.0" } }, - "node_modules/@types/node-forge": { - "version": "1.3.12", - "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.12.tgz", - "integrity": "sha512-a0ToKlRVnUw3aXKQq2F+krxZKq7B8LEQijzPn5RdFAMatARD2JX9o8FBpMXOOrjob0uc13aN+V/AXniOXW4d9A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", @@ -978,9 +1140,9 @@ } }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", "bin": { @@ -990,6 +1152,19 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "acorn": "^8.14.0" + } + }, "node_modules/adjust-sourcemap-loader": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz", @@ -1112,6 +1287,34 @@ "dev": true, "license": "MIT" }, + "node_modules/asn1js": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.7.tgz", + "integrity": "sha512-uLvq6KJu04qoQM6gvBfKFjlh6Gl0vOKQuR5cJMDHQkmwfMOQeN3F3SHCv9SNYSL+CRoHvOGFfllDlVz03GQjvQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", + "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", @@ -1143,24 +1346,24 @@ } }, "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", "dev": true, "license": "MIT", "dependencies": { - "bytes": "3.1.2", + "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", "type-is": "~1.6.18", - "unpipe": "1.0.0" + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8", @@ -1199,9 +1402,9 @@ } }, "node_modules/browserslist": { - "version": "4.25.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", - "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "dev": true, "funding": [ { @@ -1219,10 +1422,11 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001726", - "electron-to-chromium": "^1.5.173", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" @@ -1264,6 +1468,16 @@ "node": ">= 0.8" } }, + "node_modules/bytestreamjs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/bytestreamjs/-/bytestreamjs-2.0.1.tgz", + "integrity": "sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -1307,9 +1521,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001726", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz", - "integrity": "sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==", + "version": "1.0.30001777", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001777.tgz", + "integrity": "sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ==", "dev": true, "funding": [ { @@ -1462,9 +1676,9 @@ } }, "node_modules/compression": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.0.tgz", - "integrity": "sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", "dev": true, "license": "MIT", "dependencies": { @@ -1472,7 +1686,7 @@ "compressible": "~2.0.18", "debug": "2.6.9", "negotiator": "~0.6.4", - "on-headers": "~1.0.2", + "on-headers": "~1.1.0", "safe-buffer": "5.2.1", "vary": "~1.1.2" }, @@ -1521,9 +1735,9 @@ "license": "MIT" }, "node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "dev": true, "license": "MIT", "engines": { @@ -1531,27 +1745,27 @@ } }, "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", "dev": true, "license": "MIT" }, "node_modules/copy-webpack-plugin": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-13.0.0.tgz", - "integrity": "sha512-FgR/h5a6hzJqATDGd9YG41SeDViH+0bkHn6WNXCi5zKAZkeESeSxLySSsFLHqLEVCh0E+rITmCf0dusXWYukeQ==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-14.0.0.tgz", + "integrity": "sha512-3JLW90aBGeaTLpM7mYQKpnVdgsUZRExY55giiZgLuX/xTQRUs1dOCwbBnWnvY6Q6rfZoXMNwzOQJCSZPppfqXA==", "dev": true, "license": "MIT", "dependencies": { "glob-parent": "^6.0.1", "normalize-path": "^3.0.0", "schema-utils": "^4.2.0", - "serialize-javascript": "^6.0.2", + "serialize-javascript": "^7.0.3", "tinyglobby": "^0.2.12" }, "engines": { - "node": ">= 18.12.0" + "node": ">= 20.9.0" }, "funding": { "type": "opencollective", @@ -1597,20 +1811,20 @@ } }, "node_modules/css-loader": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz", - "integrity": "sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.4.tgz", + "integrity": "sha512-vv3J9tlOl04WjiMvHQI/9tmIrCxVrj6PFbHemBB1iihpeRbi/I4h033eoFIhwxBBqLhI0KYFS7yvynBFhIZfTw==", "dev": true, "license": "MIT", "dependencies": { "icss-utils": "^5.1.0", - "postcss": "^8.4.33", + "postcss": "^8.4.40", "postcss-modules-extract-imports": "^3.1.0", "postcss-modules-local-by-default": "^4.0.5", "postcss-modules-scope": "^3.2.0", "postcss-modules-values": "^4.0.0", "postcss-value-parser": "^4.2.0", - "semver": "^7.5.4" + "semver": "^7.6.3" }, "engines": { "node": ">= 18.12.0" @@ -1620,7 +1834,7 @@ "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "@rspack/core": "0.x || 1.x", + "@rspack/core": "0.x || ^1.0.0 || ^2.0.0-0", "webpack": "^5.27.0" }, "peerDependenciesMeta": { @@ -1981,9 +2195,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.179", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.179.tgz", - "integrity": "sha512-UWKi/EbBopgfFsc5k61wFpV7WrnnSlSzW/e2XcBmS6qKYTivZlLtoll5/rdqRTxGglGHkmkW0j0pFNJG10EUIQ==", + "version": "1.5.307", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.307.tgz", + "integrity": "sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==", "dev": true, "license": "ISC" }, @@ -2008,14 +2222,14 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.18.2", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz", - "integrity": "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==", + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz", + "integrity": "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==", "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" + "tapable": "^2.3.0" }, "engines": { "node": ">=10.13.0" @@ -2068,9 +2282,9 @@ } }, "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", "dev": true, "license": "MIT" }, @@ -2179,40 +2393,40 @@ } }, "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", "dev": true, "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", + "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.13.0", + "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", + "send": "~0.19.0", + "serve-static": "~1.16.2", "setprototypeof": "1.2.0", - "statuses": "2.0.1", + "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -2310,18 +2524,18 @@ } }, "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", "dev": true, "license": "MIT", "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "statuses": "2.0.1", + "statuses": "~2.0.2", "unpipe": "~1.0.0" }, "engines": { @@ -2632,9 +2846,9 @@ } }, "node_modules/html-webpack-plugin": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.3.tgz", - "integrity": "sha512-QSf1yjtSAsmf7rYBV7XX86uua4W/vkhIt0xNXKbsi2foEeW7vjJQz4bhnpL3xH+l1ryl1680uNv968Z+X6jSYg==", + "version": "5.6.6", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.6.tgz", + "integrity": "sha512-bLjW01UTrvoWTJQL5LsMRo1SypHW80FTm12OJRSnr3v6YHNhfe+1r0MYUZJMACxnCHURVnBWRwAsWs2yPU9Ezw==", "dev": true, "license": "MIT", "dependencies": { @@ -2692,20 +2906,24 @@ "license": "MIT" }, "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "dev": true, "license": "MIT", "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/http-parser-js": { @@ -2792,9 +3010,9 @@ } }, "node_modules/immutable": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.3.tgz", - "integrity": "sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.5.tgz", + "integrity": "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==", "dev": true, "license": "MIT" }, @@ -3136,13 +3354,17 @@ } }, "node_modules/loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", "dev": true, "license": "MIT", "engines": { "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/loader-utils": { @@ -3315,9 +3537,9 @@ } }, "node_modules/mini-css-extract-plugin": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.2.tgz", - "integrity": "sha512-GJuACcS//jtq4kCtd5ii/M0SZf7OZRH+BxdqXZHaJfb8TJiVl+NgQRPwiYt2EuqeSkNydn/7vP+bcE27C5mb9w==", + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.10.1.tgz", + "integrity": "sha512-k7G3Y5QOegl380tXmZ68foBRRjE9Ljavx835ObdvmZjQ639izvZD8CS7BkWw1qKPPzHsGL/JDhl0uyU1zc2rJw==", "dev": true, "license": "MIT", "dependencies": { @@ -3468,20 +3690,10 @@ "url": "https://opencollective.com/node-fetch" } }, - "node_modules/node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", - "dev": true, - "license": "(BSD-3-Clause OR GPL-2.0)", - "engines": { - "node": ">= 6.13.0" - } - }, "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", "dev": true, "license": "MIT" }, @@ -3552,9 +3764,9 @@ } }, "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", "dev": true, "license": "MIT", "engines": { @@ -3736,6 +3948,24 @@ "node": ">=8" } }, + "node_modules/pkijs": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.3.3.tgz", + "integrity": "sha512-+KD8hJtqQMYoTuL1bbGOqxb4z+nZkTAwVdNtWwe8Tc2xNbEmdJYIYoc6Qt0uF55e6YW6KuTHw1DjQ18gMhzepw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@noble/hashes": "1.4.0", + "asn1js": "^3.0.6", + "bytestreamjs": "^2.0.1", + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", @@ -3850,9 +4080,9 @@ "license": "MIT" }, "node_modules/prettier": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", - "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "dev": true, "license": "MIT", "bin": { @@ -3907,14 +4137,34 @@ "node": ">= 0.10" } }, + "node_modules/pvtsutils": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", + "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/pvutils": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.5.tgz", + "integrity": "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.6" + "side-channel": "^1.1.0" }, "engines": { "node": ">=0.6" @@ -3923,16 +4173,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -3944,16 +4184,16 @@ } }, "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", "dev": true, "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8" @@ -4005,6 +4245,13 @@ "resolved": "../../reconcile-js", "link": true }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/regex-parser": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.3.1.tgz", @@ -4242,14 +4489,14 @@ "license": "MIT" }, "node_modules/sass": { - "version": "1.89.2", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.89.2.tgz", - "integrity": "sha512-xCmtksBKd/jdJ9Bt9p7nPKiuqrlBMBuuGkQlkhZjjQk3Ty48lv93k5Dq6OPkKt4XwxDJ7tvlfrTa1MPA9bf+QA==", + "version": "1.98.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.98.0.tgz", + "integrity": "sha512-+4N/u9dZ4PrgzGgPlKnaaRQx64RO0JBKs9sDhQ2pLgN6JQZ25uPQZKQYaBJU48Kd5BxgXoJ4e09Dq7nMcOUW3A==", "dev": true, "license": "MIT", "dependencies": { "chokidar": "^4.0.0", - "immutable": "^5.0.2", + "immutable": "^5.1.5", "source-map-js": ">=0.6.2 <2.0.0" }, "bin": { @@ -4263,9 +4510,9 @@ } }, "node_modules/sass-loader": { - "version": "16.0.5", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.5.tgz", - "integrity": "sha512-oL+CMBXrj6BZ/zOq4os+UECPL+bWqt6OAC6DWS8Ln8GZRcMDjlJ4JC3FBDuHJdYaFWIdKNIBYmtZtK2MaMkNIw==", + "version": "16.0.7", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.7.tgz", + "integrity": "sha512-w6q+fRHourZ+e+xA1kcsF27iGM6jdB8teexYCfdUw0sYgcDNeZESnDNT9sUmmPm3ooziwUJXGwZJSTF3kOdBfA==", "dev": true, "license": "MIT", "dependencies": { @@ -4279,7 +4526,7 @@ "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "@rspack/core": "0.x || 1.x", + "@rspack/core": "0.x || ^1.0.0 || ^2.0.0-0", "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", "sass": "^1.3.0", "sass-embedded": "*", @@ -4304,9 +4551,9 @@ } }, "node_modules/schema-utils": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", - "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "dev": true, "license": "MIT", "dependencies": { @@ -4331,17 +4578,17 @@ "license": "MIT" }, "node_modules/selfsigned": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", - "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-5.5.0.tgz", + "integrity": "sha512-ftnu3TW4+3eBfLRFnDEkzGxSF/10BJBkaLJuBHZX0kiPS7bRdlpZGu6YGt4KngMkdTwJE6MbjavFpqHvqVt+Ew==", "dev": true, "license": "MIT", "dependencies": { - "@types/node-forge": "^1.3.0", - "node-forge": "^1" + "@peculiar/x509": "^1.14.2", + "pkijs": "^3.3.3" }, "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/semver": { @@ -4358,40 +4605,30 @@ } }, "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", "dev": true, "license": "MIT", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "range-parser": "~1.2.1", - "statuses": "2.0.1" + "statuses": "~2.0.2" }, "engines": { "node": ">= 0.8.0" } }, - "node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -4400,13 +4637,13 @@ "license": "MIT" }, "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.4.tgz", + "integrity": "sha512-DuGdB+Po43Q5Jxwpzt1lhyFSYKryqoNjQSA9M92tyw0lyHIOur+XCalOUe0KTJpyqzT8+fQ5A0Jf7vCx/NKmIg==", "dev": true, "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" + "engines": { + "node": ">=20.0.0" } }, "node_modules/serve-index": { @@ -4479,16 +4716,16 @@ } }, "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", "dev": true, "license": "MIT", "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.19.0" + "send": "~0.19.1" }, "engines": { "node": ">= 0.8.0" @@ -4759,9 +4996,9 @@ "license": "MIT" }, "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "dev": true, "license": "MIT", "engines": { @@ -4935,13 +5172,17 @@ "license": "CC0-1.0" }, "node_modules/tapable": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", - "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", "dev": true, "license": "MIT", "engines": { "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/terser": { @@ -4964,16 +5205,15 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.14", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", - "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.4.0.tgz", + "integrity": "sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==", "dev": true, "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", "schema-utils": "^4.3.0", - "serialize-javascript": "^6.0.2", "terser": "^5.31.1" }, "engines": { @@ -5111,9 +5351,9 @@ } }, "node_modules/ts-loader": { - "version": "9.5.2", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.2.tgz", - "integrity": "sha512-Qo4piXvOTWcMGIgRiuFa6nHNm+54HbYaZCKqc9eeZCLRy3XqafQgwX2F7mofrbJG3g7EEb+lkiR+z2Lic2s3Zw==", + "version": "9.5.4", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.4.tgz", + "integrity": "sha512-nCz0rEwunlTZiy6rXFByQU1kVVpCIgUpc/psFiKVrUwrizdnIbRFu8w7bxhUF0X613DYwT4XzrZHpVyMe758hQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5148,6 +5388,26 @@ "dev": true, "license": "0BSD" }, + "node_modules/tsyringe": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/tsyringe/-/tsyringe-4.10.0.tgz", + "integrity": "sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^1.9.3" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/tsyringe/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true, + "license": "0BSD" + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -5163,9 +5423,9 @@ } }, "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -5194,9 +5454,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "dev": true, "funding": [ { @@ -5269,9 +5529,9 @@ } }, "node_modules/watchpack": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", - "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", + "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", "dev": true, "license": "MIT", "dependencies": { @@ -5303,36 +5563,37 @@ } }, "node_modules/webpack": { - "version": "5.99.9", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.9.tgz", - "integrity": "sha512-brOPwM3JnmOa+7kd3NsmOUOwbDAj8FT9xDsG3IW0MgbN9yZV7Oi/s/+MNQ/EcSMqw7qfoRyXPoeEWT8zLVdVGg==", + "version": "5.105.4", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.4.tgz", + "integrity": "sha512-jTywjboN9aHxFlToqb0K0Zs9SbBoW4zRUlGzI2tYNxVYcEi/IPpn+Xi4ye5jTLvX2YeLuic/IvxNot+Q1jMoOw==", "dev": true, "license": "MIT", "dependencies": { "@types/eslint-scope": "^3.7.7", - "@types/estree": "^1.0.6", + "@types/estree": "^1.0.8", "@types/json-schema": "^7.0.15", "@webassemblyjs/ast": "^1.14.1", "@webassemblyjs/wasm-edit": "^1.14.1", "@webassemblyjs/wasm-parser": "^1.14.1", - "acorn": "^8.14.0", - "browserslist": "^4.24.0", + "acorn": "^8.16.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.1", - "es-module-lexer": "^1.2.1", + "enhanced-resolve": "^5.20.0", + "es-module-lexer": "^2.0.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", + "loader-runner": "^4.3.1", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^4.3.2", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.11", - "watchpack": "^2.4.1", - "webpack-sources": "^3.2.3" + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.3.17", + "watchpack": "^2.5.1", + "webpack-sources": "^3.3.4" }, "bin": { "webpack": "bin/webpack.js" @@ -5434,15 +5695,15 @@ } }, "node_modules/webpack-dev-server": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.2.tgz", - "integrity": "sha512-QcQ72gh8a+7JO63TAx/6XZf/CWhgMzu5m0QirvPfGvptOusAxG12w2+aua1Jkjr7hzaWDnJ2n6JFeexMHI+Zjg==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.3.tgz", + "integrity": "sha512-9Gyu2F7+bg4Vv+pjbovuYDhHX+mqdqITykfzdM9UyKqKHlsE5aAjRhR+oOEfXW5vBeu8tarzlJFIZva4ZjAdrQ==", "dev": true, "license": "MIT", "dependencies": { "@types/bonjour": "^3.5.13", "@types/connect-history-api-fallback": "^1.5.4", - "@types/express": "^4.17.21", + "@types/express": "^4.17.25", "@types/express-serve-static-core": "^4.17.21", "@types/serve-index": "^1.9.4", "@types/serve-static": "^1.15.5", @@ -5452,9 +5713,9 @@ "bonjour-service": "^1.2.1", "chokidar": "^3.6.0", "colorette": "^2.0.10", - "compression": "^1.7.4", + "compression": "^1.8.1", "connect-history-api-fallback": "^2.0.0", - "express": "^4.21.2", + "express": "^4.22.1", "graceful-fs": "^4.2.6", "http-proxy-middleware": "^2.0.9", "ipaddr.js": "^2.1.0", @@ -5462,7 +5723,7 @@ "open": "^10.0.3", "p-retry": "^6.2.0", "schema-utils": "^4.2.0", - "selfsigned": "^2.4.1", + "selfsigned": "^5.5.0", "serve-index": "^1.9.1", "sockjs": "^0.3.24", "spdy": "^4.0.2", @@ -5558,9 +5819,9 @@ } }, "node_modules/webpack-sources": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", - "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.4.tgz", + "integrity": "sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==", "dev": true, "license": "MIT", "engines": { diff --git a/examples/website/package.json b/examples/website/package.json index 23588ff..9a16f32 100644 --- a/examples/website/package.json +++ b/examples/website/package.json @@ -22,22 +22,22 @@ ], "homepage": "https://github.com/schmelczer/reconcile#readme", "devDependencies": { - "copy-webpack-plugin": "^13.0.0", - "css-loader": "^7.1.2", - "html-webpack-plugin": "^5.6.3", + "copy-webpack-plugin": "^14.0.0", + "css-loader": "^7.1.4", + "html-webpack-plugin": "^5.6.6", "inline-source-webpack-plugin": "^3.0.1", - "mini-css-extract-plugin": "^2.9.2", - "prettier": "^3.6.2", + "mini-css-extract-plugin": "^2.10.1", + "prettier": "^3.8.1", "reconcile-text": "file:../../reconcile-js", "resolve-url-loader": "^5.0.0", - "sass": "^1.89.2", - "sass-loader": "^16.0.5", + "sass": "^1.98.0", + "sass-loader": "^16.0.7", "svg-inline-loader": "^0.8.2", - "terser-webpack-plugin": "^5.3.14", - "ts-loader": "^9.5.2", - "typescript": "^5.8.3", - "webpack": "^5.99.9", + "terser-webpack-plugin": "^5.4.0", + "ts-loader": "^9.5.4", + "typescript": "^5.9.3", + "webpack": "^5.105.4", "webpack-cli": "^6.0.1", - "webpack-dev-server": "^5.2.2" + "webpack-dev-server": "^5.2.3" } } diff --git a/reconcile-js/package-lock.json b/reconcile-js/package-lock.json index a8177a1..d8c38a5 100644 --- a/reconcile-js/package-lock.json +++ b/reconcile-js/package-lock.json @@ -10,14 +10,14 @@ "license": "MIT", "devDependencies": { "@types/jest": "^30.0.0", - "jest": "^30.0.4", - "prettier": "^3.6.2", + "jest": "^30.3.0", + "prettier": "^3.8.1", "reconcile-text": "file:../pkg", - "ts-jest": "^29.4.0", - "ts-loader": "^9.5.2", + "ts-jest": "^29.4.6", + "ts-loader": "^9.5.4", "tslib": "2.8.1", - "typescript": "5.8.3", - "webpack": "^5.99.9", + "typescript": "5.9.3", + "webpack": "^5.105.4", "webpack-cli": "^6.0.1", "webpack-merge": "^6.0.1" } @@ -157,7 +157,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", "dev": true, "license": "MIT", "engines": { @@ -173,7 +175,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, "license": "MIT", "engines": { @@ -216,6 +220,8 @@ }, "node_modules/@babel/plugin-syntax-async-generators": { "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", "dev": true, "license": "MIT", "dependencies": { @@ -227,6 +233,8 @@ }, "node_modules/@babel/plugin-syntax-bigint": { "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", "dev": true, "license": "MIT", "dependencies": { @@ -238,6 +246,8 @@ }, "node_modules/@babel/plugin-syntax-class-properties": { "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", "dev": true, "license": "MIT", "dependencies": { @@ -249,6 +259,8 @@ }, "node_modules/@babel/plugin-syntax-class-static-block": { "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", "dev": true, "license": "MIT", "dependencies": { @@ -262,11 +274,13 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.27.1", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -277,6 +291,8 @@ }, "node_modules/@babel/plugin-syntax-import-meta": { "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", "dev": true, "license": "MIT", "dependencies": { @@ -288,6 +304,8 @@ }, "node_modules/@babel/plugin-syntax-json-strings": { "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", "dev": true, "license": "MIT", "dependencies": { @@ -298,11 +316,13 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.27.1", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -313,6 +333,8 @@ }, "node_modules/@babel/plugin-syntax-logical-assignment-operators": { "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", "dev": true, "license": "MIT", "dependencies": { @@ -324,6 +346,8 @@ }, "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", "dev": true, "license": "MIT", "dependencies": { @@ -335,6 +359,8 @@ }, "node_modules/@babel/plugin-syntax-numeric-separator": { "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", "dev": true, "license": "MIT", "dependencies": { @@ -346,6 +372,8 @@ }, "node_modules/@babel/plugin-syntax-object-rest-spread": { "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", "dev": true, "license": "MIT", "dependencies": { @@ -357,6 +385,8 @@ }, "node_modules/@babel/plugin-syntax-optional-catch-binding": { "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", "dev": true, "license": "MIT", "dependencies": { @@ -368,6 +398,8 @@ }, "node_modules/@babel/plugin-syntax-optional-chaining": { "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", "dev": true, "license": "MIT", "dependencies": { @@ -379,6 +411,8 @@ }, "node_modules/@babel/plugin-syntax-private-property-in-object": { "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", "dev": true, "license": "MIT", "dependencies": { @@ -393,6 +427,8 @@ }, "node_modules/@babel/plugin-syntax-top-level-await": { "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", "dev": true, "license": "MIT", "dependencies": { @@ -406,11 +442,13 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.27.1", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -450,12 +488,14 @@ } }, "node_modules/@babel/types": { - "version": "7.28.0", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -463,6 +503,8 @@ }, "node_modules/@bcoe/v8-coverage": { "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true, "license": "MIT" }, @@ -474,8 +516,44 @@ "node": ">=14.17.0" } }, + "node_modules/@emnapi/core": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", + "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", + "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "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==", "dev": true, "license": "ISC", "dependencies": { @@ -492,6 +570,8 @@ }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", "dev": true, "license": "ISC", "dependencies": { @@ -514,15 +594,17 @@ } }, "node_modules/@jest/console": { - "version": "30.0.4", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.3.0.tgz", + "integrity": "sha512-PAwCvFJ4696XP2qZj+LAn1BWjZaJ6RjG6c7/lkMaUJnkyMS34ucuIsfqYvfskVNvUI27R/u4P1HMYFnlVXG/Ww==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.0.1", + "@jest/types": "30.3.0", "@types/node": "*", "chalk": "^4.1.2", - "jest-message-util": "30.0.2", - "jest-util": "30.0.2", + "jest-message-util": "30.3.0", + "jest-util": "30.3.0", "slash": "^3.0.0" }, "engines": { @@ -530,37 +612,38 @@ } }, "node_modules/@jest/core": { - "version": "30.0.4", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.3.0.tgz", + "integrity": "sha512-U5mVPsBxLSO6xYbf+tgkymLx+iAhvZX43/xI1+ej2ZOPnPdkdO1CzDmFKh2mZBn2s4XZixszHeQnzp1gm/DIxw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.0.4", + "@jest/console": "30.3.0", "@jest/pattern": "30.0.1", - "@jest/reporters": "30.0.4", - "@jest/test-result": "30.0.4", - "@jest/transform": "30.0.4", - "@jest/types": "30.0.1", + "@jest/reporters": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", "@types/node": "*", "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", "ci-info": "^4.2.0", "exit-x": "^0.2.2", "graceful-fs": "^4.2.11", - "jest-changed-files": "30.0.2", - "jest-config": "30.0.4", - "jest-haste-map": "30.0.2", - "jest-message-util": "30.0.2", + "jest-changed-files": "30.3.0", + "jest-config": "30.3.0", + "jest-haste-map": "30.3.0", + "jest-message-util": "30.3.0", "jest-regex-util": "30.0.1", - "jest-resolve": "30.0.2", - "jest-resolve-dependencies": "30.0.4", - "jest-runner": "30.0.4", - "jest-runtime": "30.0.4", - "jest-snapshot": "30.0.4", - "jest-util": "30.0.2", - "jest-validate": "30.0.2", - "jest-watcher": "30.0.4", - "micromatch": "^4.0.8", - "pretty-format": "30.0.2", + "jest-resolve": "30.3.0", + "jest-resolve-dependencies": "30.3.0", + "jest-runner": "30.3.0", + "jest-runtime": "30.3.0", + "jest-snapshot": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", + "jest-watcher": "30.3.0", + "pretty-format": "30.3.0", "slash": "^3.0.0" }, "engines": { @@ -576,7 +659,9 @@ } }, "node_modules/@jest/diff-sequences": { - "version": "30.0.1", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.3.0.tgz", + "integrity": "sha512-cG51MVnLq1ecVUaQ3fr6YuuAOitHK1S4WUJHnsPFE/quQr33ADUx1FfrTCpMCRxvy0Yr9BThKpDjSlcTi91tMA==", "dev": true, "license": "MIT", "engines": { @@ -584,60 +669,70 @@ } }, "node_modules/@jest/environment": { - "version": "30.0.4", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.3.0.tgz", + "integrity": "sha512-SlLSF4Be735yQXyh2+mctBOzNDx5s5uLv88/j8Qn1wH679PDcwy67+YdADn8NJnGjzlXtN62asGH/T4vWOkfaw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/fake-timers": "30.0.4", - "@jest/types": "30.0.1", + "@jest/fake-timers": "30.3.0", + "@jest/types": "30.3.0", "@types/node": "*", - "jest-mock": "30.0.2" + "jest-mock": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect": { - "version": "30.0.4", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.3.0.tgz", + "integrity": "sha512-76Nlh4xJxk2D/9URCn3wFi98d2hb19uWE1idLsTt2ywhvdOldbw3S570hBgn25P4ICUZ/cBjybrBex2g17IDbg==", "dev": true, "license": "MIT", "dependencies": { - "expect": "30.0.4", - "jest-snapshot": "30.0.4" + "expect": "30.3.0", + "jest-snapshot": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect-utils": { - "version": "30.0.4", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.3.0.tgz", + "integrity": "sha512-j0+W5iQQ8hBh7tHZkTQv3q2Fh/M7Je72cIsYqC4OaktgtO7v1So9UTjp6uPBHIaB6beoF/RRsCgMJKvti0wADA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.0.1" + "@jest/get-type": "30.1.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/fake-timers": { - "version": "30.0.4", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.3.0.tgz", + "integrity": "sha512-WUQDs8SOP9URStX1DzhD425CqbN/HxUYCTwVrT8sTVBfMvFqYt/s61EK5T05qnHu0po6RitXIvP9otZxYDzTGQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.0.1", - "@sinonjs/fake-timers": "^13.0.0", + "@jest/types": "30.3.0", + "@sinonjs/fake-timers": "^15.0.0", "@types/node": "*", - "jest-message-util": "30.0.2", - "jest-mock": "30.0.2", - "jest-util": "30.0.2" + "jest-message-util": "30.3.0", + "jest-mock": "30.3.0", + "jest-util": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/get-type": { - "version": "30.0.1", + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", + "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", "dev": true, "license": "MIT", "engines": { @@ -645,14 +740,16 @@ } }, "node_modules/@jest/globals": { - "version": "30.0.4", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.3.0.tgz", + "integrity": "sha512-+owLCBBdfpgL3HU+BD5etr1SvbXpSitJK0is1kiYjJxAAJggYMRQz5hSdd5pq1sSggfxPbw2ld71pt4x5wwViA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.0.4", - "@jest/expect": "30.0.4", - "@jest/types": "30.0.1", - "jest-mock": "30.0.2" + "@jest/environment": "30.3.0", + "@jest/expect": "30.3.0", + "@jest/types": "30.3.0", + "jest-mock": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -671,30 +768,32 @@ } }, "node_modules/@jest/reporters": { - "version": "30.0.4", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.3.0.tgz", + "integrity": "sha512-a09z89S+PkQnL055bVj8+pe2Caed2PBOaczHcXCykW5ngxX9EWx/1uAwncxc/HiU0oZqfwseMjyhxgRjS49qPw==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "30.0.4", - "@jest/test-result": "30.0.4", - "@jest/transform": "30.0.4", - "@jest/types": "30.0.1", + "@jest/console": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", "@jridgewell/trace-mapping": "^0.3.25", "@types/node": "*", "chalk": "^4.1.2", "collect-v8-coverage": "^1.0.2", "exit-x": "^0.2.2", - "glob": "^10.3.10", + "glob": "^10.5.0", "graceful-fs": "^4.2.11", "istanbul-lib-coverage": "^3.0.0", "istanbul-lib-instrument": "^6.0.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^5.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "30.0.2", - "jest-util": "30.0.2", - "jest-worker": "30.0.2", + "jest-message-util": "30.3.0", + "jest-util": "30.3.0", + "jest-worker": "30.3.0", "slash": "^3.0.0", "string-length": "^4.0.2", "v8-to-istanbul": "^9.0.1" @@ -712,13 +811,15 @@ } }, "node_modules/@jest/reporters/node_modules/jest-worker": { - "version": "30.0.2", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.3.0.tgz", + "integrity": "sha512-DrCKkaQwHexjRUFTmPzs7sHQe0TSj9nvDALKGdwmK5mW9v7j90BudWirKAJHt3QQ9Dhrg1F7DogPzhChppkJpQ==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", "@ungap/structured-clone": "^1.3.0", - "jest-util": "30.0.2", + "jest-util": "30.3.0", "merge-stream": "^2.0.0", "supports-color": "^8.1.1" }, @@ -727,7 +828,9 @@ } }, "node_modules/@jest/schemas": { - "version": "30.0.1", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", "dev": true, "license": "MIT", "dependencies": { @@ -738,11 +841,13 @@ } }, "node_modules/@jest/snapshot-utils": { - "version": "30.0.4", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.3.0.tgz", + "integrity": "sha512-ORbRN9sf5PP82v3FXNSwmO1OTDR2vzR2YTaR+E3VkSBZ8zadQE6IqYdYEeFH1NIkeB2HIGdF02dapb6K0Mj05g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.0.1", + "@jest/types": "30.3.0", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "natural-compare": "^1.4.0" @@ -753,6 +858,8 @@ }, "node_modules/@jest/source-map": { "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", + "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", "dev": true, "license": "MIT", "dependencies": { @@ -765,12 +872,14 @@ } }, "node_modules/@jest/test-result": { - "version": "30.0.4", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.3.0.tgz", + "integrity": "sha512-e/52nJGuD74AKTSe0P4y5wFRlaXP0qmrS17rqOMHeSwm278VyNyXE3gFO/4DTGF9w+65ra3lo3VKj0LBrzmgdQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.0.4", - "@jest/types": "30.0.1", + "@jest/console": "30.3.0", + "@jest/types": "30.3.0", "@types/istanbul-lib-coverage": "^2.0.6", "collect-v8-coverage": "^1.0.2" }, @@ -779,13 +888,15 @@ } }, "node_modules/@jest/test-sequencer": { - "version": "30.0.4", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.3.0.tgz", + "integrity": "sha512-dgbWy9b8QDlQeRZcv7LNF+/jFiiYHTKho1xirauZ7kVwY7avjFF6uTT0RqlgudB5OuIPagFdVtfFMosjVbk1eA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "30.0.4", + "@jest/test-result": "30.3.0", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.0.2", + "jest-haste-map": "30.3.0", "slash": "^3.0.0" }, "engines": { @@ -793,22 +904,23 @@ } }, "node_modules/@jest/transform": { - "version": "30.0.4", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.3.0.tgz", + "integrity": "sha512-TLKY33fSLVd/lKB2YI1pH69ijyUblO/BQvCj566YvnwuzoTNr648iE0j22vRvVNk2HsPwByPxATg3MleS3gf5A==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.27.4", - "@jest/types": "30.0.1", + "@jest/types": "30.3.0", "@jridgewell/trace-mapping": "^0.3.25", - "babel-plugin-istanbul": "^7.0.0", + "babel-plugin-istanbul": "^7.0.1", "chalk": "^4.1.2", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.0.2", + "jest-haste-map": "30.3.0", "jest-regex-util": "30.0.1", - "jest-util": "30.0.2", - "micromatch": "^4.0.8", + "jest-util": "30.3.0", "pirates": "^4.0.7", "slash": "^3.0.0", "write-file-atomic": "^5.0.1" @@ -818,12 +930,14 @@ } }, "node_modules/@jest/types": { - "version": "30.0.1", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz", + "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==", "dev": true, "license": "MIT", "dependencies": { "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", + "@jest/schemas": "30.0.5", "@types/istanbul-lib-coverage": "^2.0.6", "@types/istanbul-reports": "^3.0.4", "@types/node": "*", @@ -876,8 +990,23 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" + } + }, "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==", "dev": true, "license": "MIT", "optional": true, @@ -886,7 +1015,9 @@ } }, "node_modules/@pkgr/core": { - "version": "0.2.7", + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", "dev": true, "license": "MIT", "engines": { @@ -897,12 +1028,16 @@ } }, "node_modules/@sinclair/typebox": { - "version": "0.34.37", + "version": "0.34.48", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", + "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", "dev": true, "license": "MIT" }, "node_modules/@sinonjs/commons": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -910,15 +1045,30 @@ } }, "node_modules/@sinonjs/fake-timers": { - "version": "13.0.5", + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.1.1.tgz", + "integrity": "sha512-cO5W33JgAPbOh07tvZjUOJ7oWhtaqGHiZw+11DPbyqh2kHTBc3eF/CjJDeQ4205RLQsX6rxCuYOroFQwl7JDRw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { "@sinonjs/commons": "^3.0.1" } }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", "dev": true, "license": "MIT", "dependencies": { @@ -931,6 +1081,8 @@ }, "node_modules/@types/babel__generator": { "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", "dev": true, "license": "MIT", "dependencies": { @@ -939,6 +1091,8 @@ }, "node_modules/@types/babel__template": { "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", "dev": true, "license": "MIT", "dependencies": { @@ -947,11 +1101,13 @@ } }, "node_modules/@types/babel__traverse": { - "version": "7.20.7", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.20.7" + "@babel/types": "^7.28.2" } }, "node_modules/@types/eslint": { @@ -1043,8 +1199,192 @@ "dev": true, "license": "ISC" }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@unrs/resolver-binding-linux-x64-gnu": { - "version": "1.11.0", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", + "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", "cpu": [ "x64" ], @@ -1056,7 +1396,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-x64-musl": { - "version": "1.11.0", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", + "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", "cpu": [ "x64" ], @@ -1067,6 +1409,65 @@ "linux" ] }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.11" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", + "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@webassemblyjs/ast": { "version": "1.14.1", "dev": true, @@ -1325,6 +1726,8 @@ }, "node_modules/ansi-escapes": { "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1338,7 +1741,9 @@ } }, "node_modules/ansi-regex": { - "version": "6.1.0", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, "license": "MIT", "engines": { @@ -1376,26 +1781,25 @@ }, "node_modules/argparse": { "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "license": "MIT", "dependencies": { "sprintf-js": "~1.0.2" } }, - "node_modules/async": { - "version": "3.2.6", - "dev": true, - "license": "MIT" - }, "node_modules/babel-jest": { - "version": "30.0.4", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.3.0.tgz", + "integrity": "sha512-gRpauEU2KRrCox5Z296aeVHR4jQ98BCnu0IO332D/xpHNOsIH/bgSRk9k6GbKIbBw8vFeN6ctuu6tV8WOyVfYQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/transform": "30.0.4", + "@jest/transform": "30.3.0", "@types/babel__core": "^7.20.5", - "babel-plugin-istanbul": "^7.0.0", - "babel-preset-jest": "30.0.1", + "babel-plugin-istanbul": "^7.0.1", + "babel-preset-jest": "30.3.0", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "slash": "^3.0.0" @@ -1404,13 +1808,18 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { - "@babel/core": "^7.11.0" + "@babel/core": "^7.11.0 || ^8.0.0-0" } }, "node_modules/babel-plugin-istanbul": { - "version": "7.0.0", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", + "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", "dev": true, "license": "BSD-3-Clause", + "workspaces": [ + "test/babel-8" + ], "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", @@ -1423,12 +1832,12 @@ } }, "node_modules/babel-plugin-jest-hoist": { - "version": "30.0.1", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.3.0.tgz", + "integrity": "sha512-+TRkByhsws6sfPjVaitzadk1I0F5sPvOVUH5tyTSzhePpsGIVrdeunHSw/C36QeocS95OOk8lunc4rlu5Anwsg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.27.3", "@types/babel__core": "^7.20.5" }, "engines": { @@ -1436,7 +1845,9 @@ } }, "node_modules/babel-preset-current-node-syntax": { - "version": "1.1.0", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", "dev": true, "license": "MIT", "dependencies": { @@ -1457,26 +1868,30 @@ "@babel/plugin-syntax-top-level-await": "^7.14.5" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.0.0 || ^8.0.0-0" } }, "node_modules/babel-preset-jest": { - "version": "30.0.1", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.3.0.tgz", + "integrity": "sha512-6ZcUbWHC+dMz2vfzdNwi87Z1gQsLNK2uLuK1Q89R11xdvejcivlYYwDlEv0FHX3VwEXpbBQ9uufB/MUNpZGfhQ==", "dev": true, "license": "MIT", "dependencies": { - "babel-plugin-jest-hoist": "30.0.1", - "babel-preset-current-node-syntax": "^1.1.0" + "babel-plugin-jest-hoist": "30.3.0", + "babel-preset-current-node-syntax": "^1.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { - "@babel/core": "^7.11.0" + "@babel/core": "^7.11.0 || ^8.0.0-beta.1" } }, "node_modules/balanced-match": { "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" }, @@ -1495,6 +1910,8 @@ }, "node_modules/brace-expansion": { "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -1573,6 +1990,8 @@ }, "node_modules/callsites": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, "license": "MIT", "engines": { @@ -1581,6 +2000,8 @@ }, "node_modules/camelcase": { "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true, "license": "MIT", "engines": { @@ -1636,6 +2057,8 @@ }, "node_modules/char-regex": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", "dev": true, "license": "MIT", "engines": { @@ -1665,12 +2088,16 @@ } }, "node_modules/cjs-module-lexer": { - "version": "2.1.0", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", + "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", "dev": true, "license": "MIT" }, "node_modules/cliui": { "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, "license": "ISC", "dependencies": { @@ -1684,6 +2111,8 @@ }, "node_modules/cliui/node_modules/ansi-regex": { "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": { @@ -1692,11 +2121,15 @@ }, "node_modules/cliui/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/cliui/node_modules/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==", "dev": true, "license": "MIT", "dependencies": { @@ -1710,6 +2143,8 @@ }, "node_modules/cliui/node_modules/strip-ansi": { "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": { @@ -1721,6 +2156,8 @@ }, "node_modules/cliui/node_modules/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==", "dev": true, "license": "MIT", "dependencies": { @@ -1750,6 +2187,8 @@ }, "node_modules/co": { "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", "dev": true, "license": "MIT", "engines": { @@ -1758,7 +2197,9 @@ } }, "node_modules/collect-v8-coverage": { - "version": "1.0.2", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", "dev": true, "license": "MIT" }, @@ -1792,6 +2233,8 @@ }, "node_modules/concat-map": { "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true, "license": "MIT" }, @@ -1830,7 +2273,9 @@ } }, "node_modules/dedent": { - "version": "1.6.0", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", + "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", "dev": true, "license": "MIT", "peerDependencies": { @@ -1844,6 +2289,8 @@ }, "node_modules/deepmerge": { "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "dev": true, "license": "MIT", "engines": { @@ -1852,6 +2299,8 @@ }, "node_modules/detect-newline": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", "dev": true, "license": "MIT", "engines": { @@ -1860,23 +2309,11 @@ }, "node_modules/eastasianwidth": { "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true, "license": "MIT" }, - "node_modules/ejs": { - "version": "3.1.10", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "jake": "^10.8.5" - }, - "bin": { - "ejs": "bin/cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/electron-to-chromium": { "version": "1.5.307", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.307.tgz", @@ -1886,6 +2323,8 @@ }, "node_modules/emittery": { "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", "dev": true, "license": "MIT", "engines": { @@ -1897,6 +2336,8 @@ }, "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==", "dev": true, "license": "MIT" }, @@ -1926,7 +2367,9 @@ } }, "node_modules/error-ex": { - "version": "1.3.2", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1970,6 +2413,8 @@ }, "node_modules/esprima": { "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true, "license": "BSD-2-Clause", "bin": { @@ -2017,6 +2462,8 @@ }, "node_modules/execa": { "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, "license": "MIT", "dependencies": { @@ -2037,8 +2484,17 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, + "node_modules/execa/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, "node_modules/exit-x": { "version": "0.2.2", + "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", + "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", "dev": true, "license": "MIT", "engines": { @@ -2046,16 +2502,18 @@ } }, "node_modules/expect": { - "version": "30.0.4", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.3.0.tgz", + "integrity": "sha512-1zQrciTiQfRdo7qJM1uG4navm8DayFa2TgCSRlzUyNkhcJ6XUZF3hjnpkyr3VhAqPH7i/9GkG7Tv5abz6fqz0Q==", "dev": true, "license": "MIT", "dependencies": { - "@jest/expect-utils": "30.0.4", - "@jest/get-type": "30.0.1", - "jest-matcher-utils": "30.0.4", - "jest-message-util": "30.0.2", - "jest-mock": "30.0.2", - "jest-util": "30.0.2" + "@jest/expect-utils": "30.3.0", + "@jest/get-type": "30.1.0", + "jest-matcher-utils": "30.3.0", + "jest-message-util": "30.3.0", + "jest-mock": "30.3.0", + "jest-util": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -2106,35 +2564,6 @@ "bser": "2.1.1" } }, - "node_modules/filelist": { - "version": "1.0.4", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "minimatch": "^5.0.1" - } - }, - "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", - "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/fill-range": { "version": "7.1.1", "dev": true, @@ -2168,6 +2597,8 @@ }, "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==", "dev": true, "license": "ISC", "dependencies": { @@ -2181,22 +2612,28 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/foreground-child/node_modules/signal-exit": { - "version": "4.1.0", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/fs.realpath": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true, "license": "ISC" }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "dev": true, @@ -2215,6 +2652,8 @@ }, "node_modules/get-caller-file": { "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, "license": "ISC", "engines": { @@ -2223,6 +2662,8 @@ }, "node_modules/get-package-type": { "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true, "license": "MIT", "engines": { @@ -2231,6 +2672,8 @@ }, "node_modules/get-stream": { "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true, "license": "MIT", "engines": { @@ -2242,6 +2685,9 @@ }, "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", "dev": true, "license": "ISC", "dependencies": { @@ -2268,6 +2714,8 @@ }, "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==", "dev": true, "license": "MIT", "dependencies": { @@ -2295,6 +2743,28 @@ "dev": true, "license": "ISC" }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, "node_modules/has-flag": { "version": "4.0.0", "dev": true, @@ -2316,11 +2786,15 @@ }, "node_modules/html-escaper": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true, "license": "MIT" }, "node_modules/human-signals": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -2355,6 +2829,9 @@ }, "node_modules/inflight": { "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, "license": "ISC", "dependencies": { @@ -2364,6 +2841,8 @@ }, "node_modules/inherits": { "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true, "license": "ISC" }, @@ -2377,6 +2856,8 @@ }, "node_modules/is-arrayish": { "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true, "license": "MIT" }, @@ -2396,6 +2877,8 @@ }, "node_modules/is-fullwidth-code-point": { "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": { @@ -2404,6 +2887,8 @@ }, "node_modules/is-generator-fn": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", "dev": true, "license": "MIT", "engines": { @@ -2431,6 +2916,8 @@ }, "node_modules/is-stream": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, "license": "MIT", "engines": { @@ -2489,6 +2976,8 @@ }, "node_modules/istanbul-lib-report": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -2502,6 +2991,8 @@ }, "node_modules/istanbul-lib-report/node_modules/supports-color": { "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": { @@ -2513,6 +3004,8 @@ }, "node_modules/istanbul-lib-source-maps": { "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -2525,7 +3018,9 @@ } }, "node_modules/istanbul-reports": { - "version": "3.1.7", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -2538,6 +3033,8 @@ }, "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==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -2550,32 +3047,17 @@ "@pkgjs/parseargs": "^0.11.0" } }, - "node_modules/jake": { - "version": "10.9.2", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "async": "^3.2.3", - "chalk": "^4.0.2", - "filelist": "^1.0.4", - "minimatch": "^3.1.2" - }, - "bin": { - "jake": "bin/cli.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/jest": { - "version": "30.0.4", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.3.0.tgz", + "integrity": "sha512-AkXIIFcaazymvey2i/+F94XRnM6TsVLZDhBMLsd1Sf/W0wzsvvpjeyUrCZD6HGG4SDYPgDJDBKeiJTBb10WzMg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "30.0.4", - "@jest/types": "30.0.1", + "@jest/core": "30.3.0", + "@jest/types": "30.3.0", "import-local": "^3.2.0", - "jest-cli": "30.0.4" + "jest-cli": "30.3.0" }, "bin": { "jest": "bin/jest.js" @@ -2593,12 +3075,14 @@ } }, "node_modules/jest-changed-files": { - "version": "30.0.2", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.3.0.tgz", + "integrity": "sha512-B/7Cny6cV5At6M25EWDgf9S617lHivamL8vl6KEpJqkStauzcG4e+WPfDgMMF+H4FVH4A2PLRyvgDJan4441QA==", "dev": true, "license": "MIT", "dependencies": { "execa": "^5.1.1", - "jest-util": "30.0.2", + "jest-util": "30.3.0", "p-limit": "^3.1.0" }, "engines": { @@ -2606,27 +3090,29 @@ } }, "node_modules/jest-circus": { - "version": "30.0.4", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.3.0.tgz", + "integrity": "sha512-PyXq5szeSfR/4f1lYqCmmQjh0vqDkURUYi9N6whnHjlRz4IUQfMcXkGLeEoiJtxtyPqgUaUUfyQlApXWBSN1RA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.0.4", - "@jest/expect": "30.0.4", - "@jest/test-result": "30.0.4", - "@jest/types": "30.0.1", + "@jest/environment": "30.3.0", + "@jest/expect": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/types": "30.3.0", "@types/node": "*", "chalk": "^4.1.2", "co": "^4.6.0", "dedent": "^1.6.0", "is-generator-fn": "^2.1.0", - "jest-each": "30.0.2", - "jest-matcher-utils": "30.0.4", - "jest-message-util": "30.0.2", - "jest-runtime": "30.0.4", - "jest-snapshot": "30.0.4", - "jest-util": "30.0.2", + "jest-each": "30.3.0", + "jest-matcher-utils": "30.3.0", + "jest-message-util": "30.3.0", + "jest-runtime": "30.3.0", + "jest-snapshot": "30.3.0", + "jest-util": "30.3.0", "p-limit": "^3.1.0", - "pretty-format": "30.0.2", + "pretty-format": "30.3.0", "pure-rand": "^7.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.6" @@ -2636,19 +3122,21 @@ } }, "node_modules/jest-cli": { - "version": "30.0.4", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.3.0.tgz", + "integrity": "sha512-l6Tqx+j1fDXJEW5bqYykDQQ7mQg+9mhWXtnj+tQZrTWYHyHoi6Be8HPumDSA+UiX2/2buEgjA58iJzdj146uCw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "30.0.4", - "@jest/test-result": "30.0.4", - "@jest/types": "30.0.1", + "@jest/core": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/types": "30.3.0", "chalk": "^4.1.2", "exit-x": "^0.2.2", "import-local": "^3.2.0", - "jest-config": "30.0.4", - "jest-util": "30.0.2", - "jest-validate": "30.0.2", + "jest-config": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", "yargs": "^17.7.2" }, "bin": { @@ -2667,32 +3155,33 @@ } }, "node_modules/jest-config": { - "version": "30.0.4", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.3.0.tgz", + "integrity": "sha512-WPMAkMAtNDY9P/oKObtsRG/6KTrhtgPJoBTmk20uDn4Uy6/3EJnnaZJre/FMT1KVRx8cve1r7/FlMIOfRVWL4w==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.27.4", - "@jest/get-type": "30.0.1", + "@jest/get-type": "30.1.0", "@jest/pattern": "30.0.1", - "@jest/test-sequencer": "30.0.4", - "@jest/types": "30.0.1", - "babel-jest": "30.0.4", + "@jest/test-sequencer": "30.3.0", + "@jest/types": "30.3.0", + "babel-jest": "30.3.0", "chalk": "^4.1.2", "ci-info": "^4.2.0", "deepmerge": "^4.3.1", - "glob": "^10.3.10", + "glob": "^10.5.0", "graceful-fs": "^4.2.11", - "jest-circus": "30.0.4", - "jest-docblock": "30.0.1", - "jest-environment-node": "30.0.4", + "jest-circus": "30.3.0", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.3.0", "jest-regex-util": "30.0.1", - "jest-resolve": "30.0.2", - "jest-runner": "30.0.4", - "jest-util": "30.0.2", - "jest-validate": "30.0.2", - "micromatch": "^4.0.8", + "jest-resolve": "30.3.0", + "jest-runner": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", "parse-json": "^5.2.0", - "pretty-format": "30.0.2", + "pretty-format": "30.3.0", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, @@ -2717,21 +3206,25 @@ } }, "node_modules/jest-diff": { - "version": "30.0.4", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.3.0.tgz", + "integrity": "sha512-n3q4PDQjS4LrKxfWB3Z5KNk1XjXtZTBwQp71OP0Jo03Z6V60x++K5L8k6ZrW8MY8pOFylZvHM0zsjS1RqlHJZQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/diff-sequences": "30.0.1", - "@jest/get-type": "30.0.1", + "@jest/diff-sequences": "30.3.0", + "@jest/get-type": "30.1.0", "chalk": "^4.1.2", - "pretty-format": "30.0.2" + "pretty-format": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-docblock": { - "version": "30.0.1", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz", + "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==", "dev": true, "license": "MIT", "dependencies": { @@ -2742,51 +3235,57 @@ } }, "node_modules/jest-each": { - "version": "30.0.2", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.3.0.tgz", + "integrity": "sha512-V8eMndg/aZ+3LnCJgSm13IxS5XSBM22QSZc9BtPK8Dek6pm+hfUNfwBdvsB3d342bo1q7wnSkC38zjX259qZNA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.0.1", - "@jest/types": "30.0.1", + "@jest/get-type": "30.1.0", + "@jest/types": "30.3.0", "chalk": "^4.1.2", - "jest-util": "30.0.2", - "pretty-format": "30.0.2" + "jest-util": "30.3.0", + "pretty-format": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-environment-node": { - "version": "30.0.4", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.3.0.tgz", + "integrity": "sha512-4i6HItw/JSiJVsC5q0hnKIe/hbYfZLVG9YJ/0pU9Hz2n/9qZe3Rhn5s5CUZA5ORZlcdT/vmAXRMyONXJwPrmYQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.0.4", - "@jest/fake-timers": "30.0.4", - "@jest/types": "30.0.1", + "@jest/environment": "30.3.0", + "@jest/fake-timers": "30.3.0", + "@jest/types": "30.3.0", "@types/node": "*", - "jest-mock": "30.0.2", - "jest-util": "30.0.2", - "jest-validate": "30.0.2" + "jest-mock": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-haste-map": { - "version": "30.0.2", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.3.0.tgz", + "integrity": "sha512-mMi2oqG4KRU0R9QEtscl87JzMXfUhbKaFqOxmjb2CKcbHcUGFrJCBWHmnTiUqi6JcnzoBlO4rWfpdl2k/RfLCA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.0.1", + "@jest/types": "30.3.0", "@types/node": "*", "anymatch": "^3.1.3", "fb-watchman": "^2.0.2", "graceful-fs": "^4.2.11", "jest-regex-util": "30.0.1", - "jest-util": "30.0.2", - "jest-worker": "30.0.2", - "micromatch": "^4.0.8", + "jest-util": "30.3.0", + "jest-worker": "30.3.0", + "picomatch": "^4.0.3", "walker": "^1.0.8" }, "engines": { @@ -2797,13 +3296,15 @@ } }, "node_modules/jest-haste-map/node_modules/jest-worker": { - "version": "30.0.2", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.3.0.tgz", + "integrity": "sha512-DrCKkaQwHexjRUFTmPzs7sHQe0TSj9nvDALKGdwmK5mW9v7j90BudWirKAJHt3QQ9Dhrg1F7DogPzhChppkJpQ==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", "@ungap/structured-clone": "^1.3.0", - "jest-util": "30.0.2", + "jest-util": "30.3.0", "merge-stream": "^2.0.0", "supports-color": "^8.1.1" }, @@ -2811,44 +3312,63 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/jest-haste-map/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/jest-leak-detector": { - "version": "30.0.2", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.3.0.tgz", + "integrity": "sha512-cuKmUUGIjfXZAiGJ7TbEMx0bcqNdPPI6P1V+7aF+m/FUJqFDxkFR4JqkTu8ZOiU5AaX/x0hZ20KaaIPXQzbMGQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.0.1", - "pretty-format": "30.0.2" + "@jest/get-type": "30.1.0", + "pretty-format": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-matcher-utils": { - "version": "30.0.4", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.3.0.tgz", + "integrity": "sha512-HEtc9uFQgaUHkC7nLSlQL3Tph4Pjxt/yiPvkIrrDCt9jhoLIgxaubo1G+CFOnmHYMxHwwdaSN7mkIFs6ZK8OhA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.0.1", + "@jest/get-type": "30.1.0", "chalk": "^4.1.2", - "jest-diff": "30.0.4", - "pretty-format": "30.0.2" + "jest-diff": "30.3.0", + "pretty-format": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-message-util": { - "version": "30.0.2", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.3.0.tgz", + "integrity": "sha512-Z/j4Bo+4ySJ+JPJN3b2Qbl9hDq3VrXmnjjGEWD/x0BCXeOXPTV1iZYYzl2X8c1MaCOL+ewMyNBcm88sboE6YWw==", "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@jest/types": "30.0.1", + "@jest/types": "30.3.0", "@types/stack-utils": "^2.0.3", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", - "micromatch": "^4.0.8", - "pretty-format": "30.0.2", + "picomatch": "^4.0.3", + "pretty-format": "30.3.0", "slash": "^3.0.0", "stack-utils": "^2.0.6" }, @@ -2856,14 +3376,29 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/jest-message-util/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/jest-mock": { - "version": "30.0.2", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.3.0.tgz", + "integrity": "sha512-OTzICK8CpE+t4ndhKrwlIdbM6Pn8j00lvmSmq5ejiO+KxukbLjgOflKWMn3KE34EZdQm5RqTuKj+5RIEniYhog==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.0.1", + "@jest/types": "30.3.0", "@types/node": "*", - "jest-util": "30.0.2" + "jest-util": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -2871,6 +3406,8 @@ }, "node_modules/jest-pnp-resolver": { "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", "dev": true, "license": "MIT", "engines": { @@ -2894,16 +3431,18 @@ } }, "node_modules/jest-resolve": { - "version": "30.0.2", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.3.0.tgz", + "integrity": "sha512-NRtTAHQlpd15F9rUR36jqwelbrDV/dY4vzNte3S2kxCKUJRYNd5/6nTSbYiak1VX5g8IoFF23Uj5TURkUW8O5g==", "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.1.2", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.0.2", + "jest-haste-map": "30.3.0", "jest-pnp-resolver": "^1.2.3", - "jest-util": "30.0.2", - "jest-validate": "30.0.2", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", "slash": "^3.0.0", "unrs-resolver": "^1.7.11" }, @@ -2912,42 +3451,46 @@ } }, "node_modules/jest-resolve-dependencies": { - "version": "30.0.4", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.3.0.tgz", + "integrity": "sha512-9ev8s3YN6Hsyz9LV75XUwkCVFlwPbaFn6Wp75qnI0wzAINYWY8Fb3+6y59Rwd3QaS3kKXffHXsZMziMavfz/nw==", "dev": true, "license": "MIT", "dependencies": { "jest-regex-util": "30.0.1", - "jest-snapshot": "30.0.4" + "jest-snapshot": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-runner": { - "version": "30.0.4", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.3.0.tgz", + "integrity": "sha512-gDv6C9LGKWDPLia9TSzZwf4h3kMQCqyTpq+95PODnTRDO0g9os48XIYYkS6D236vjpBir2fF63YmJFtqkS5Duw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.0.4", - "@jest/environment": "30.0.4", - "@jest/test-result": "30.0.4", - "@jest/transform": "30.0.4", - "@jest/types": "30.0.1", + "@jest/console": "30.3.0", + "@jest/environment": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", "@types/node": "*", "chalk": "^4.1.2", "emittery": "^0.13.1", "exit-x": "^0.2.2", "graceful-fs": "^4.2.11", - "jest-docblock": "30.0.1", - "jest-environment-node": "30.0.4", - "jest-haste-map": "30.0.2", - "jest-leak-detector": "30.0.2", - "jest-message-util": "30.0.2", - "jest-resolve": "30.0.2", - "jest-runtime": "30.0.4", - "jest-util": "30.0.2", - "jest-watcher": "30.0.4", - "jest-worker": "30.0.2", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.3.0", + "jest-haste-map": "30.3.0", + "jest-leak-detector": "30.3.0", + "jest-message-util": "30.3.0", + "jest-resolve": "30.3.0", + "jest-runtime": "30.3.0", + "jest-util": "30.3.0", + "jest-watcher": "30.3.0", + "jest-worker": "30.3.0", "p-limit": "^3.1.0", "source-map-support": "0.5.13" }, @@ -2956,13 +3499,15 @@ } }, "node_modules/jest-runner/node_modules/jest-worker": { - "version": "30.0.2", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.3.0.tgz", + "integrity": "sha512-DrCKkaQwHexjRUFTmPzs7sHQe0TSj9nvDALKGdwmK5mW9v7j90BudWirKAJHt3QQ9Dhrg1F7DogPzhChppkJpQ==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", "@ungap/structured-clone": "^1.3.0", - "jest-util": "30.0.2", + "jest-util": "30.3.0", "merge-stream": "^2.0.0", "supports-color": "^8.1.1" }, @@ -2972,6 +3517,8 @@ }, "node_modules/jest-runner/node_modules/source-map-support": { "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", "dev": true, "license": "MIT", "dependencies": { @@ -2980,30 +3527,32 @@ } }, "node_modules/jest-runtime": { - "version": "30.0.4", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.3.0.tgz", + "integrity": "sha512-CgC+hIBJbuh78HEffkhNKcbXAytQViplcl8xupqeIWyKQF50kCQA8J7GeJCkjisC6hpnC9Muf8jV5RdtdFbGng==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.0.4", - "@jest/fake-timers": "30.0.4", - "@jest/globals": "30.0.4", + "@jest/environment": "30.3.0", + "@jest/fake-timers": "30.3.0", + "@jest/globals": "30.3.0", "@jest/source-map": "30.0.1", - "@jest/test-result": "30.0.4", - "@jest/transform": "30.0.4", - "@jest/types": "30.0.1", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", "@types/node": "*", "chalk": "^4.1.2", "cjs-module-lexer": "^2.1.0", "collect-v8-coverage": "^1.0.2", - "glob": "^10.3.10", + "glob": "^10.5.0", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.0.2", - "jest-message-util": "30.0.2", - "jest-mock": "30.0.2", + "jest-haste-map": "30.3.0", + "jest-message-util": "30.3.0", + "jest-mock": "30.3.0", "jest-regex-util": "30.0.1", - "jest-resolve": "30.0.2", - "jest-snapshot": "30.0.4", - "jest-util": "30.0.2", + "jest-resolve": "30.3.0", + "jest-snapshot": "30.3.0", + "jest-util": "30.3.0", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, @@ -3012,7 +3561,9 @@ } }, "node_modules/jest-snapshot": { - "version": "30.0.4", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.3.0.tgz", + "integrity": "sha512-f14c7atpb4O2DeNhwcvS810Y63wEn8O1HqK/luJ4F6M4NjvxmAKQwBUWjbExUtMxWJQ0wVgmCKymeJK6NZMnfQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3021,20 +3572,20 @@ "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/types": "^7.27.3", - "@jest/expect-utils": "30.0.4", - "@jest/get-type": "30.0.1", - "@jest/snapshot-utils": "30.0.4", - "@jest/transform": "30.0.4", - "@jest/types": "30.0.1", - "babel-preset-current-node-syntax": "^1.1.0", + "@jest/expect-utils": "30.3.0", + "@jest/get-type": "30.1.0", + "@jest/snapshot-utils": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", + "babel-preset-current-node-syntax": "^1.2.0", "chalk": "^4.1.2", - "expect": "30.0.4", + "expect": "30.3.0", "graceful-fs": "^4.2.11", - "jest-diff": "30.0.4", - "jest-matcher-utils": "30.0.4", - "jest-message-util": "30.0.2", - "jest-util": "30.0.2", - "pretty-format": "30.0.2", + "jest-diff": "30.3.0", + "jest-matcher-utils": "30.3.0", + "jest-message-util": "30.3.0", + "jest-util": "30.3.0", + "pretty-format": "30.3.0", "semver": "^7.7.2", "synckit": "^0.11.8" }, @@ -3043,7 +3594,9 @@ } }, "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.7.2", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -3054,23 +3607,27 @@ } }, "node_modules/jest-util": { - "version": "30.0.2", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.3.0.tgz", + "integrity": "sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.0.1", + "@jest/types": "30.3.0", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", "graceful-fs": "^4.2.11", - "picomatch": "^4.0.2" + "picomatch": "^4.0.3" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-util/node_modules/picomatch": { - "version": "4.0.2", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", "engines": { @@ -3081,16 +3638,18 @@ } }, "node_modules/jest-validate": { - "version": "30.0.2", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.3.0.tgz", + "integrity": "sha512-I/xzC8h5G+SHCb2P2gWkJYrNiTbeL47KvKeW5EzplkyxzBRBw1ssSHlI/jXec0ukH2q7x2zAWQm7015iusg62Q==", "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.0.1", - "@jest/types": "30.0.1", + "@jest/get-type": "30.1.0", + "@jest/types": "30.3.0", "camelcase": "^6.3.0", "chalk": "^4.1.2", "leven": "^3.1.0", - "pretty-format": "30.0.2" + "pretty-format": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -3098,6 +3657,8 @@ }, "node_modules/jest-validate/node_modules/camelcase": { "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, "license": "MIT", "engines": { @@ -3108,17 +3669,19 @@ } }, "node_modules/jest-watcher": { - "version": "30.0.4", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.3.0.tgz", + "integrity": "sha512-PJ1d9ThtTR8aMiBWUdcownq9mDdLXsQzJayTk4kmaBRHKvwNQn+ANveuhEBUyNI2hR1TVhvQ8D5kHubbzBHR/w==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "30.0.4", - "@jest/types": "30.0.1", + "@jest/test-result": "30.3.0", + "@jest/types": "30.3.0", "@types/node": "*", "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", "emittery": "^0.13.1", - "jest-util": "30.0.2", + "jest-util": "30.3.0", "string-length": "^4.0.2" }, "engines": { @@ -3147,6 +3710,8 @@ }, "node_modules/js-yaml": { "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, "license": "MIT", "dependencies": { @@ -3201,6 +3766,8 @@ }, "node_modules/leven": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", "dev": true, "license": "MIT", "engines": { @@ -3209,6 +3776,8 @@ }, "node_modules/lines-and-columns": { "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true, "license": "MIT" }, @@ -3252,6 +3821,8 @@ }, "node_modules/make-dir": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, "license": "MIT", "dependencies": { @@ -3265,7 +3836,9 @@ } }, "node_modules/make-dir/node_modules/semver": { - "version": "7.7.2", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -3326,6 +3899,8 @@ }, "node_modules/mimic-fn": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true, "license": "MIT", "engines": { @@ -3345,10 +3920,22 @@ "node": "*" } }, - "node_modules/minipass": { - "version": "7.1.2", + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true, - "license": "ISC", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" } @@ -3359,7 +3946,9 @@ "license": "MIT" }, "node_modules/napi-postinstall": { - "version": "0.3.0", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", "dev": true, "license": "MIT", "bin": { @@ -3374,6 +3963,8 @@ }, "node_modules/natural-compare": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true, "license": "MIT" }, @@ -3404,6 +3995,8 @@ }, "node_modules/npm-run-path": { "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, "license": "MIT", "dependencies": { @@ -3415,6 +4008,8 @@ }, "node_modules/once": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, "license": "ISC", "dependencies": { @@ -3423,6 +4018,8 @@ }, "node_modules/onetime": { "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, "license": "MIT", "dependencies": { @@ -3437,6 +4034,8 @@ }, "node_modules/p-limit": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3484,11 +4083,15 @@ }, "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==", "dev": true, "license": "BlueOak-1.0.0" }, "node_modules/parse-json": { "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, "license": "MIT", "dependencies": { @@ -3514,6 +4117,8 @@ }, "node_modules/path-is-absolute": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true, "license": "MIT", "engines": { @@ -3535,6 +4140,8 @@ }, "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==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -3550,6 +4157,8 @@ }, "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==", "dev": true, "license": "ISC" }, @@ -3589,7 +4198,9 @@ } }, "node_modules/prettier": { - "version": "3.6.2", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "dev": true, "license": "MIT", "bin": { @@ -3603,11 +4214,13 @@ } }, "node_modules/pretty-format": { - "version": "30.0.2", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", + "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "30.0.1", + "@jest/schemas": "30.0.5", "ansi-styles": "^5.2.0", "react-is": "^18.3.1" }, @@ -3628,6 +4241,8 @@ }, "node_modules/pure-rand": { "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", + "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", "dev": true, "funding": [ { @@ -3663,6 +4278,8 @@ }, "node_modules/require-directory": { "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, "license": "MIT", "engines": { @@ -3776,9 +4393,17 @@ } }, "node_modules/signal-exit": { - "version": "3.0.7", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, - "license": "ISC" + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, "node_modules/slash": { "version": "3.0.0", @@ -3809,6 +4434,8 @@ }, "node_modules/sprintf-js": { "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true, "license": "BSD-3-Clause" }, @@ -3825,6 +4452,8 @@ }, "node_modules/string-length": { "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3837,6 +4466,8 @@ }, "node_modules/string-length/node_modules/ansi-regex": { "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": { @@ -3845,6 +4476,8 @@ }, "node_modules/string-length/node_modules/strip-ansi": { "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": { @@ -3856,6 +4489,8 @@ }, "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==", "dev": true, "license": "MIT", "dependencies": { @@ -3873,6 +4508,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==", "dev": true, "license": "MIT", "dependencies": { @@ -3886,6 +4523,8 @@ }, "node_modules/string-width-cjs/node_modules/ansi-regex": { "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": { @@ -3894,11 +4533,15 @@ }, "node_modules/string-width-cjs/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/string-width-cjs/node_modules/strip-ansi": { "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": { @@ -3909,11 +4552,13 @@ } }, "node_modules/strip-ansi": { - "version": "7.1.0", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" }, "engines": { "node": ">=12" @@ -3925,6 +4570,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==", "dev": true, "license": "MIT", "dependencies": { @@ -3936,6 +4583,8 @@ }, "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { "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": { @@ -3944,6 +4593,8 @@ }, "node_modules/strip-bom": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "dev": true, "license": "MIT", "engines": { @@ -3952,6 +4603,8 @@ }, "node_modules/strip-final-newline": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true, "license": "MIT", "engines": { @@ -3960,6 +4613,8 @@ }, "node_modules/strip-json-comments": { "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, "license": "MIT", "engines": { @@ -3995,11 +4650,13 @@ } }, "node_modules/synckit": { - "version": "0.11.8", + "version": "0.11.12", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", + "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", "dev": true, "license": "MIT", "dependencies": { - "@pkgr/core": "^0.2.4" + "@pkgr/core": "^0.2.9" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -4077,6 +4734,8 @@ }, "node_modules/test-exclude": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", "dev": true, "license": "ISC", "dependencies": { @@ -4090,6 +4749,9 @@ }, "node_modules/test-exclude/node_modules/glob": { "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "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", "dev": true, "license": "ISC", "dependencies": { @@ -4124,17 +4786,19 @@ } }, "node_modules/ts-jest": { - "version": "29.4.0", + "version": "29.4.6", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.6.tgz", + "integrity": "sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==", "dev": true, "license": "MIT", "dependencies": { "bs-logger": "^0.2.6", - "ejs": "^3.1.10", "fast-json-stable-stringify": "^2.1.0", + "handlebars": "^4.7.8", "json5": "^2.2.3", "lodash.memoize": "^4.1.2", "make-error": "^1.3.6", - "semver": "^7.7.2", + "semver": "^7.7.3", "type-fest": "^4.41.0", "yargs-parser": "^21.1.1" }, @@ -4175,7 +4839,9 @@ } }, "node_modules/ts-jest/node_modules/semver": { - "version": "7.7.2", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -4197,7 +4863,9 @@ } }, "node_modules/ts-loader": { - "version": "9.5.2", + "version": "9.5.4", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.4.tgz", + "integrity": "sha512-nCz0rEwunlTZiy6rXFByQU1kVVpCIgUpc/psFiKVrUwrizdnIbRFu8w7bxhUF0X613DYwT4XzrZHpVyMe758hQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4241,6 +4909,8 @@ }, "node_modules/type-detect": { "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true, "license": "MIT", "engines": { @@ -4249,6 +4919,8 @@ }, "node_modules/type-fest": { "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { @@ -4259,7 +4931,9 @@ } }, "node_modules/typescript": { - "version": "5.8.3", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -4270,13 +4944,29 @@ "node": ">=14.17" } }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/undici-types": { "version": "7.16.0", "dev": true, "license": "MIT" }, "node_modules/unrs-resolver": { - "version": "1.11.0", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", + "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -4287,25 +4977,25 @@ "url": "https://opencollective.com/unrs-resolver" }, "optionalDependencies": { - "@unrs/resolver-binding-android-arm-eabi": "1.11.0", - "@unrs/resolver-binding-android-arm64": "1.11.0", - "@unrs/resolver-binding-darwin-arm64": "1.11.0", - "@unrs/resolver-binding-darwin-x64": "1.11.0", - "@unrs/resolver-binding-freebsd-x64": "1.11.0", - "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.0", - "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.0", - "@unrs/resolver-binding-linux-arm64-gnu": "1.11.0", - "@unrs/resolver-binding-linux-arm64-musl": "1.11.0", - "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.0", - "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.0", - "@unrs/resolver-binding-linux-riscv64-musl": "1.11.0", - "@unrs/resolver-binding-linux-s390x-gnu": "1.11.0", - "@unrs/resolver-binding-linux-x64-gnu": "1.11.0", - "@unrs/resolver-binding-linux-x64-musl": "1.11.0", - "@unrs/resolver-binding-wasm32-wasi": "1.11.0", - "@unrs/resolver-binding-win32-arm64-msvc": "1.11.0", - "@unrs/resolver-binding-win32-ia32-msvc": "1.11.0", - "@unrs/resolver-binding-win32-x64-msvc": "1.11.0" + "@unrs/resolver-binding-android-arm-eabi": "1.11.1", + "@unrs/resolver-binding-android-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-x64": "1.11.1", + "@unrs/resolver-binding-freebsd-x64": "1.11.1", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", + "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-musl": "1.11.1", + "@unrs/resolver-binding-wasm32-wasi": "1.11.1", + "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", + "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", + "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" } }, "node_modules/update-browserslist-db": { @@ -4341,6 +5031,8 @@ }, "node_modules/v8-to-istanbul": { "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", "dev": true, "license": "ISC", "dependencies": { @@ -4514,8 +5206,17 @@ "dev": true, "license": "MIT" }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, "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==", "dev": true, "license": "MIT", "dependencies": { @@ -4533,6 +5234,8 @@ "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==", "dev": true, "license": "MIT", "dependencies": { @@ -4549,6 +5252,8 @@ }, "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { "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": { @@ -4557,11 +5262,15 @@ }, "node_modules/wrap-ansi-cjs/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/wrap-ansi-cjs/node_modules/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==", "dev": true, "license": "MIT", "dependencies": { @@ -4575,6 +5284,8 @@ }, "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { "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": { @@ -4585,7 +5296,9 @@ } }, "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.1", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "license": "MIT", "engines": { @@ -4597,6 +5310,8 @@ }, "node_modules/wrappy": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true, "license": "ISC" }, @@ -4612,19 +5327,10 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/write-file-atomic/node_modules/signal-exit": { - "version": "4.1.0", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/y18n": { "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, "license": "ISC", "engines": { @@ -4638,6 +5344,8 @@ }, "node_modules/yargs": { "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "license": "MIT", "dependencies": { @@ -4663,6 +5371,8 @@ }, "node_modules/yargs/node_modules/ansi-regex": { "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": { @@ -4671,11 +5381,15 @@ }, "node_modules/yargs/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/yargs/node_modules/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==", "dev": true, "license": "MIT", "dependencies": { @@ -4689,6 +5403,8 @@ }, "node_modules/yargs/node_modules/strip-ansi": { "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": { @@ -4700,6 +5416,8 @@ }, "node_modules/yocto-queue": { "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, "license": "MIT", "engines": { diff --git a/reconcile-js/package.json b/reconcile-js/package.json index f1e9021..50f406d 100644 --- a/reconcile-js/package.json +++ b/reconcile-js/package.json @@ -37,14 +37,14 @@ }, "devDependencies": { "@types/jest": "^30.0.0", - "jest": "^30.0.4", - "prettier": "^3.6.2", + "jest": "^30.3.0", + "prettier": "^3.8.1", "reconcile-text": "file:../pkg", - "ts-jest": "^29.4.0", - "ts-loader": "^9.5.2", + "ts-jest": "^29.4.6", + "ts-loader": "^9.5.4", "tslib": "2.8.1", - "typescript": "5.8.3", - "webpack": "^5.99.9", + "typescript": "5.9.3", + "webpack": "^5.105.4", "webpack-cli": "^6.0.1", "webpack-merge": "^6.0.1" } diff --git a/reconcile-python/Cargo.lock b/reconcile-python/Cargo.lock index ed40d8b..5e07549 100644 --- a/reconcile-python/Cargo.lock +++ b/reconcile-python/Cargo.lock @@ -2,48 +2,18 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "autocfg" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "indoc" -version = "2.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" -dependencies = [ - "rustversion", -] - [[package]] name = "libc" version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - [[package]] name = "once_cell" version = "1.21.3" @@ -67,37 +37,32 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.24.2" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5203598f366b11a02b13aa20cab591229ff0a89fd121a308a5df751d5fc9219" +checksum = "cf85e27e86080aafd5a22eae58a162e133a589551542b3e5cee4beb27e54f8e1" dependencies = [ - "cfg-if", - "indoc", "libc", - "memoffset", "once_cell", "portable-atomic", "pyo3-build-config", "pyo3-ffi", "pyo3-macros", - "unindent", ] [[package]] name = "pyo3-build-config" -version = "0.24.2" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99636d423fa2ca130fa5acde3059308006d46f98caac629418e53f7ebb1e9999" +checksum = "8bf94ee265674bf76c09fa430b0e99c26e319c945d96ca0d5a8215f31bf81cf7" dependencies = [ - "once_cell", "target-lexicon", ] [[package]] name = "pyo3-ffi" -version = "0.24.2" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78f9cf92ba9c409279bc3305b5409d90db2d2c22392d443a87df3a1adad59e33" +checksum = "491aa5fc66d8059dd44a75f4580a2962c1862a1c2945359db36f6c2818b748dc" dependencies = [ "libc", "pyo3-build-config", @@ -105,9 +70,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.24.2" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b999cb1a6ce21f9a6b147dcf1be9ffedf02e0043aec74dc390f3007047cecd9" +checksum = "f5d671734e9d7a43449f8480f8b38115df67bef8d21f76837fa75ee7aaa5e52e" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -117,9 +82,9 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.24.2" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "822ece1c7e1012745607d5cf0bcb2874769f0f7cb34c4cde03b9358eb9ef911a" +checksum = "22faaa1ce6c430a1f71658760497291065e6450d7b5dc2bcf254d49f66ee700a" dependencies = [ "heck", "proc-macro2", @@ -139,25 +104,19 @@ dependencies = [ [[package]] name = "reconcile-text" -version = "0.8.0" +version = "0.9.0" dependencies = [ "thiserror", ] [[package]] name = "reconcile-text-python" -version = "0.8.0" +version = "0.9.0" dependencies = [ "pyo3", "reconcile-text", ] -[[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - [[package]] name = "syn" version = "2.0.117" @@ -200,9 +159,3 @@ name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" - -[[package]] -name = "unindent" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" diff --git a/reconcile-python/Cargo.toml b/reconcile-python/Cargo.toml index 5515c79..c24ed23 100644 --- a/reconcile-python/Cargo.toml +++ b/reconcile-python/Cargo.toml @@ -13,4 +13,4 @@ crate-type = ["cdylib"] [dependencies] reconcile-text = { path = ".." } -pyo3 = { version = "0.24", features = ["extension-module"] } +pyo3 = { version = "0.28.2", features = ["extension-module"] } From 5a698fe65d306830e81a6f566910f06093cf40b7 Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Wed, 11 Mar 2026 22:01:16 +0000 Subject: [PATCH 29/61] Fix breaks --- reconcile-python/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/reconcile-python/src/lib.rs b/reconcile-python/src/lib.rs index 809e0b0..a50366a 100644 --- a/reconcile-python/src/lib.rs +++ b/reconcile-python/src/lib.rs @@ -21,7 +21,7 @@ fn extract_text_with_cursors(input: &Bound<'_, PyAny>) -> PyResult()?; + let dict = input.cast::()?; let text: String = dict .get_item("text")? @@ -30,10 +30,10 @@ fn extract_text_with_cursors(input: &Bound<'_, PyAny>) -> PyResult { - let list = obj.downcast::()?; + let list = obj.cast::()?; let mut cursors = Vec::with_capacity(list.len()); for item in list { - let cursor_dict = item.downcast::()?; + let cursor_dict = item.cast::()?; let id: usize = cursor_dict .get_item("id")? .ok_or_else(|| pyo3::exceptions::PyKeyError::new_err("id"))? From 0aea22c21170550f5f6e01d8a72514a328efb102 Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Wed, 11 Mar 2026 22:03:58 +0000 Subject: [PATCH 30/61] Python 3.13 --- .github/workflows/check.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 282fcf1..1d16b7b 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -140,6 +140,10 @@ jobs: steps: - uses: actions/checkout@v6 + - uses: actions/setup-python@v5 + with: + python-version: '3.13' + - uses: PyO3/maturin-action@v1 with: target: ${{ matrix.target }} From 0ab0e2e8606a0a7e107ab0c8f5a3a7d0eb61854c Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Wed, 11 Mar 2026 22:04:59 +0000 Subject: [PATCH 31/61] Bump versions to 0.9.1 --- Cargo.lock | 2 +- Cargo.toml | 2 +- examples/website/package-lock.json | 14 +++++++------- reconcile-js/package-lock.json | 6 +++--- reconcile-js/package.json | 2 +- reconcile-python/Cargo.toml | 2 +- reconcile-python/pyproject.toml | 2 +- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e72b02d..9b12c0d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -428,7 +428,7 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "reconcile-text" -version = "0.9.0" +version = "0.9.1" dependencies = [ "console_error_panic_hook", "diff-match-patch-rs", diff --git a/Cargo.toml b/Cargo.toml index a4bef26..1c0cd83 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "reconcile-text" description = "Intelligent 3-way text merging with automated conflict resolution" -version = "0.9.0" +version = "0.9.1" rust-version = "1.94" authors = ["Andras Schmelczer "] edition = "2024" diff --git a/examples/website/package-lock.json b/examples/website/package-lock.json index a7abd88..df07a1f 100644 --- a/examples/website/package-lock.json +++ b/examples/website/package-lock.json @@ -28,19 +28,19 @@ }, "../../reconcile-js": { "name": "reconcile-text", - "version": "0.9.0", + "version": "0.9.1", "dev": true, "license": "MIT", "devDependencies": { "@types/jest": "^30.0.0", - "jest": "^30.0.4", - "prettier": "^3.6.2", + "jest": "^30.3.0", + "prettier": "^3.8.1", "reconcile-text": "file:../pkg", - "ts-jest": "^29.4.0", - "ts-loader": "^9.5.2", + "ts-jest": "^29.4.6", + "ts-loader": "^9.5.4", "tslib": "2.8.1", - "typescript": "5.8.3", - "webpack": "^5.99.9", + "typescript": "5.9.3", + "webpack": "^5.105.4", "webpack-cli": "^6.0.1", "webpack-merge": "^6.0.1" } diff --git a/reconcile-js/package-lock.json b/reconcile-js/package-lock.json index d8c38a5..d7d5254 100644 --- a/reconcile-js/package-lock.json +++ b/reconcile-js/package-lock.json @@ -1,12 +1,12 @@ { "name": "reconcile-text", - "version": "0.9.0", + "version": "0.9.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "reconcile-text", - "version": "0.9.0", + "version": "0.9.1", "license": "MIT", "devDependencies": { "@types/jest": "^30.0.0", @@ -24,7 +24,7 @@ }, "../pkg": { "name": "reconcile-text", - "version": "0.9.0", + "version": "0.9.1", "dev": true, "license": "MIT" }, diff --git a/reconcile-js/package.json b/reconcile-js/package.json index 50f406d..e560c38 100644 --- a/reconcile-js/package.json +++ b/reconcile-js/package.json @@ -1,6 +1,6 @@ { "name": "reconcile-text", - "version": "0.9.0", + "version": "0.9.1", "description": "Intelligent 3-way text merging with automated conflict resolution", "main": "dist/reconcile.node.js", "browser": "dist/reconcile.web.js", diff --git a/reconcile-python/Cargo.toml b/reconcile-python/Cargo.toml index c24ed23..7d7820a 100644 --- a/reconcile-python/Cargo.toml +++ b/reconcile-python/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reconcile-text-python" -version = "0.9.0" +version = "0.9.1" edition = "2024" rust-version = "1.94" authors = ["Andras Schmelczer "] diff --git a/reconcile-python/pyproject.toml b/reconcile-python/pyproject.toml index a256177..54d47f5 100644 --- a/reconcile-python/pyproject.toml +++ b/reconcile-python/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "reconcile-text" -version = "0.9.0" +version = "0.9.1" description = "Intelligent 3-way text merging with automated conflict resolution" readme = "../README.md" license = { text = "MIT" } From 59284d00f9d21db0ee994021803911f415cf2daa Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Wed, 11 Mar 2026 22:44:39 +0000 Subject: [PATCH 32/61] Fix publishing --- .github/workflows/check.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 1d16b7b..1a12e59 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -74,6 +74,8 @@ jobs: needs: build runs-on: ubuntu-latest if: startsWith(github.ref, 'refs/tags/') + permissions: + id-token: write steps: - uses: actions/checkout@v6 @@ -115,9 +117,7 @@ jobs: run: | cd reconcile-js cp ../README.md . - npm publish - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + npm publish --provenance --access public build-python-wheels: needs: build @@ -147,7 +147,7 @@ jobs: - uses: PyO3/maturin-action@v1 with: target: ${{ matrix.target }} - args: --release --out dist + args: --release --out dist --find-interpreter manylinux: auto working-directory: reconcile-python From e1d39d916a845da75a278814c852c9469287a35e Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Wed, 11 Mar 2026 22:46:27 +0000 Subject: [PATCH 33/61] Bump version --- .github/workflows/check.yml | 4 ++-- reconcile-python/Cargo.lock | 4 ++-- reconcile-python/uv.lock | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 1a12e59..771ee13 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -140,9 +140,9 @@ jobs: steps: - uses: actions/checkout@v6 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: - python-version: '3.13' + python-version: '3.x' - uses: PyO3/maturin-action@v1 with: diff --git a/reconcile-python/Cargo.lock b/reconcile-python/Cargo.lock index 5e07549..d2a3ef0 100644 --- a/reconcile-python/Cargo.lock +++ b/reconcile-python/Cargo.lock @@ -104,14 +104,14 @@ dependencies = [ [[package]] name = "reconcile-text" -version = "0.9.0" +version = "0.9.1" dependencies = [ "thiserror", ] [[package]] name = "reconcile-text-python" -version = "0.9.0" +version = "0.9.1" dependencies = [ "pyo3", "reconcile-text", diff --git a/reconcile-python/uv.lock b/reconcile-python/uv.lock index 02387e3..cc9068c 100644 --- a/reconcile-python/uv.lock +++ b/reconcile-python/uv.lock @@ -168,7 +168,7 @@ wheels = [ [[package]] name = "reconcile-text" -version = "0.8.0" +version = "0.9.1" source = { editable = "." } [package.dev-dependencies] From c58d81592decdcc97d7a834ebb9d44de5e446b18 Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Wed, 11 Mar 2026 22:46:43 +0000 Subject: [PATCH 34/61] Bump versions to 0.9.2 --- Cargo.lock | 2 +- Cargo.toml | 2 +- examples/website/package-lock.json | 2 +- reconcile-js/package-lock.json | 6 +++--- reconcile-js/package.json | 2 +- reconcile-python/Cargo.toml | 2 +- reconcile-python/pyproject.toml | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9b12c0d..1efceb5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -428,7 +428,7 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "reconcile-text" -version = "0.9.1" +version = "0.9.2" dependencies = [ "console_error_panic_hook", "diff-match-patch-rs", diff --git a/Cargo.toml b/Cargo.toml index 1c0cd83..b978c91 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "reconcile-text" description = "Intelligent 3-way text merging with automated conflict resolution" -version = "0.9.1" +version = "0.9.2" rust-version = "1.94" authors = ["Andras Schmelczer "] edition = "2024" diff --git a/examples/website/package-lock.json b/examples/website/package-lock.json index df07a1f..ca22df1 100644 --- a/examples/website/package-lock.json +++ b/examples/website/package-lock.json @@ -28,7 +28,7 @@ }, "../../reconcile-js": { "name": "reconcile-text", - "version": "0.9.1", + "version": "0.9.2", "dev": true, "license": "MIT", "devDependencies": { diff --git a/reconcile-js/package-lock.json b/reconcile-js/package-lock.json index d7d5254..4d4b670 100644 --- a/reconcile-js/package-lock.json +++ b/reconcile-js/package-lock.json @@ -1,12 +1,12 @@ { "name": "reconcile-text", - "version": "0.9.1", + "version": "0.9.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "reconcile-text", - "version": "0.9.1", + "version": "0.9.2", "license": "MIT", "devDependencies": { "@types/jest": "^30.0.0", @@ -24,7 +24,7 @@ }, "../pkg": { "name": "reconcile-text", - "version": "0.9.1", + "version": "0.9.2", "dev": true, "license": "MIT" }, diff --git a/reconcile-js/package.json b/reconcile-js/package.json index e560c38..9ad0e8d 100644 --- a/reconcile-js/package.json +++ b/reconcile-js/package.json @@ -1,6 +1,6 @@ { "name": "reconcile-text", - "version": "0.9.1", + "version": "0.9.2", "description": "Intelligent 3-way text merging with automated conflict resolution", "main": "dist/reconcile.node.js", "browser": "dist/reconcile.web.js", diff --git a/reconcile-python/Cargo.toml b/reconcile-python/Cargo.toml index 7d7820a..849bec3 100644 --- a/reconcile-python/Cargo.toml +++ b/reconcile-python/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reconcile-text-python" -version = "0.9.1" +version = "0.9.2" edition = "2024" rust-version = "1.94" authors = ["Andras Schmelczer "] diff --git a/reconcile-python/pyproject.toml b/reconcile-python/pyproject.toml index 54d47f5..8e25a4b 100644 --- a/reconcile-python/pyproject.toml +++ b/reconcile-python/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "reconcile-text" -version = "0.9.1" +version = "0.9.2" description = "Intelligent 3-way text merging with automated conflict resolution" readme = "../README.md" license = { text = "MIT" } From 25ee83174edd2444dcb33cc1a8c416d5614cb7cd Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Wed, 11 Mar 2026 22:57:27 +0000 Subject: [PATCH 35/61] Update python locks on publishing --- scripts/bump-version.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/bump-version.sh b/scripts/bump-version.sh index 4e03849..3298f8b 100755 --- a/scripts/bump-version.sh +++ b/scripts/bump-version.sh @@ -41,6 +41,8 @@ NEWVER=$(grep '^version = ' ../Cargo.toml | head -1 | sed 's/version = "\(.*\)"/ cd ../reconcile-python sed -i '' "s/^version = \".*\"/version = \"$NEWVER\"/" Cargo.toml sed -i '' "s/^version = \".*\"/version = \"$NEWVER\"/" pyproject.toml +cargo update --workspace +uv lock cd ../examples/website npm install From 6aa7ebf29d16a1c3db9ce70ac3b86e01eed53094 Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Thu, 12 Mar 2026 07:37:53 +0000 Subject: [PATCH 36/61] Fix NPM publish --- .github/workflows/check.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 771ee13..f1c2a84 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -75,6 +75,7 @@ jobs: runs-on: ubuntu-latest if: startsWith(github.ref, 'refs/tags/') permissions: + contents: read id-token: write steps: From 40b18721ad5243e1ccf1d43324137987a73c73d4 Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Thu, 12 Mar 2026 07:41:25 +0000 Subject: [PATCH 37/61] Fix python publishing --- .github/workflows/check.yml | 6 +++ examples/website/src/index.html | 85 +++++++++++++++++++++++++-------- reconcile-python/.gitignore | 1 + reconcile-python/pyproject.toml | 2 +- 4 files changed, 72 insertions(+), 22 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index f1c2a84..4bddad5 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -145,6 +145,9 @@ jobs: with: python-version: '3.x' + - name: Copy README + run: cp README.md reconcile-python/ + - uses: PyO3/maturin-action@v1 with: target: ${{ matrix.target }} @@ -165,6 +168,9 @@ jobs: steps: - uses: actions/checkout@v6 + - name: Copy README + run: cp README.md reconcile-python/ + - uses: PyO3/maturin-action@v1 with: command: sdist diff --git a/examples/website/src/index.html b/examples/website/src/index.html index 2bfe8a6..1e7ac57 100644 --- a/examples/website/src/index.html +++ b/examples/website/src/index.html @@ -195,28 +195,71 @@ diff --git a/reconcile-python/.gitignore b/reconcile-python/.gitignore index 772a74a..c93ab73 100644 --- a/reconcile-python/.gitignore +++ b/reconcile-python/.gitignore @@ -7,3 +7,4 @@ __pycache__/ *.dylib *.dSYM/ dist/ +README.md diff --git a/reconcile-python/pyproject.toml b/reconcile-python/pyproject.toml index 8e25a4b..b9f78f5 100644 --- a/reconcile-python/pyproject.toml +++ b/reconcile-python/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "maturin" name = "reconcile-text" version = "0.9.2" description = "Intelligent 3-way text merging with automated conflict resolution" -readme = "../README.md" +readme = "README.md" license = { text = "MIT" } authors = [{ name = "Andras Schmelczer", email = "andras@schmelczer.dev" }] requires-python = ">=3.9" From 7c18b6201f2fe8f35e2cb528082508dd7755b0c8 Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Thu, 12 Mar 2026 07:42:15 +0000 Subject: [PATCH 38/61] Bump versions to 0.9.3 --- Cargo.lock | 2 +- Cargo.toml | 2 +- examples/website/package-lock.json | 2 +- reconcile-js/package-lock.json | 6 +++--- reconcile-js/package.json | 2 +- reconcile-python/Cargo.lock | 4 ++-- reconcile-python/Cargo.toml | 2 +- reconcile-python/pyproject.toml | 2 +- reconcile-python/uv.lock | 2 +- 9 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1efceb5..168c1e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -428,7 +428,7 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "reconcile-text" -version = "0.9.2" +version = "0.9.3" dependencies = [ "console_error_panic_hook", "diff-match-patch-rs", diff --git a/Cargo.toml b/Cargo.toml index b978c91..c7321f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "reconcile-text" description = "Intelligent 3-way text merging with automated conflict resolution" -version = "0.9.2" +version = "0.9.3" rust-version = "1.94" authors = ["Andras Schmelczer "] edition = "2024" diff --git a/examples/website/package-lock.json b/examples/website/package-lock.json index ca22df1..faaa511 100644 --- a/examples/website/package-lock.json +++ b/examples/website/package-lock.json @@ -28,7 +28,7 @@ }, "../../reconcile-js": { "name": "reconcile-text", - "version": "0.9.2", + "version": "0.9.3", "dev": true, "license": "MIT", "devDependencies": { diff --git a/reconcile-js/package-lock.json b/reconcile-js/package-lock.json index 4d4b670..e776163 100644 --- a/reconcile-js/package-lock.json +++ b/reconcile-js/package-lock.json @@ -1,12 +1,12 @@ { "name": "reconcile-text", - "version": "0.9.2", + "version": "0.9.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "reconcile-text", - "version": "0.9.2", + "version": "0.9.3", "license": "MIT", "devDependencies": { "@types/jest": "^30.0.0", @@ -24,7 +24,7 @@ }, "../pkg": { "name": "reconcile-text", - "version": "0.9.2", + "version": "0.9.3", "dev": true, "license": "MIT" }, diff --git a/reconcile-js/package.json b/reconcile-js/package.json index 9ad0e8d..aadd6ed 100644 --- a/reconcile-js/package.json +++ b/reconcile-js/package.json @@ -1,6 +1,6 @@ { "name": "reconcile-text", - "version": "0.9.2", + "version": "0.9.3", "description": "Intelligent 3-way text merging with automated conflict resolution", "main": "dist/reconcile.node.js", "browser": "dist/reconcile.web.js", diff --git a/reconcile-python/Cargo.lock b/reconcile-python/Cargo.lock index d2a3ef0..43aea7c 100644 --- a/reconcile-python/Cargo.lock +++ b/reconcile-python/Cargo.lock @@ -104,14 +104,14 @@ dependencies = [ [[package]] name = "reconcile-text" -version = "0.9.1" +version = "0.9.3" dependencies = [ "thiserror", ] [[package]] name = "reconcile-text-python" -version = "0.9.1" +version = "0.9.3" dependencies = [ "pyo3", "reconcile-text", diff --git a/reconcile-python/Cargo.toml b/reconcile-python/Cargo.toml index 849bec3..09db884 100644 --- a/reconcile-python/Cargo.toml +++ b/reconcile-python/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reconcile-text-python" -version = "0.9.2" +version = "0.9.3" edition = "2024" rust-version = "1.94" authors = ["Andras Schmelczer "] diff --git a/reconcile-python/pyproject.toml b/reconcile-python/pyproject.toml index b9f78f5..9370730 100644 --- a/reconcile-python/pyproject.toml +++ b/reconcile-python/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "reconcile-text" -version = "0.9.2" +version = "0.9.3" description = "Intelligent 3-way text merging with automated conflict resolution" readme = "README.md" license = { text = "MIT" } diff --git a/reconcile-python/uv.lock b/reconcile-python/uv.lock index cc9068c..dee6b29 100644 --- a/reconcile-python/uv.lock +++ b/reconcile-python/uv.lock @@ -168,7 +168,7 @@ wheels = [ [[package]] name = "reconcile-text" -version = "0.9.1" +version = "0.9.3" source = { editable = "." } [package.dev-dependencies] From 6d280112fdbad906ce36b7cf7d7ebfed5f8d7c59 Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Thu, 12 Mar 2026 07:46:24 +0000 Subject: [PATCH 39/61] Fix vulnerabilities --- examples/website/package-lock.json | 42 +++++++++++++++--------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/examples/website/package-lock.json b/examples/website/package-lock.json index faaa511..a77f13d 100644 --- a/examples/website/package-lock.json +++ b/examples/website/package-lock.json @@ -651,16 +651,6 @@ "node": ">=20.0.0" } }, - "node_modules/@trysound/sax": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", - "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/@types/body-parser": { "version": "1.19.6", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", @@ -1180,9 +1170,9 @@ } }, "node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", "dependencies": { @@ -3396,9 +3386,9 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "dev": true, "license": "MIT" }, @@ -4550,6 +4540,16 @@ } } }, + "node_modules/sax": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.5.0.tgz", + "integrity": "sha512-21IYA3Q5cQf089Z6tgaUTr7lDAyzoTPx5HRtbhsME8Udispad8dC/+sziTNugOEx54ilvatQ9YCzl4KQLPcRHA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, "node_modules/schema-utils": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", @@ -5098,19 +5098,19 @@ } }, "node_modules/svgo": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz", - "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.3.tgz", + "integrity": "sha512-+wn7I4p7YgJhHs38k2TNjy1vCfPIfLIJWR5MnCStsN8WuuTcBnRKcMHQLMM2ijxGZmDoZwNv8ipl5aTTen62ng==", "dev": true, "license": "MIT", "dependencies": { - "@trysound/sax": "0.2.0", "commander": "^7.2.0", "css-select": "^5.1.0", "css-tree": "^2.3.1", "css-what": "^6.1.0", "csso": "^5.0.5", - "picocolors": "^1.0.0" + "picocolors": "^1.0.0", + "sax": "^1.5.0" }, "bin": { "svgo": "bin/svgo" From 386535497b74662bfc0e286fd08dceaa0aeb7fee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Mar 2026 21:25:49 +0000 Subject: [PATCH 40/61] Bump actions/upload-artifact from 4 to 7 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 7. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v4...v7) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/check.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 4bddad5..60233dd 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -155,7 +155,7 @@ jobs: manylinux: auto working-directory: reconcile-python - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: name: wheels-${{ matrix.os }}-${{ matrix.target }} path: reconcile-python/dist/*.whl @@ -177,7 +177,7 @@ jobs: args: --out dist working-directory: reconcile-python - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: name: sdist path: reconcile-python/dist/*.tar.gz From 7759275a53f9f57902dac06d579acc5902ff2361 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Mar 2026 21:25:44 +0000 Subject: [PATCH 41/61] Bump astral-sh/setup-uv from 6 to 7 Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 6 to 7. - [Release notes](https://github.com/astral-sh/setup-uv/releases) - [Commits](https://github.com/astral-sh/setup-uv/compare/v6...v7) --- updated-dependencies: - dependency-name: astral-sh/setup-uv dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 60233dd..5f451e7 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -25,7 +25,7 @@ jobs: check-latest: true - name: Install uv - uses: astral-sh/setup-uv@v6 + uses: astral-sh/setup-uv@v7 - name: Cache Rust dependencies uses: actions/cache@v5 From 5d588b1bacec270133cf784d477712eb1ac50435 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Mar 2026 21:25:40 +0000 Subject: [PATCH 42/61] Bump actions/download-artifact from 4 to 8 Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4 to 8. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v4...v8) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: '8' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 5f451e7..261031d 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -189,7 +189,7 @@ jobs: id-token: write steps: - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v8 with: pattern: '{wheels-*,sdist}' merge-multiple: true From 149ff8fd9517caa808324ce2b3abf2183ace38ae Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Thu, 12 Mar 2026 07:49:06 +0000 Subject: [PATCH 43/61] Fix missing readme --- scripts/lint.sh | 1 + scripts/test.sh | 1 + 2 files changed, 2 insertions(+) diff --git a/scripts/lint.sh b/scripts/lint.sh index 84c096c..c46991d 100755 --- a/scripts/lint.sh +++ b/scripts/lint.sh @@ -20,6 +20,7 @@ npm ci npm run format cd ../../reconcile-python +cp ../README.md . uv run maturin develop -q uv run ruff check python/ tests/ uv run ruff format python/ tests/ diff --git a/scripts/test.sh b/scripts/test.sh index 31b3c78..cdc60e0 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -28,6 +28,7 @@ npm run test cd - cd reconcile-python +cp ../README.md . uv run maturin develop uv run pytest -v cd - From e08ef27d6a74ef0e7fac47c20672119d70cf70aa Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Thu, 12 Mar 2026 07:50:24 +0000 Subject: [PATCH 44/61] Open on new page --- examples/website/src/index.html | 8 ++++++++ examples/website/src/style.scss | 24 +++++++++++++----------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/examples/website/src/index.html b/examples/website/src/index.html index 1e7ac57..4519472 100644 --- a/examples/website/src/index.html +++ b/examples/website/src/index.html @@ -198,6 +198,8 @@