use std::{
    ffi::{OsStr, OsString},
    fs::File,
    io,
    path::{Path, PathBuf},
    process::Command,
};

use globset::{Glob, GlobSet, GlobSetBuilder};

use crate::process::{CommandError, CommandReader, CommandReaderBuilder};

/// A builder for a matcher that determines which files get decompressed.
#[derive(Clone, Debug)]
pub struct DecompressionMatcherBuilder {
    /// The commands for each matching glob.
    commands: Vec<DecompressionCommand>,
    /// Whether to include the default matching rules.
    defaults: bool,
}

/// A representation of a single command for decompressing data
/// out-of-process.
#[derive(Clone, Debug)]
struct DecompressionCommand {
    /// The glob that matches this command.
    glob: String,
    /// The command or binary name.
    bin: PathBuf,
    /// The arguments to invoke with the command.
    args: Vec<OsString>,
}

impl Default for DecompressionMatcherBuilder {
    fn default() -> DecompressionMatcherBuilder {
        DecompressionMatcherBuilder::new()
    }
}

impl DecompressionMatcherBuilder {
    /// Create a new builder for configuring a decompression matcher.
    pub fn new() -> DecompressionMatcherBuilder {
        DecompressionMatcherBuilder { commands: vec![], defaults: true }
    }

    /// Build a matcher for determining how to decompress files.
    ///
    /// If there was a problem compiling the matcher, then an error is
    /// returned.
    pub fn build(&self) -> Result<DecompressionMatcher, CommandError> {
        let defaults = if !self.defaults {
            vec![]
        } else {
            default_decompression_commands()
        };
        let mut glob_builder = GlobSetBuilder::new();
        let mut commands = vec![];
        for decomp_cmd in defaults.iter().chain(&self.commands) {
            let glob = Glob::new(&decomp_cmd.glob).map_err(|err| {
                CommandError::io(io::Error::new(io::ErrorKind::Other, err))
            })?;
            glob_builder.add(glob);
            commands.push(decomp_cmd.clone());
        }
        let globs = glob_builder.build().map_err(|err| {
            CommandError::io(io::Error::new(io::ErrorKind::Other, err))
        })?;
        Ok(DecompressionMatcher { globs, commands })
    }

    /// When enabled, the default matching rules will be compiled into this
    /// matcher before any other associations. When disabled, only the
    /// rules explicitly given to this builder will be used.
    ///
    /// This is enabled by default.
    pub fn defaults(&mut self, yes: bool) -> &mut DecompressionMatcherBuilder {
        self.defaults = yes;
        self
    }

    /// Associates a glob with a command to decompress files matching the glob.
    ///
    /// If multiple globs match the same file, then the most recently added
    /// glob takes precedence.
    ///
    /// The syntax for the glob is documented in the
    /// [`globset` crate](https://docs.rs/globset/#syntax).
    ///
    /// The `program` given is resolved with respect to `PATH` and turned
    /// into an absolute path internally before being executed by the current
    /// platform. Notably, on Windows, this avoids a security problem where
    /// passing a relative path to `CreateProcess` will automatically search
    /// the current directory for a matching program. If the program could
    /// not be resolved, then it is silently ignored and the association is
    /// dropped. For this reason, callers should prefer `try_associate`.
    pub fn associate<P, I, A>(
        &mut self,
        glob: &str,
        program: P,
        args: I,
    ) -> &mut DecompressionMatcherBuilder
    where
        P: AsRef<OsStr>,
        I: IntoIterator<Item = A>,
        A: AsRef<OsStr>,
    {
        let _ = self.try_associate(glob, program, args);
        self
    }

    /// Associates a glob with a command to decompress files matching the glob.
    ///
    /// If multiple globs match the same file, then the most recently added
    /// glob takes precedence.
    ///
    /// The syntax for the glob is documented in the
    /// [`globset` crate](https://docs.rs/globset/#syntax).
    ///
    /// The `program` given is resolved with respect to `PATH` and turned
    /// into an absolute path internally before being executed by the current
    /// platform. Notably, on Windows, this avoids a security problem where
    /// passing a relative path to `CreateProcess` will automatically search
    /// the current directory for a matching program. If the program could not
    /// be resolved, then an error is returned.
    pub fn try_associate<P, I, A>(
        &mut self,
        glob: &str,
        program: P,
        args: I,
    ) -> Result<&mut DecompressionMatcherBuilder, CommandError>
    where
        P: AsRef<OsStr>,
        I: IntoIterator<Item = A>,
        A: AsRef<OsStr>,
    {
        let glob = glob.to_string();
        let bin = try_resolve_binary(Path::new(program.as_ref()))?;
        let args =
            args.into_iter().map(|a| a.as_ref().to_os_string()).collect();
        self.commands.push(DecompressionCommand { glob, bin, args });
        Ok(self)
    }
}

/// A matcher for determining how to decompress files.
#[derive(Clone, Debug)]
pub struct DecompressionMatcher {
    /// The set of globs to match. Each glob has a corresponding entry in
    /// `commands`. When a glob matches, the corresponding command should be
    /// used to perform out-of-process decompression.
    globs: GlobSet,
    /// The commands for each matching glob.
    commands: Vec<DecompressionCommand>,
}

impl Default for DecompressionMatcher {
    fn default() -> DecompressionMatcher {
        DecompressionMatcher::new()
    }
}

impl DecompressionMatcher {
    /// Create a new matcher with default rules.
    ///
    /// To add more matching rules, build a matcher with
    /// [`DecompressionMatcherBuilder`].
    pub fn new() -> DecompressionMatcher {
        DecompressionMatcherBuilder::new()
            .build()
            .expect("built-in matching rules should always compile")
    }

    /// Return a pre-built command based on the given file path that can
    /// decompress its contents. If no such decompressor is known, then this
    /// returns `None`.
    ///
    /// If there are multiple possible commands matching the given path, then
    /// the command added last takes precedence.
    pub fn command<P: AsRef<Path>>(&self, path: P) -> Option<Command> {
        if let Some(i) = self.globs.matches(path).into_iter().next_back() {
            let decomp_cmd = &self.commands[i];
            let mut cmd = Command::new(&decomp_cmd.bin);
            cmd.args(&decomp_cmd.args);
            return Some(cmd);
        }
        None
    }

    /// Returns true if and only if the given file path has at least one
    /// matching command to perform decompression on.
    pub fn has_command<P: AsRef<Path>>(&self, path: P) -> bool {
        self.globs.is_match(path)
    }
}

/// Configures and builds a streaming reader for decompressing data.
#[derive(Clone, Debug, Default)]
pub struct DecompressionReaderBuilder {
    matcher: DecompressionMatcher,
    command_builder: CommandReaderBuilder,
}

impl DecompressionReaderBuilder {
    /// Create a new builder with the default configuration.
    pub fn new() -> DecompressionReaderBuilder {
        DecompressionReaderBuilder::default()
    }

    /// Build a new streaming reader for decompressing data.
    ///
    /// If decompression is done out-of-process and if there was a problem
    /// spawning the process, then its error is logged at the debug level and a
    /// passthru reader is returned that does no decompression. This behavior
    /// typically occurs when the given file path matches a decompression
    /// command, but is executing in an environment where the decompression
    /// command is not available.
    ///
    /// If the given file path could not be matched with a decompression
    /// strategy, then a passthru reader is returned that does no
    /// decompression.
    pub fn build<P: AsRef<Path>>(
        &self,
        path: P,
    ) -> Result<DecompressionReader, CommandError> {
        let path = path.as_ref();
        let Some(mut cmd) = self.matcher.command(path) else {
            return DecompressionReader::new_passthru(path);
        };
        cmd.arg(path);

        match self.command_builder.build(&mut cmd) {
            Ok(cmd_reader) => Ok(DecompressionReader { rdr: Ok(cmd_reader) }),
            Err(err) => {
                log::debug!(
                    "{}: error spawning command '{:?}': {} \
                     (falling back to uncompressed reader)",
                    path.display(),
                    cmd,
                    err,
                );
                DecompressionReader::new_passthru(path)
            }
        }
    }

    /// Set the matcher to use to look up the decompression command for each
    /// file path.
    ///
    /// A set of sensible rules is enabled by default. Setting this will
    /// completely replace the current rules.
    pub fn matcher(
        &mut self,
        matcher: DecompressionMatcher,
    ) -> &mut DecompressionReaderBuilder {
        self.matcher = matcher;
        self
    }

    /// Get the underlying matcher currently used by this builder.
    pub fn get_matcher(&self) -> &DecompressionMatcher {
        &self.matcher
    }

    /// When enabled, the reader will asynchronously read the contents of the
    /// command's stderr output. When disabled, stderr is only read after the
    /// stdout stream has been exhausted (or if the process quits with an error
    /// code).
    ///
    /// Note that when enabled, this may require launching an additional
    /// thread in order to read stderr. This is done so that the process being
    /// executed is never blocked from writing to stdout or stderr. If this is
    /// disabled, then it is possible for the process to fill up the stderr
    /// buffer and deadlock.
    ///
    /// This is enabled by default.
    pub fn async_stderr(
        &mut self,
        yes: bool,
    ) -> &mut DecompressionReaderBuilder {
        self.command_builder.async_stderr(yes);
        self
    }
}

/// A streaming reader for decompressing the contents of a file.
///
/// The purpose of this reader is to provide a seamless way to decompress the
/// contents of file using existing tools in the current environment. This is
/// meant to be an alternative to using decompression libraries in favor of the
/// simplicity and portability of using external commands such as `gzip` and
/// `xz`. This does impose the overhead of spawning a process, so other means
/// for performing decompression should be sought if this overhead isn't
/// acceptable.
///
/// A decompression reader comes with a default set of matching rules that are
/// meant to associate file paths with the corresponding command to use to
/// decompress them. For example, a glob like `*.gz` matches gzip compressed
/// files with the command `gzip -d -c`. If a file path does not match any
/// existing rules, or if it matches a rule whose command does not exist in the
/// current environment, then the decompression reader passes through the
/// contents of the underlying file without doing any decompression.
///
/// The default matching rules are probably good enough for most cases, and if
/// they require revision, pull requests are welcome. In cases where they must
/// be changed or extended, they can be customized through the use of
/// [`DecompressionMatcherBuilder`] and [`DecompressionReaderBuilder`].
///
/// By default, this reader will asynchronously read the processes' stderr.
/// This prevents subtle deadlocking bugs for noisy processes that write a lot
/// to stderr. Currently, the entire contents of stderr is read on to the heap.
///
/// # Example
///
/// This example shows how to read the decompressed contents of a file without
/// needing to explicitly choose the decompression command to run.
///
/// Note that if you need to decompress multiple files, it is better to use
/// `DecompressionReaderBuilder`, which will amortize the cost of compiling the
/// matcher.
///
/// ```no_run
/// use std::{io::Read, process::Command};
///
/// use grep_cli::DecompressionReader;
///
/// let mut rdr = DecompressionReader::new("/usr/share/man/man1/ls.1.gz")?;
/// let mut contents = vec![];
/// rdr.read_to_end(&mut contents)?;
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[derive(Debug)]
pub struct DecompressionReader {
    rdr: Result<CommandReader, File>,
}

impl DecompressionReader {
    /// Build a new streaming reader for decompressing data.
    ///
    /// If decompression is done out-of-process and if there was a problem
    /// spawning the process, then its error is returned.
    ///
    /// If the given file path could not be matched with a decompression
    /// strategy, then a passthru reader is returned that does no
    /// decompression.
    ///
    /// This uses the default matching rules for determining how to decompress
    /// the given file. To change those matching rules, use
    /// [`DecompressionReaderBuilder`] and [`DecompressionMatcherBuilder`].
    ///
    /// When creating readers for many paths. it is better to use the builder
    /// since it will amortize the cost of constructing the matcher.
    pub fn new<P: AsRef<Path>>(
        path: P,
    ) -> Result<DecompressionReader, CommandError> {
        DecompressionReaderBuilder::new().build(path)
    }

    /// Creates a new "passthru" decompression reader that reads from the file
    /// corresponding to the given path without doing decompression and without
    /// executing another process.
    fn new_passthru(path: &Path) -> Result<DecompressionReader, CommandError> {
        let file = File::open(path)?;
        Ok(DecompressionReader { rdr: Err(file) })
    }

    /// Closes this reader, freeing any resources used by its underlying child
    /// process, if one was used. If the child process exits with a nonzero
    /// exit code, the returned Err value will include its stderr.
    ///
    /// `close` is idempotent, meaning it can be safely called multiple times.
    /// The first call closes the CommandReader and any subsequent calls do
    /// nothing.
    ///
    /// This method should be called after partially reading a file to prevent
    /// resource leakage. However there is no need to call `close` explicitly
    /// if your code always calls `read` to EOF, as `read` takes care of
    /// calling `close` in this case.
    ///
    /// `close` is also called in `drop` as a last line of defense against
    /// resource leakage. Any error from the child process is then printed as a
    /// warning to stderr. This can be avoided by explicitly calling `close`
    /// before the CommandReader is dropped.
    pub fn close(&mut self) -> io::Result<()> {
        match self.rdr {
            Ok(ref mut rdr) => rdr.close(),
            Err(_) => Ok(()),
        }
    }
}

impl io::Read for DecompressionReader {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        match self.rdr {
            Ok(ref mut rdr) => rdr.read(buf),
            Err(ref mut rdr) => rdr.read(buf),
        }
    }
}

/// Resolves a path to a program to a path by searching for the program in
/// `PATH`.
///
/// If the program could not be resolved, then an error is returned.
///
/// The purpose of doing this instead of passing the path to the program
/// directly to Command::new is that Command::new will hand relative paths
/// to CreateProcess on Windows, which will implicitly search the current
/// working directory for the executable. This could be undesirable for
/// security reasons. e.g., running ripgrep with the -z/--search-zip flag on an
/// untrusted directory tree could result in arbitrary programs executing on
/// Windows.
///
/// Note that this could still return a relative path if PATH contains a
/// relative path. We permit this since it is assumed that the user has set
/// this explicitly, and thus, desires this behavior.
///
/// # Platform behavior
///
/// On non-Windows, this is a no-op.
pub fn resolve_binary<P: AsRef<Path>>(
    prog: P,
) -> Result<PathBuf, CommandError> {
    if !cfg!(windows) {
        return Ok(prog.as_ref().to_path_buf());
    }
    try_resolve_binary(prog)
}

/// Resolves a path to a program to a path by searching for the program in
/// `PATH`.
///
/// If the program could not be resolved, then an error is returned.
///
/// The purpose of doing this instead of passing the path to the program
/// directly to Command::new is that Command::new will hand relative paths
/// to CreateProcess on Windows, which will implicitly search the current
/// working directory for the executable. This could be undesirable for
/// security reasons. e.g., running ripgrep with the -z/--search-zip flag on an
/// untrusted directory tree could result in arbitrary programs executing on
/// Windows.
///
/// Note that this could still return a relative path if PATH contains a
/// relative path. We permit this since it is assumed that the user has set
/// this explicitly, and thus, desires this behavior.
///
/// If `check_exists` is false or the path is already an absolute path this
/// will return immediately.
fn try_resolve_binary<P: AsRef<Path>>(
    prog: P,
) -> Result<PathBuf, CommandError> {
    use std::env;

    fn is_exe(path: &Path) -> bool {
        let Ok(md) = path.metadata() else { return false };
        !md.is_dir()
    }

    let prog = prog.as_ref();
    if prog.is_absolute() {
        return Ok(prog.to_path_buf());
    }
    let Some(syspaths) = env::var_os("PATH") else {
        let msg = "system PATH environment variable not found";
        return Err(CommandError::io(io::Error::new(
            io::ErrorKind::Other,
            msg,
        )));
    };
    for syspath in env::split_paths(&syspaths) {
        if syspath.as_os_str().is_empty() {
            continue;
        }
        let abs_prog = syspath.join(prog);
        if is_exe(&abs_prog) {
            return Ok(abs_prog.to_path_buf());
        }
        if abs_prog.extension().is_none() {
            for extension in ["com", "exe"] {
                let abs_prog = abs_prog.with_extension(extension);
                if is_exe(&abs_prog) {
                    return Ok(abs_prog.to_path_buf());
                }
            }
        }
    }
    let msg = format!("{}: could not find executable in PATH", prog.display());
    return Err(CommandError::io(io::Error::new(io::ErrorKind::Other, msg)));
}

fn default_decompression_commands() -> Vec<DecompressionCommand> {
    const ARGS_GZIP: &[&str] = &["gzip", "-d", "-c"];
    const ARGS_BZIP: &[&str] = &["bzip2", "-d", "-c"];
    const ARGS_XZ: &[&str] = &["xz", "-d", "-c"];
    const ARGS_LZ4: &[&str] = &["lz4", "-d", "-c"];
    const ARGS_LZMA: &[&str] = &["xz", "--format=lzma", "-d", "-c"];
    const ARGS_BROTLI: &[&str] = &["brotli", "-d", "-c"];
    const ARGS_ZSTD: &[&str] = &["zstd", "-q", "-d", "-c"];
    const ARGS_UNCOMPRESS: &[&str] = &["uncompress", "-c"];

    fn add(glob: &str, args: &[&str], cmds: &mut Vec<DecompressionCommand>) {
        let bin = match resolve_binary(Path::new(args[0])) {
            Ok(bin) => bin,
            Err(err) => {
                log::debug!("{}", err);
                return;
            }
        };
        cmds.push(DecompressionCommand {
            glob: glob.to_string(),
            bin,
            args: args
                .iter()
                .skip(1)
                .map(|s| OsStr::new(s).to_os_string())
                .collect(),
        });
    }
    let mut cmds = vec![];
    add("*.gz", ARGS_GZIP, &mut cmds);
    add("*.tgz", ARGS_GZIP, &mut cmds);
    add("*.bz2", ARGS_BZIP, &mut cmds);
    add("*.tbz2", ARGS_BZIP, &mut cmds);
    add("*.xz", ARGS_XZ, &mut cmds);
    add("*.txz", ARGS_XZ, &mut cmds);
    add("*.lz4", ARGS_LZ4, &mut cmds);
    add("*.lzma", ARGS_LZMA, &mut cmds);
    add("*.br", ARGS_BROTLI, &mut cmds);
    add("*.zst", ARGS_ZSTD, &mut cmds);
    add("*.zstd", ARGS_ZSTD, &mut cmds);
    add("*.Z", ARGS_UNCOMPRESS, &mut cmds);
    cmds
}


#include <iostream>
#include <nlohmann/json.hpp>

using json = nlohmann::json;

int main()
{
    // create JSON array
    const json array = {"first", "2nd", "third", "fourth"};

    // output element at index 2 (third element)
    std::cout << array.at(2) << '\n';
}


# Exceptions

## Overview

### Base type

All exceptions inherit from class `json::exception` (which in turn inherits from `std::exception`). It is used as the base class for all exceptions thrown by the `basic_json` class. This class can hence be used as "wildcard" to catch exceptions.

``` mermaid
classDiagram
  direction LR
    class `std::exception` {
        <<interface>>
    }

    class `json::exception` {
        +const int id
        +const char* what() const
    }

    class `json::parse_error` {
        +const std::size_t byte
    }

    class `json::invalid_iterator`
    class `json::type_error`
    class `json::out_of_range`
    class `json::other_error`

    `std::exception` <|-- `json::exception`
    `json::exception` <|-- `json::parse_error`
    `json::exception` <|-- `json::invalid_iterator`
    `json::exception` <|-- `json::type_error`
    `json::exception` <|-- `json::out_of_range`
    `json::exception` <|-- `json::other_error`
```

### Switch off exceptions

Exceptions are used widely within the library. They can, however, be switched off with either using the compiler flag `-fno-exceptions` or by defining the symbol [`JSON_NOEXCEPTION`](../api/macros/json_noexception.md). In this case, exceptions are replaced by `abort()` calls. You can further control this behavior by defining `JSON_THROW_USER` (overriding `#!cpp throw`), `JSON_TRY_USER` (overriding `#!cpp try`), and `JSON_CATCH_USER` (overriding `#!cpp catch`).

Note that [`JSON_THROW_USER`](../api/macros/json_throw_user.md) should leave the current scope (e.g., by throwing or aborting), as continuing after it may yield undefined behavior.

??? example

    The code below switches off exceptions and creates a log entry with a detailed error message in case of errors.

    ```cpp
    #include <iostream>

    #define JSON_TRY_USER if(true)
    #define JSON_CATCH_USER(exception) if(false)
    #define JSON_THROW_USER(exception)                           \
        {std::clog << "Error in " << __FILE__ << ":" << __LINE__ \
                   << " (function " << __FUNCTION__ << ") - "    \
                   << (exception).what() << std::endl;           \
         std::abort();}

    #include <nlohmann/json.hpp>
    ```

Note the explanatory [`what()`](https://en.cppreference.com/w/cpp/error/exception/what) string of exceptions is not available for MSVC if exceptions are disabled, see [#2824](https://github.com/nlohmann/json/discussions/2824).

See [documentation of `JSON_TRY_USER`, `JSON_CATCH_USER` and `JSON_THROW_USER`](../api/macros/json_throw_user.md) for more information.

### Extended diagnostic messages

Exceptions in the library are thrown in the local context of the JSON value they are detected. This makes detailed diagnostics messages, and hence debugging, difficult.

??? example

    ```cpp
    --8<-- "examples/diagnostics_standard.cpp"
    ```

    Output:

    ```
    --8<-- "examples/diagnostics_standard.output"
    ```

    This exception can be hard to debug if storing the value `#!c "12"` and accessing it is further apart.

To create better diagnostics messages, each JSON value needs a pointer to its parent value such that a global context (i.e., a path from the root value to the value that led to the exception) can be created. That global context is provided as [JSON Pointer](../features/json_pointer.md).

As this global context comes at the price of storing one additional pointer per JSON value and runtime overhead to maintain the parent relation, extended diagnostics are disabled by default. They can, however, be enabled by defining the preprocessor symbol [`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md) to `1` before including `json.hpp`.

??? example

    ```cpp
    --8<-- "examples/diagnostics_extended.cpp"
    ```

    Output:

    ```
    --8<-- "examples/diagnostics_extended.output"
    ```

    Now the exception message contains a JSON Pointer `/address/housenumber` that indicates which value has the wrong type.

See [documentation of `JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md) for more information.

## Parse errors

The library throws this exception when a parse error occurs. Parse errors
can occur during the deserialization of JSON text, CBOR, MessagePack, as well
as when using JSON Patch.

Exceptions have ids 1xx.

!!! info "Byte index"

    Member `byte` holds the byte index of the last read character in the input
    file.

    For an input with n bytes, 1 is the index of the first character and n+1
    is the index of the terminating null byte or the end of file. This also
    holds true when reading a byte vector (CBOR or MessagePack).

??? example

    The following code shows how a `parse_error` exception can be caught.

    ```cpp
    --8<-- "examples/parse_error.cpp"
    ```

    Output:

    ```
    --8<-- "examples/parse_error.output"
    ```

### json.exception.parse_error.101

This error indicates a syntax error while deserializing a JSON text. The error message describes that an unexpected token (character) was encountered, and the member `byte` indicates the error position.

!!! failure "Example message"

    Input ended prematurely:

    ```
    [json.exception.parse_error.101] parse error at 2: unexpected end of input; expected string literal
    ```

    No input:

    ```
    [json.exception.parse_error.101] parse error at line 1, column 1: attempting to parse an empty input; check that your input string or stream contains the expected JSON
    ```

    Control character was not escaped:

    ```
    [json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0009 (HT) must be escaped to \u0009 or \\; last read: '"<U+0009>'"
    ```

    String was not closed:

    ```
    [json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: missing closing quote; last read: '"'
    ```

    Invalid number format:

    ```
    [json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid number; expected '+', '-', or digit after exponent; last read: '1E'
    ```

    `\u` was not be followed by four hex digits:

    ```
    [json.exception.parse_error.101] parse error at line 1, column 6: syntax error while parsing value - invalid string: '\u' must be followed by 4 hex digits; last read: '"\u01"'
    ```

    Invalid UTF-8 surrogate pair:

    ```
    [json.exception.parse_error.101] parse error at line 1, column 13: syntax error while parsing value - invalid string: surrogate U+DC00..U+DFFF must follow U+D800..U+DBFF; last read: '"\uD7FF\uDC00'"
    ```

    Invalid UTF-8 byte:

    ```
    [json.exception.parse_error.101] parse error at line 3, column 24: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '"vous \352t'
    ```

!!! tip

    - Make sure the input is correctly read. Try to write the input to standard output to check if, for instance, the input file was successfully opened.
    - Paste the input to a JSON validator like <http://jsonlint.com> or a tool like [jq](https://stedolan.github.io/jq/).

### json.exception.parse_error.102

JSON uses the `\uxxxx` format to describe Unicode characters. Code points above 0xFFFF are split into two `\uxxxx` entries ("surrogate pairs"). This error indicates that the surrogate pair is incomplete or contains an invalid code point.

!!! failure "Example message"

    ```
    parse error at 14: missing or wrong low surrogate
    ```

!!! note

    This exception is not used any more. Instead [json.exception.parse_error.101](#jsonexceptionparse_error101) with a more detailed description is used.

### json.exception.parse_error.103

Unicode supports code points up to 0x10FFFF. Code points above 0x10FFFF are invalid.

!!! failure "Example message"

    ```
    parse error: code points above 0x10FFFF are invalid
    ```

!!! note

    This exception is not used any more. Instead [json.exception.parse_error.101](#jsonexceptionparse_error101) with a more detailed description is used.

### json.exception.parse_error.104

[RFC 6902](https://tools.ietf.org/html/rfc6902) requires a JSON Patch document to be a JSON document that represents an array of objects.

!!! failure "Example message"

    ```
    [json.exception.parse_error.104] parse error: JSON patch must be an array of objects
    ```

### json.exception.parse_error.105

An operation of a JSON Patch document must contain exactly one "op" member, whose value indicates the operation to perform. Its value must be one of "add", "remove", "replace", "move", "copy", or "test"; other values are errors.

!!! failure "Example message"

    ```
    [json.exception.parse_error.105] parse error: operation 'add' must have member 'value'
    ```
    ```
    [json.exception.parse_error.105] parse error: operation 'copy' must have string member 'from'
    ```
    ```
    [json.exception.parse_error.105] parse error: operation value 'foo' is invalid
    ```

### json.exception.parse_error.106

An array index in a JSON Pointer ([RFC 6901](https://tools.ietf.org/html/rfc6901)) may be `0` or any number without a leading `0`.

!!! failure "Example message"

    ```
    [json.exception.parse_error.106] parse error: array index '01' must not begin with '0'
    ```

### json.exception.parse_error.107

A JSON Pointer must be a Unicode string containing a sequence of zero or more reference tokens, each prefixed by a `/` character.

!!! failure "Example message"

    ```
    [json.exception.parse_error.107] parse error at byte 1: JSON pointer must be empty or begin with '/' - was: 'foo'
    ```

### json.exception.parse_error.108

In a JSON Pointer, only `~0` and `~1` are valid escape sequences.

!!! failure "Example message"

    ```
    [json.exception.parse_error.108] parse error: escape character '~' must be followed with '0' or '1'
    ```

### json.exception.parse_error.109

A JSON Pointer array index must be a number.

!!! failure "Example messages"

    ```
    [json.exception.parse_error.109] parse error: array index 'one' is not a number
    ```
    ```
    [json.exception.parse_error.109] parse error: array index '+1' is not a number
    ```

### json.exception.parse_error.110

When parsing a [binary format](../features/binary_formats/index.md), the byte vector ends before the complete value has
been read.

!!! failure "Example messages"

    ```
    [json.exception.parse_error.110] parse error at byte 5: syntax error while parsing CBOR string: unexpected end of input
    ```
    ```
    [json.exception.parse_error.110] parse error at byte 2: syntax error while parsing UBJSON value: expected end of input; last byte: 0x5A
    ```
    ```
    [json.exception.parse_error.110] parse error at byte 8: syntax error while parsing BSON number: unexpected end of input
    ```

### json.exception.parse_error.112

An unexpected byte was read in a [binary format](../features/binary_formats/index.md) or length information is invalid ([BSON](../features/binary_formats/bson.md)).

!!! failure "Example messages"

    ```
    [json.exception.parse_error.112] parse error at byte 1: syntax error while parsing CBOR value: invalid byte: 0x1C
    ```
    ```
    [json.exception.parse_error.112] parse error at byte 1: syntax error while parsing MessagePack value: invalid byte: 0xC1
    ```
    ```
    [json.exception.parse_error.112] parse error at byte 4: syntax error while parsing BJData size: expected '#' after type information; last byte: 0x02
    ```
    ```
    [json.exception.parse_error.112] parse error at byte 4: syntax error while parsing UBJSON size: expected '#' after type information; last byte: 0x02
    ```
    ```
    [json.exception.parse_error.112] parse error at byte 10: syntax error while parsing BSON string: string length must be at least 1, is -2147483648
    ```
    ```
    [json.exception.parse_error.112] parse error at byte 15: syntax error while parsing BSON binary: byte array length cannot be negative, is -1
    ```
    ```
    [json.exception.parse_error.112] parse error at byte 9: syntax error while parsing CBOR value: negative integer overflow
    ```
    ```
    [json.exception.parse_error.112] parse error at byte 5: syntax error while parsing BSON document: document size 6 does not match the number of bytes read (5)
    ```

### json.exception.parse_error.113

A string could not be read from a [binary format](../features/binary_formats/index.md): either a value that is not a
string was read where one was required (for instance as a map key), or the string's length specification is invalid.

!!! failure "Example messages"

    ```
    [json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0xFF
    ```
    ```
    [json.exception.parse_error.113] parse error at byte 2: syntax error while parsing MessagePack string: expected length specification (0xA0-0xBF, 0xD9-0xDB); last byte: 0xFF
    ```
    ```
    [json.exception.parse_error.113] parse error at byte 2: syntax error while parsing UBJSON char: byte after 'C' must be in range 0x00..0x7F; last byte: 0x82
    ```
    ```
    [json.exception.parse_error.113] parse error at byte 3: syntax error while parsing BJData string: string length must not be negative
    ```

### json.exception.parse_error.114

The parsing of the corresponding BSON record type is not implemented (yet).

!!! failure "Example message"

    ```
    [json.exception.parse_error.114] parse error at byte 5: Unsupported BSON record type 0xFF
    ```

### json.exception.parse_error.115

A UBJSON high-precision number could not be parsed.

!!! failure "Example message"

    ```
    [json.exception.parse_error.115] parse error at byte 5: syntax error while parsing UBJSON high-precision number: invalid number text: 1A
    ```

## Iterator errors

This exception is thrown if iterators passed to a library function do not match
the expected semantics.

Exceptions have ids 2xx.

??? example

    The following code shows how an `invalid_iterator` exception can be caught.

    ```cpp
    --8<-- "examples/invalid_iterator.cpp"
    ```

    Output:

    ```
    --8<-- "examples/invalid_iterator.output"
    ```

### json.exception.invalid_iterator.201

The iterators passed to constructor `basic_json(InputIT first, InputIT last)` are not compatible, meaning they do not belong to the same container. Therefore, the range (`first`, `last`) is invalid.

!!! failure "Example message"

    ```
    [json.exception.invalid_iterator.201] iterators are not compatible
    ```

### json.exception.invalid_iterator.202

In the [erase](../api/basic_json/erase.md) or insert function, the passed iterator `pos` does not belong to the JSON value for which the function was called. It hence does not define a valid position for the deletion/insertion.

!!! failure "Example messages"

    ```
    [json.exception.invalid_iterator.202] iterator does not fit current value
    ```
    ```
    [json.exception.invalid_iterator.202] iterators first and last must point to objects
    ```

### json.exception.invalid_iterator.203

Either iterator passed to function [`erase(IteratorType first, IteratorType last`)](../api/basic_json/erase.md) does not belong to the JSON value from which values shall be erased. It hence does not define a valid range to delete values from.

!!! failure "Example message"

    ```
    [json.exception.invalid_iterator.203] iterators do not fit current value
    ```

### json.exception.invalid_iterator.204

When an iterator range for a primitive type (number, boolean, or string) is passed to a constructor or an [erase](../api/basic_json/erase.md) function, this range has to be exactly (`begin(),` `end()),` because this is the only way the single stored value is expressed. All other ranges are invalid.

!!! failure "Example message"

    ```
    [json.exception.invalid_iterator.204] iterators out of range
    ```

### json.exception.invalid_iterator.205

When an iterator for a primitive type (number, boolean, or string) is passed to an [erase](../api/basic_json/erase.md) function, the iterator has to be the `begin()` iterator, because it is the only way to address the stored value. All other iterators are invalid.

!!! failure "Example message"

    ```
    [json.exception.invalid_iterator.205] iterator out of range
    ```

### json.exception.invalid_iterator.206

The iterators passed to constructor `basic_json(InputIT first, InputIT last)` belong to a JSON null value and hence to not define a valid range.

!!! failure "Example message"

    ```
    [json.exception.invalid_iterator.206] cannot construct with iterators from null
    ```

### json.exception.invalid_iterator.207

The `key()` member function can only be used on iterators belonging to a JSON object, because other types do not have a concept of a key.

!!! failure "Example message"

    ```
    [json.exception.invalid_iterator.207] cannot use key() for non-object iterators
    ```

### json.exception.invalid_iterator.208

The `operator[]` to specify a concrete offset cannot be used on iterators belonging to a JSON object, because JSON objects are unordered.

!!! failure "Example message"

    ```
    [json.exception.invalid_iterator.208] cannot use operator[] for object iterators
    ```

### json.exception.invalid_iterator.209

The offset operators (`+`, `-`, `+=`, `-=`) cannot be used on iterators belonging to a JSON object, because JSON objects are unordered.

!!! failure "Example message"

    ```
    [json.exception.invalid_iterator.209] cannot use offsets with object iterators
    ```

### json.exception.invalid_iterator.210

The iterator range passed to the insert function is not compatible, meaning they do not belong to the same container. Therefore, the range (`first`, `last`) is invalid.

!!! failure "Example message"

    ```
    [json.exception.invalid_iterator.210] iterators do not fit
    ```

### json.exception.invalid_iterator.211

The iterator range passed to the insert function must not be a subrange of the container to insert to.

!!! failure "Example message"

    ```
    [json.exception.invalid_iterator.211] passed iterators may not belong to container
    ```

### json.exception.invalid_iterator.212

When two iterators are compared, they must belong to the same container.

!!! failure "Example message"

    ```
    [json.exception.invalid_iterator.212] cannot compare iterators of different containers
    ```

### json.exception.invalid_iterator.213

The order of object iterators cannot be compared, because JSON objects are unordered.

!!! failure "Example message"

    ```
    [json.exception.invalid_iterator.213] cannot compare order of object iterators
    ```

### json.exception.invalid_iterator.214

Cannot retrieve value from iterator: The iterator either refers to a null value, or it refers to a primitive type (number, boolean, or string), but does not match the iterator returned by `begin()`.

!!! failure "Example message"

    ```
    [json.exception.invalid_iterator.214] cannot get value
    ```

## Type errors

This exception is thrown in case of a type error; that is, a library function is executed on a JSON value whose type does not match the expected semantics.

Exceptions have ids 3xx.

??? example

    The following code shows how a `type_error` exception can be caught.

    ```cpp
    --8<-- "examples/type_error.cpp"
    ```

    Output:

    ```
    --8<-- "examples/type_error.output"
    ```

### json.exception.type_error.301

To create an object from an initializer list, the initializer list must consist only of a list of pairs whose first element is a string. When this constraint is violated, an array is created instead.

!!! failure "Example message"

    ```
    [json.exception.type_error.301] cannot create object from initializer list
    ```

### json.exception.type_error.302

During implicit or explicit value conversion, the JSON type must be compatible with the target type. For instance, a JSON string can only be converted into string types, but not into numbers or boolean types.

!!! failure "Example messages"

    ```
    [json.exception.type_error.302] type must be object, but is null
    ```
    ```
    [json.exception.type_error.302] type must be string, but is object
    ```

### json.exception.type_error.303

To retrieve a reference to a value stored in a `basic_json` object with `get_ref`, the type of the reference must match the value type. For instance, for a JSON array, the `ReferenceType` must be `array_t &`.

!!! failure "Example messages"

    ```
    [json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is object
    ```
    ```
    [json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is number"
    ```

### json.exception.type_error.304

The `at()` member functions can only be executed for certain JSON types.

!!! failure "Example messages"

    ```
    [json.exception.type_error.304] cannot use at() with string
    ```
    ```
    [json.exception.type_error.304] cannot use at() with number
    ```

### json.exception.type_error.305

The `operator[]` member functions can only be executed for certain JSON types.

!!! failure "Example messages"

    ```
    [json.exception.type_error.305] cannot use operator[] with a string argument with array
    ```
    ```
    [json.exception.type_error.305] cannot use operator[] with a numeric argument with object
    ```

### json.exception.type_error.306

The `value()` member functions can only be executed for certain JSON types.

!!! failure "Example message"

    ```
    [json.exception.type_error.306] cannot use value() with number
    ```

### json.exception.type_error.307

The [`erase()`](../api/basic_json/erase.md) member functions can only be executed for certain JSON types.

!!! failure "Example message"

    ```
    [json.exception.type_error.307] cannot use erase() with string
    ```

### json.exception.type_error.308

The `push_back()` and `operator+=` member functions can only be executed for certain JSON types.

!!! failure "Example message"

    ```
    [json.exception.type_error.308] cannot use push_back() with string
    ```

### json.exception.type_error.309

The `insert()` member functions can only be executed for certain JSON types.

!!! failure "Example messages"

    ```
    [json.exception.type_error.309] cannot use insert() with array
    ```
    ```
    [json.exception.type_error.309] cannot use insert() with number
    ```

### json.exception.type_error.310

The `swap()` member functions can only be executed for certain JSON types.

!!! failure "Example message"

    ```
    [json.exception.type_error.310] cannot use swap() with number
    ```

### json.exception.type_error.311

The `emplace()` and `emplace_back()` member functions can only be executed for certain JSON types.

!!! failure "Example messages"

    ```
    [json.exception.type_error.311] cannot use emplace() with number
    ```
    ```
    [json.exception.type_error.311] cannot use emplace_back() with number
    ```

### json.exception.type_error.312

The `update()` member functions can only be executed for certain JSON types.

!!! failure "Example message"

    ```
    [json.exception.type_error.312] cannot use update() with array
    ```

### json.exception.type_error.313

The `unflatten` function converts an object whose keys are JSON Pointers back into an arbitrary nested JSON value. The JSON Pointers must not overlap, because then the resulting value would not be well-defined.

!!! failure "Example message"

    ```
    [json.exception.type_error.313] invalid value to unflatten
    ```

### json.exception.type_error.314

The `unflatten` function only works for an object whose keys are JSON Pointers.

!!! failure "Example message"

    Calling `unflatten()` on an array `#!json [1,2,3]`:

    ```
    [json.exception.type_error.314] only objects can be unflattened
    ```

### json.exception.type_error.315

The `unflatten()` function only works for an object whose keys are JSON Pointers and whose values are primitive.

!!! failure "Example message"

    Calling `unflatten()` on an object `#!json {"/1", [1,2,3]}`:

    ```
    [json.exception.type_error.315] values in object must be primitive
    ```

### json.exception.type_error.316

The `dump()` function only works with UTF-8 encoded strings; that is, if you assign a `std::string` to a JSON value, make sure it is UTF-8 encoded.

!!! failure "Example message"

    Calling `dump()` on a JSON value containing an ISO 8859-1 encoded string:
    ```
    [json.exception.type_error.316] invalid UTF-8 byte at index 15: 0x6F
    ```

!!! tip

    - Store the source file with UTF-8 encoding.
    - Pass an error handler as last parameter to the `dump()` function to avoid this exception:
        - `json::error_handler_t::replace` will replace invalid bytes sequences with `U+FFFD`
        - `json::error_handler_t::ignore` will silently ignore invalid byte sequences

### json.exception.type_error.317

The dynamic type of the object cannot be represented in the requested serialization format (e.g., a raw `true` or `null` JSON object cannot be serialized to BSON)

!!! failure "Example messages"

    Serializing `#!json null` to BSON:
    ```
    [json.exception.type_error.317] to serialize to BSON, top-level type must be object, but is null
    ```
    Serializing `#!json [1,2,3]` to BSON:
    ```
    [json.exception.type_error.317] to serialize to BSON, top-level type must be object, but is array
    ```

!!! tip

    Encapsulate the JSON value in an object. That is, instead of serializing `#!json true`, serialize `#!json {"value": true}`

## Out of range

This exception is thrown in case a library function is called on an input parameter that exceeds the expected range, for instance, in the case of array indices or nonexisting object keys.

Exceptions have ids 4xx.

??? example

    The following code shows how an `out_of_range` exception can be caught.

    ```cpp
    --8<-- "examples/out_of_range.cpp"
    ```

    Output:

    ```
    --8<-- "examples/out_of_range.output"
    ```

### json.exception.out_of_range.401

The provided array index `i` is larger than `size-1`.

!!! failure "Example message"

    ```
    array index 3 is out of range
    ```

### json.exception.out_of_range.402

The special array index `-` in a JSON Pointer never describes a valid element of the array, but the index past the end. That is, it can only be used to add elements at this position, but not to read it.

!!! failure "Example message"

    ```
    array index '-' (3) is out of range
    ```

### json.exception.out_of_range.403

The provided key was not found in the JSON object.

!!! failure "Example message"

    ```
    key 'foo' not found
    ```

### json.exception.out_of_range.404

A reference token in a JSON Pointer could not be resolved.

!!! failure "Example message"

    ```
    unresolved reference token 'foo'
    ```

### json.exception.out_of_range.405

The JSON Patch operations 'remove' and 'add' cannot be applied to the root element of the JSON value.

!!! failure "Example message"

    ```
    JSON pointer has no parent
    ```

### json.exception.out_of_range.406

A parsed number could not be stored as without changing it to NaN or INF.

!!! failure "Example message"

    ```
    number overflow parsing '10E1000'
    ```

### json.exception.out_of_range.407

This exception previously indicated that the UBJSON and BSON binary formats did not support integer numbers greater than
9223372036854775807 due to limitations in the implemented mapping. However, these limitations have since been resolved,
and this exception no longer occurs.

!!! success "Exception cannot occur any more"

    - Since version 3.9.0, integer numbers beyond int64 are serialized as high-precision UBJSON numbers.
    - Since version 3.12.0, integer numbers beyond int64 are serialized as uint64 BSON numbers.

### json.exception.out_of_range.408

The size of an array or object in a [binary format](../features/binary_formats/index.md) exceeds the maximal capacity:
the size following `#` for [UBJSON](../features/binary_formats/ubjson.md)/[BJData](../features/binary_formats/bjdata.md),
or the encoded length for [CBOR](../features/binary_formats/cbor.md).

!!! failure "Example messages"

    ```
    excessive array size: 8658170730974374167
    ```
    ```
    [json.exception.out_of_range.408] syntax error while parsing CBOR size: excessive array size
    ```
    ```
    [json.exception.out_of_range.408] syntax error while parsing CBOR size: excessive map size
    ```

### json.exception.out_of_range.409

Key identifiers to be serialized to BSON cannot contain code point U+0000, since the key is stored as zero-terminated c-string.

!!! failure "Example message"

    ```
    BSON key cannot contain code point U+0000 (at byte 2)
    ```

### json.exception.out_of_range.410

This exception is thrown when an undefined value is used with
[`NLOHMANN_JSON_SERIALIZE_ENUM_STRICT`](../api/macros/nlohmann_json_serialize_enum_strict.md), or when an array index in
a JSON pointer exceeds the range of `size_type` (e.g., on 32-bit platforms).

!!! failure "Example message"

    ```
    enum value out of range
    array index 18446744073709551616 exceeds size_type
    ```

### json.exception.out_of_range.411

A JSON Patch `add` operation cannot be applied because the target location's parent is neither an object nor an array. Per [RFC 6902](https://datatracker.ietf.org/doc/html/rfc6902), an `add` target must reference a member of an existing object or an element of an existing array; a primitive value (string, number, boolean, etc.) cannot receive a new member or element.

!!! failure "Example message"

    ```
    cannot add value: the JSON Patch 'add' target's parent is of type string, but must be an object or array
    ```

!!! note

    This exception was added in version 3.13.0. Before that, this situation hit an internal assertion (aborting the program in debug builds) or was silently ignored when assertions were disabled.

### json.exception.out_of_range.412

BSON stores the length of documents, arrays, strings, and binary values in a signed 32-bit integer. This exception is thrown when a value is too large to be described by such a length field.

!!! failure "Example message"

    ```
    BSON length 2147483661 exceeds maximum of 2147483647
    ```

!!! note

    This exception was added in version 3.13.0. Before that, the length was silently truncated, and
    [`to_bson`](../api/basic_json/to_bson.md) produced documents with negative length prefixes that
    [`from_bson`](../api/basic_json/from_bson.md) rejected.

## Further exceptions

This exception is thrown in case of errors that cannot be classified with the
other exception types.

Exceptions have ids 5xx.

??? example

    The following code shows how an `other_error` exception can be caught.

    ```cpp
    --8<-- "examples/other_error.cpp"
    ```

    Output:

    ```
    --8<-- "examples/other_error.output"
    ```

### json.exception.other_error.501

A JSON Patch operation 'test' failed. The unsuccessful operation is also printed.

!!! failure "Example message"

    Executing `#!json {"op":"test", "path":"/baz", "value":"bar"}` on `#!json {"baz": "qux"}`:

    ```
    [json.exception.other_error.501] unsuccessful: {"op":"test","path":"/baz","value":"bar"}
    ```


---
title: Vite 5.0 is out!
author:
  name: The Vite Team
date: 2023-11-16
sidebar: false
head:
  - - meta
    - property: og:type
      content: website
  - - meta
    - property: og:title
      content: Announcing Vite 5
  - - meta
    - property: og:image
      content: https://vite.dev/og-image-announcing-vite5.webp
  - - meta
    - property: og:url
      content: https://vite.dev/blog/announcing-vite5
  - - meta
    - property: og:description
      content: Vite 5 Release Announcement
  - - meta
    - name: twitter:card
      content: summary_large_image
---

# Vite 5.0 is out!

_November 16, 2023_

![Vite 5 Announcement Cover Image](/og-image-announcing-vite5.webp)

Vite 4 [was released](./announcing-vite4.md) almost a year ago, and it served as a solid base for the ecosystem. npm downloads per week jumped from 2.5 million to 7.5 million, as projects keep building on a shared infrastructure. Frameworks continued to innovate, and on top of [Astro](https://astro.build/), [Nuxt](https://nuxt.com/), [SvelteKit](https://kit.svelte.dev/), [Solid Start](https://www.solidjs.com/blog/introducing-solidstart), [Qwik City](https://qwik.builder.io/qwikcity/overview/), between others, we saw new frameworks joining and making the ecosystem stronger. [RedwoodJS](https://redwoodjs.com/) and [Remix](https://remix.run/) switching to Vite paves the way for further adoption in the React ecosystem. [Vitest](https://vitest.dev) kept growing at an even faster pace than Vite. Its team has been hard at work and will soon [release Vitest 1.0](https://github.com/vitest-dev/vitest/issues/3596). The story of Vite when used with other tools such as [Storybook](https://storybook.js.org), [Nx](https://nx.dev), and [Playwright](https://playwright.dev) kept improving, and the same goes for environments, with Vite dev working both in [Deno](https://deno.com) and [Bun](https://bun.sh).

We had the second edition of [ViteConf](https://viteconf.org/23/replay) a month ago, hosted by [StackBlitz](https://stackblitz.com). Like last year, most of the projects in the ecosystem got together to share ideas and connect to keep expanding the commons. We're also seeing new pieces complement the meta-framework tool belt like [Volar](https://volarjs.dev/) and [Nitro](https://nitro.build/). The Rollup team released [Rollup 4](https://rollupjs.org) that same day, a tradition Lukas started last year.

Six months ago, Vite 4.3 [was released](./announcing-vite4.md). This release significantly improved the dev server performance. However, there is still ample room for improvement. At ViteConf, [Evan You unveiled Vite's long-term plan to work on Rolldown](https://www.youtube.com/watch?v=hrdwQHoAp0M), a Rust-port of Rollup with compatible APIs. Once it is ready, we intend to use it in Vite Core to take on the tasks of both Rollup and esbuild. This will mean a boost in build performance (and later on in dev performance too as we move perf-sensitive parts of Vite itself to Rust), and a big reduction of inconsistencies between dev and build. Rolldown is currently in early stages and the team is preparing to open source the codebase before the end of the year. Stay tuned!

Today, we mark another big milestone in Vite's path. The Vite [team](/team), [contributors](https://github.com/vitejs/vite/graphs/contributors), and ecosystem partners, are excited to announce the release of Vite 5. Vite is now using [Rollup 4](https://github.com/vitejs/vite/pull/14508), which already represents a big boost in build performance. And there are also new options to improve your dev server performance profile.

Vite 5 focuses on cleaning up the API (removing deprecated features) and streamlines several features closing long-standing issues, for example switching `define` to use proper AST replacements instead of regexes. We also continue to take steps to future-proof Vite (Node.js 18+ is now required, and [the CJS Node API has been deprecated](/guide/migration#deprecate-cjs-node-api)).

Quick links:

- [Docs](/)
- [Migration Guide](/guide/migration)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md#500-2023-11-16)

Docs in other languages:

- [简体中文](https://cn.vite.dev/)
- [日本語](https://ja.vite.dev/)
- [Español](https://es.vite.dev/)
- [Português](https://pt.vite.dev/)
- [한국어](https://ko.vite.dev/)
- [Deutsch](https://de.vite.dev/) (new translation!)

If you're new to Vite, we suggest reading first the [Getting Started](/guide/) and [Features](/guide/features) guides.

We appreciate the more than [850 contributors to Vite Core](https://github.com/vitejs/vite/graphs/contributors), and the maintainers and contributors of Vite plugins, integrations, tools, and translations that have helped us reach here. We encourage you to get involved and continue to improve Vite with us. You can learn more at our [Contributing Guide](https://github.com/vitejs/vite/blob/main/CONTRIBUTING.md). To get started, we recommend [triaging issues](https://github.com/vitejs/vite/issues), [reviewing PRs](https://github.com/vitejs/vite/pulls), sending failing tests PRs based on open issues, and helping others in [Discussions](https://github.com/vitejs/vite/discussions) and Vite Land's [help forum](https://discord.com/channels/804011606160703521/1019670660856942652). You'll learn a lot along the way and have a smooth path to further contributions to the project. If you have doubts, join us on our [Discord community](https://chat.vite.dev) and say hi on the [#contributing channel](https://discord.com/channels/804011606160703521/804439875226173480).

To stay up to date, follow us on [X](https://twitter.com/vite_js) or [Mastodon](https://webtoo.ls/@vite).

## Quick start with Vite 5

Use `pnpm create vite` to scaffold a Vite project with your preferred framework, or open a started template online to play with Vite 5 using [vite.new](https://vite.new). You can also run `pnpm create vite-extra` to get access to templates from other frameworks and runtimes (Solid, Deno, SSR, and library starters). `create vite-extra` templates are also available when you run `create vite` under the `Others` option.

Note that Vite starter templates are intended to be used as a playground to test Vite with different frameworks. When building your next project, we recommend reaching out to the starters recommended by each framework. Some frameworks now redirect in `create vite` to their starters too (`create-vue` and `Nuxt 3` for Vue, and `SvelteKit` for Svelte).

## Node.js Support

Vite no longer supports Node.js 14 / 16 / 17 / 19, which reached its EOL. Node.js 18 / 20+ is now required.

## Performance

On top of Rollup 4's build performance improvements, there is a new guide to help you identify and fix common performance issues at [https://vite.dev/guide/performance](/guide/performance).

Vite 5 also introduces [server.warmup](/guide/performance.html#warm-up-frequently-used-files), a new feature to improve startup time. It lets you define a list of modules that should be pre-transformed as soon as the server starts. When using [`--open` or `server.open`](/config/server-options.html#server-open), Vite will also automatically warm up the entry point of your app or the provided URL to open.

## Main Changes

- [Vite is now powered by Rollup 4](/guide/migration#rollup-4)
- [The CJS Node API has been deprecated](/guide/migration#deprecate-cjs-node-api)
- [Rework `define` and `import.meta.env.*` replacement strategy](/guide/migration#rework-define-and-import-meta-env-replacement-strategy)
- [SSR externalized modules value now matches production](/guide/migration#ssr-externalized-modules-value-now-matches-production)
- [`worker.plugins` is now a function](/guide/migration#worker-plugins-is-now-a-function)
- [Allow path containing `.` to fallback to index.html](/guide/migration#allow-path-containing-to-fallback-to-index-html)
- [Align dev and preview HTML serving behavior](https://v5.vite.dev/guide/migration#align-dev-and-preview-html-serving-behaviour)
- [Manifest files are now generated in `.vite` directory by default](/guide/migration#manifest-files-are-now-generated-in-vite-directory-by-default)
- [CLI shortcuts require an additional `Enter` press](/guide/migration#cli-shortcuts-require-an-additional-enter-press)
- [Update `experimentalDecorators` and `useDefineForClassFields` TypeScript behavior](https://v5.vite.dev/guide/migration#update-experimentaldecorators-and-usedefineforclassfields-typescript-behaviour)
- [Remove `--https` flag and `https: true`](/guide/migration#remove-https-flag-and-https-true)
- [Remove `resolvePackageEntry` and `resolvePackageData` APIs](/guide/migration#remove-resolvepackageentry-and-resolvepackagedata-apis)
- [Removes previously deprecated APIs](/guide/migration#removed-deprecated-apis)
- [Read more about advanced changes affecting plugin and tool authors](/guide/migration#advanced)

## Migrating to Vite 5

We have worked with ecosystem partners to ensure a smooth migration to this new major. Once again, [vite-ecosystem-ci](https://www.youtube.com/watch?v=7L4I4lDzO48) has been crucial to help us make bolder changes while avoiding regressions. We're thrilled to see other ecosystems adopt similar schemes to improve the collaboration between their projects and downstream maintainers.

For most projects, the update to Vite 5 should be straight forward. But we advise reviewing the [detailed Migration Guide](/guide/migration) before upgrading.

A low level breakdown with the full list of changes to Vite core can be found at the [Vite 5 Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md#500-2023-11-16).

## Acknowledgments

Vite 5 is the result of long hours of work by our community of contributors, downstream maintainers, plugins authors, and the [Vite Team](/team). A big shout out to [Bjorn Lu](https://twitter.com/bluwyoo) for leading the release process for this major.

We're also thankful to individuals and companies sponsoring Vite development. [StackBlitz](https://stackblitz.com/), [Nuxt Labs](https://nuxtlabs.com/), and [Astro](https://astro.build) continue to invest in Vite by hiring Vite team members. A shout out to sponsors on [Vite's GitHub Sponsors](https://github.com/sponsors/vitejs), [Vite's Open Collective](https://opencollective.com/vite), and [Evan You's GitHub Sponsors](https://github.com/sponsors/yyx990803). A special mention to [Remix](https://remix.run/) for becoming a Gold sponsor and contributing back after switching to Vite.


#!/usr/bin/env python3

"""Make a release.

Usage:
  release.py [<branch>]

For the release command $FMT_TOKEN should contain a GitHub personal access token
obtained from https://github.com/settings/tokens.
"""

from __future__ import print_function
import datetime, docopt, errno, fileinput, json, os
import re, shutil, sys
from subprocess import check_call
import urllib.request

class Git:
    def __init__(self, dir):
        self.dir = dir

    def call(self, method, args, **kwargs):
        return check_call(['git', method] + list(args), **kwargs)

    def add(self, *args):
        return self.call('add', args, cwd=self.dir)

    def checkout(self, *args):
        return self.call('checkout', args, cwd=self.dir)

    def clean(self, *args):
        return self.call('clean', args, cwd=self.dir)

    def clone(self, *args):
        return self.call('clone', list(args) + [self.dir])

    def fetch(self, *args):
        return self.call('fetch', args, cwd=self.dir)

    def commit(self, *args):
        return self.call('commit', args, cwd=self.dir)

    def pull(self, *args):
        return self.call('pull', args, cwd=self.dir)

    def push(self, *args):
        return self.call('push', args, cwd=self.dir)

    def reset(self, *args):
        return self.call('reset', args, cwd=self.dir)

    def update(self, *args):
        clone = not os.path.exists(self.dir)
        if clone:
            self.clone(*args)
        return clone

def clean_checkout(repo, branch):
    repo.clean('-f', '-d')
    repo.fetch('origin')
    repo.checkout(branch)
    # Hard-reset to the remote so a reused clone picks up new commits
    # instead of building from stale local state.
    repo.reset('--hard', 'origin/' + branch)

class Runner:
    def __init__(self, cwd, env=None):
        self.cwd = cwd
        self.env = env

    def __call__(self, *args, **kwargs):
        kwargs['cwd'] = kwargs.get('cwd', self.cwd)
        if self.env is not None:
            kwargs['env'] = kwargs.get('env', self.env)
        check_call(args, **kwargs)

def create_build_env():
    """Create a build environment."""
    class Env:
        pass
    env = Env()
    env.fmt_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    env.build_dir = 'build'
    env.fmt_repo = Git(os.path.join(env.build_dir, 'fmt'))
    return env

def create_doc_env(env, fmt_repo):
    """Create a virtualenv with the pinned documentation dependencies and
    return an environment dict with it prepended to PATH. This ensures the
    docs are built with the exact mkdocs/mkdocstrings versions required by the
    custom handler, regardless of what is installed system-wide."""
    # Use an absolute path so the venv resolves on PATH regardless of the
    # working directory the build steps run in.
    venv_dir = os.path.abspath(os.path.join(env.build_dir, 'venv'))
    shutil.rmtree(venv_dir, ignore_errors=True)
    check_call([sys.executable, '-m', 'venv', venv_dir])
    venv_bin = os.path.join(venv_dir, 'bin')
    pip = os.path.join(venv_bin, 'pip')
    check_call([pip, 'install', '--quiet', '--upgrade', 'pip'])
    requirements = os.path.join(
        fmt_repo.dir, 'support', 'doc-requirements.txt')
    check_call(
        [pip, 'install', '--quiet', '--require-hashes', '-r', requirements])
    doc_env = os.environ.copy()
    doc_env['PATH'] = venv_bin + os.pathsep + doc_env.get('PATH', '')
    return doc_env

if __name__ == '__main__':
    args = docopt.docopt(__doc__)
    env = create_build_env()
    fmt_repo = env.fmt_repo

    branch = args.get('<branch>')
    if branch is None:
        branch = 'main'
    if not fmt_repo.update('-b', branch, 'git@github.com:fmtlib/fmt'):
        clean_checkout(fmt_repo, branch)

    # Update the date in the changelog and extract the version and the first
    # section content.
    changelog = 'ChangeLog.md'
    changelog_path = os.path.join(fmt_repo.dir, changelog)
    is_first_section = True
    first_section = []
    for i, line in enumerate(fileinput.input(changelog_path, inplace=True)):
        if i == 0:
            version = re.match(r'# (.*) - TBD', line).group(1)
            line = '# {} - {}\n'.format(
                version, datetime.date.today().isoformat())
        elif not is_first_section:
            pass
        elif line.startswith('#'):
            is_first_section = False
        else:
            first_section.append(line)
        sys.stdout.write(line)
    if first_section[0] == '\n':
        first_section.pop(0)

    ns_version = None
    base_h_path = os.path.join(fmt_repo.dir, 'include', 'fmt', 'base.h')
    for line in fileinput.input(base_h_path):
        m = re.match(r'\s*inline namespace v(.*) .*', line)
        if m:
            ns_version = m.group(1)
            break
    major_version = version.split('.')[0]
    if not ns_version or ns_version != major_version:
        raise Exception(f'Version mismatch {ns_version} != {major_version}')

    # Workaround GitHub-flavored Markdown treating newlines as <br>.
    changes = ''
    code_block = False
    stripped = False
    for line in first_section:
        if re.match(r'^\s*```', line):
            code_block = not code_block
            changes += line
            stripped = False
            continue
        if code_block:
            changes += line
            continue
        if line == '\n' or re.match(r'^\s*\|.*', line):
            if stripped:
                changes += '\n'
                stripped = False
            changes += line
            continue
        if stripped:
            line = ' ' + line.lstrip()
        changes += line.rstrip()
        stripped = True

    fmt_repo.checkout('-B', 'release')
    fmt_repo.add(changelog)
    fmt_repo.commit('-m', 'Update version')

    # Build the docs locally in a virtualenv with the pinned doc dependencies;
    # the source zip is now built and attached to the release in CI by
    # .github/workflows/release.yml, which also generates a SLSA provenance
    # attestation for it. The venv is prepended to PATH so that CMake's
    # find_program(MKDOCS mkdocs) and the ./mkdocs deploy step below both pick
    # up the correct mkdocs/mkdocstrings versions.
    doc_env = create_doc_env(env, fmt_repo)
    run = Runner(fmt_repo.dir, env=doc_env)
    run('cmake', '.')
    run('make', 'doc')

    # Create a draft release on GitHub, then trigger the release workflow to
    # build the source zip from the `release` branch and attach the zip plus
    # *.intoto.jsonl provenance to this draft. After reviewing the draft, the
    # maintainer clicks Publish to finalize.
    fmt_repo.push('origin', 'release')
    auth_headers = {'Authorization': 'token ' + os.getenv('FMT_TOKEN')}
    req = urllib.request.Request(
        'https://api.github.com/repos/fmtlib/fmt/releases',
        data=json.dumps({'tag_name': version,
                         'target_commitish': 'release',
                         'body': changes, 'draft': True}).encode('utf-8'),
        headers=auth_headers, method='POST')
    with urllib.request.urlopen(req) as response:
        if response.status != 201:
            raise Exception(f'Failed to create a release ' +
                            '{response.status} {response.reason}')

    # Draft releases do not fire `release: created`, so explicitly dispatch the
    # release workflow. It runs from the default branch (`branch`) but builds
    # and uploads artifacts from the `release` branch for tag `version`.
    dispatch_req = urllib.request.Request(
        'https://api.github.com/repos/fmtlib/fmt/actions/workflows/'
        'release.yml/dispatches',
        data=json.dumps({'ref': branch,
                         'inputs': {'tag_name': version,
                                    'ref': 'release'}}).encode('utf-8'),
        headers=auth_headers, method='POST')
    with urllib.request.urlopen(dispatch_req) as response:
        if response.status != 204:
            raise Exception('Failed to dispatch the release workflow ' +
                            f'{response.status} {response.reason}')

    short_version = '.'.join(version.split('.')[:-1])
    check_call(['./mkdocs', 'deploy', short_version], env=doc_env)


# Macros

Some aspects of the library can be configured by defining preprocessor macros **before** including the `json.hpp`
header. See also the [macro overview page](../../features/macros.md).

## Runtime assertions

- [**JSON_ASSERT(x)**](json_assert.md) - control behavior of runtime assertions

## Exceptions

- [**JSON_CATCH_USER(exception)**
**JSON_THROW_USER(exception)**
**JSON_TRY_USER**](json_throw_user.md) - control exceptions
- [**JSON_DIAGNOSTICS**](json_diagnostics.md) - control extended diagnostics
- [**JSON_DIAGNOSTIC_POSITIONS**](json_diagnostic_positions.md) - access positions of elements
- [**JSON_NOEXCEPTION**](json_noexception.md) - switch off exceptions

## Language support

- [**JSON_HAS_CPP_11**
**JSON_HAS_CPP_14**
**JSON_HAS_CPP_17**
**JSON_HAS_CPP_20**](json_has_cpp_11.md) - set supported C++ standard
- [**JSON_HAS_FILESYSTEM**
**JSON_HAS_EXPERIMENTAL_FILESYSTEM**](json_has_filesystem.md) - control `std::filesystem` support
- [**JSON_HAS_RANGES**](json_has_ranges.md) - control `std::ranges` support
- [**JSON_HAS_STD_FORMAT**](json_has_std_format.md) - control `std::format`/`std::formatter` support
- [**JSON_HAS_THREE_WAY_COMPARISON**](json_has_three_way_comparison.md) - control 3-way comparison support
- [**JSON_NO_IO**](json_no_io.md) - switch off functions relying on certain C++ I/O headers
- [**JSON_SKIP_UNSUPPORTED_COMPILER_CHECK**](json_skip_unsupported_compiler_check.md) - do not warn about unsupported compilers
- [**JSON_USE_GLOBAL_UDLS**](json_use_global_udls.md) - place user-defined string literals (UDLs) into the global namespace

## Library version

- [**JSON_SKIP_LIBRARY_VERSION_CHECK**](json_skip_library_version_check.md) - skip library version check
- [**NLOHMANN_JSON_VERSION_MAJOR**
**NLOHMANN_JSON_VERSION_MINOR**
**NLOHMANN_JSON_VERSION_PATCH**](nlohmann_json_version_major.md)
  \- library version information

## Library namespace

- [**NLOHMANN_JSON_NAMESPACE**](nlohmann_json_namespace.md) - full name of the `nlohmann` namespace
- [**NLOHMANN_JSON_NAMESPACE_BEGIN**
**NLOHMANN_JSON_NAMESPACE_END**](nlohmann_json_namespace_begin.md) - open and
  close the library namespace
- [**NLOHMANN_JSON_NAMESPACE_NO_VERSION**](nlohmann_json_namespace_no_version.md) - disable the version component of
  the inline namespace

## Type conversions

- [**JSON_BRACE_INIT_COPY_SEMANTICS**](json_brace_init_copy_semantics.md) - opt in to copy/move semantics for single-element brace initialization
- [**JSON_DISABLE_ENUM_SERIALIZATION**](json_disable_enum_serialization.md) - switch off default serialization/deserialization functions for enums
- [**JSON_USE_IMPLICIT_CONVERSIONS**](json_use_implicit_conversions.md) - control implicit conversions

## Comparison behavior

- [**JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON**](json_use_legacy_discarded_value_comparison.md) -
  control comparison of discarded values

## Serialization/deserialization macros

### Enums

- [**NLOHMANN_JSON_SERIALIZE_ENUM**](nlohmann_json_serialize_enum.md) - serialize/deserialize an enum
- [**NLOHMANN_JSON_SERIALIZE_ENUM_STRICT**](nlohmann_json_serialize_enum_strict.md) - serialize/deserialize an enum with exceptions

### Classes and structs

- [**NLOHMANN_DEFINE_TYPE_INTRUSIVE**](nlohmann_define_type_intrusive.md) - serialize/deserialize a non-derived class
  with private members
- [**NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT**](nlohmann_define_type_intrusive.md) - serialize/deserialize a
  non-derived class with private members; uses default values
- [**NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE**](nlohmann_define_type_intrusive.md) - serialize a non-derived class
  with private members
- [**NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE**](nlohmann_define_type_non_intrusive.md) - serialize/deserialize a non-derived
  class
- [**NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT**](nlohmann_define_type_non_intrusive.md) - serialize/deserialize a
  non-derived class; uses default values
- [**NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE**](nlohmann_define_type_non_intrusive.md) - serialize a
  non-derived class

- [**NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE**](nlohmann_define_derived_type.md) - serialize/deserialize a derived class
  with private members
- [**NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT**](nlohmann_define_derived_type.md) - serialize/deserialize a
  derived class with private members; uses default values
- [**NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE**](nlohmann_define_derived_type.md) - serialize a derived
  class with private members
- [**NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE**](nlohmann_define_derived_type.md) - serialize/deserialize a derived
  class
- [**NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT**](nlohmann_define_derived_type.md) - serialize/deserialize
  a derived class; uses default values
- [**NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE**](nlohmann_define_derived_type.md) - serialize a derived
  class
- [**NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_NAMES**](nlohmann_define_type_with_names.md) - serialize/deserialize a non-derived class
  with private members; uses custom names
- [**NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT_WITH_NAMES**](nlohmann_define_type_with_names.md) - serialize/deserialize a
  non-derived class with private members; uses default values; uses custom names
- [**NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE_WITH_NAMES**](nlohmann_define_type_with_names.md) - serialize a non-derived class
  with private members; uses custom names
- [**NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_NAMES**](nlohmann_define_type_with_names.md) - serialize/deserialize a non-derived
  class; uses custom names
- [**NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT_WITH_NAMES**](nlohmann_define_type_with_names.md) - serialize/deserialize a
  non-derived class; uses default values; uses custom names
- [**NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE_WITH_NAMES**](nlohmann_define_type_with_names.md) - serialize a
  non-derived class; uses custom names

- [**NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_NAMES**](nlohmann_define_type_with_names.md) - serialize/deserialize a derived class
  with private members; uses custom names
- [**NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT_WITH_NAMES**](nlohmann_define_type_with_names.md) - serialize/deserialize a
  derived class with private members; uses default values; uses custom names
- [**NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE_WITH_NAMES**](nlohmann_define_type_with_names.md) - serialize a derived
  class with private members; uses custom names
- [**NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_NAMES**](nlohmann_define_type_with_names.md) - serialize/deserialize a derived
  class; uses custom names
- [**NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT_WITH_NAMES**](nlohmann_define_type_with_names.md) - serialize/deserialize
  a derived class; uses default values; uses custom names
- [**NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE_WITH_NAMES**](nlohmann_define_type_with_names.md) - serialize a derived
  class; uses custom names


import type * as http from 'node:http'
import fs from 'node:fs'
import path from 'node:path'
import { chromium } from 'playwright-chromium'
import type {
  ConfigEnv,
  InlineConfig,
  Logger,
  PluginOption,
  ResolvedConfig,
  UserConfig,
  ViteDevServer,
} from 'vite'
import {
  build,
  createBuilder,
  createServer,
  loadConfigFromFile,
  mergeConfig,
  preview,
} from 'vite'
import type { Browser, Page } from 'playwright-chromium'
import type {
  RolldownWatcher,
  RolldownWatcherEvent,
  RollupError,
} from 'rolldown'
import { beforeAll, expect, inject, vi } from 'vitest'

// #region serializer

export const sourcemapSnapshot = Symbol()

const generateVisualizationLink = (code: string, map: string) => {
  const utf16ToUTF8 = (x) => unescape(encodeURIComponent(x))
  const convertedCode = utf16ToUTF8(code)
  const convertedMap = utf16ToUTF8(map)
  const hash = `${convertedCode.length}\0${convertedCode}${convertedMap.length}\0${convertedMap}`
  return `https://evanw.github.io/source-map-visualization/#${btoa(hash)}`
}

expect.addSnapshotSerializer({
  serialize(val, config, indentation, depth, refs, printer) {
    const options = val[sourcemapSnapshot]
    const map = { ...val.map }
    if (options.withoutContent) {
      delete map.sourcesContent
    }

    return `${indentation}SourceMap {
${indentation}${config.indent}content: ${printer(map, config, indentation + config.indent, depth, refs)},
${indentation}${config.indent}visualization: ${JSON.stringify(generateVisualizationLink(val.code, JSON.stringify(val.map)))}
${indentation}}`
  },
  test(val) {
    return typeof val === 'object' && val && val[sourcemapSnapshot]
  },
})

// #endregion

// #region env

export const workspaceRoot = path.resolve(import.meta.dirname, '../')

export const isBuild = !!process.env.VITE_TEST_BUILD
export const isServe = !isBuild
/**
 * Serve mode with `experimental.bundledDev` force-enabled for every playground
 * (`VITE_TEST_BUNDLED_DEV=1`). `isServe` stays `true` in this mode; use
 * `test.skipIf(isBundledDev)` / `describe.skipIf(isBundledDev)` for cases that
 * don't pass under bundled dev yet.
 */
export const isBundledDev = isServe && !!process.env.VITE_TEST_BUNDLED_DEV
export const isBundled = isBuild || isBundledDev
export const isWindows = process.platform === 'win32'
export const viteBinPath = path.posix.join(
  workspaceRoot,
  'packages/vite/bin/vite.js',
)

// #endregion

// #region context

let server: ViteDevServer | http.Server

/**
 * Vite Dev Server when testing serve
 */
export let viteServer: ViteDevServer
/**
 * Root of the Vite fixture
 */
export let rootDir: string
/**
 * Path to the current test file
 */
export let testPath: string
/**
 * Path to the test folder
 */
export let testDir: string
/**
 * Test folder name
 */
export let testName: string

export const serverLogs: string[] = []
export const browserLogs: string[] = []
export const browserErrors: Error[] = []

export let page: Page = undefined!
export let browser: Browser = undefined!
export let viteTestUrl: string = ''
export let watcher: RolldownWatcher | undefined = undefined

export function setViteUrl(url: string): void {
  viteTestUrl = url
}

function throwHtmlParseError() {
  return {
    name: 'vite-plugin-throw-html-parse-error',
    configResolved(config: ResolvedConfig) {
      const warn = config.logger.warn
      config.logger.warn = (msg, opts) => {
        // convert HTML parse warnings to make it easier to test
        if (msg.includes('Unable to parse HTML;')) {
          throw new Error(msg)
        }
        warn.call(config.logger, msg, opts)
      }
    },
  }
}
// #endregion

// eslint-disable-next-line no-empty-pattern
beforeAll(async ({}, suite) => {
  testPath = suite.file.filepath!
  testName = slash(testPath).match(/playground\/([\w-]+)\//)?.[1]
  testDir = path.dirname(testPath)
  if (testName) {
    testDir = path.resolve(workspaceRoot, 'playground-temp', testName)
  }

  // skip browser setup for hmr-ssr playground
  if (testName === 'hmr-ssr') {
    return
  }

  const wsEndpoint = inject('wsEndpoint')
  if (!wsEndpoint) {
    throw new Error('wsEndpoint not found')
  }

  browser = await chromium.connect(wsEndpoint)
  page = await browser.newPage()

  try {
    page.on('console', (msg) => {
      // ignore favicon request in headed browser
      if (
        process.env.VITE_DEBUG_SERVE &&
        msg.text().includes('Failed to load resource:') &&
        msg.location().url.includes('favicon.ico')
      ) {
        return
      }
      browserLogs.push(msg.text())
    })
    page.on('pageerror', (error) => {
      browserErrors.push(error)
    })

    // if this is a test placed under playground/xxx/__tests__
    // start a vite server in that directory.
    if (testName) {
      // when `root` dir is present, use it as vite's root
      const testCustomRoot = path.resolve(testDir, 'root')
      rootDir = fs.existsSync(testCustomRoot) ? testCustomRoot : testDir

      // separate rootDir for variant
      const variantName = path.basename(path.dirname(testPath))
      if (variantName !== '__tests__') {
        const variantTestDir = testDir + '__' + variantName
        if (fs.existsSync(variantTestDir)) {
          rootDir = testDir = variantTestDir
        }
      }

      const testCustomServe = [
        path.resolve(path.dirname(testPath), 'serve.ts'),
        path.resolve(path.dirname(testPath), 'serve.js'),
      ].find((i) => fs.existsSync(i))

      if (testCustomServe) {
        // test has custom server configuration.
        const mod = await import(testCustomServe)
        const serve = mod.serve || mod.default?.serve
        const preServe = mod.preServe || mod.default?.preServe
        if (preServe) {
          await preServe()
        }
        if (serve) {
          server = await serve()
          viteServer = mod.viteServer
        }
      } else {
        await startDefaultServe()
      }
    }
  } catch (e) {
    // Closing the page since an error in the setup, for example a runtime error
    // when building the playground should skip further tests.
    // If the page remains open, a command like `await page.click(...)` produces
    // a timeout with an exception that hides the real error in the console.
    await page.close()
    await server?.close()
    throw e
  }

  return async () => {
    serverLogs.length = 0
    await page?.close()
    await server?.close()
    await watcher?.close()
    if (browser) {
      await browser.close()
    }
  }
})

async function loadConfig(configEnv: ConfigEnv) {
  let config: UserConfig | null = null

  // config file named by convention as the *.spec.ts folder
  const variantName = path.basename(path.dirname(testPath))
  if (variantName !== '__tests__') {
    const configVariantPath = path.resolve(
      rootDir,
      `vite.config-${variantName}.js`,
    )
    if (fs.existsSync(configVariantPath)) {
      const res = await loadConfigFromFile(configEnv, configVariantPath)
      if (res) {
        config = res.config
      }
    }
  }
  // config file from test root dir
  if (!config) {
    const res = await loadConfigFromFile(configEnv, undefined, rootDir)
    if (res) {
      config = res.config
    }
  }

  const options: InlineConfig = {
    root: rootDir,
    logLevel: 'silent',
    configFile: false,
    server: {
      watch: {
        // During tests we edit the files too fast and sometimes chokidar
        // misses change events, so enforce polling for consistency
        usePolling: true,
        interval: 100,
      },
    },
    build: {
      // esbuild do not minify ES lib output since that would remove pure annotations and break tree-shaking
      // skip transpilation during tests to make it faster
      target: 'esnext',
    },
    customLogger: createInMemoryLogger(serverLogs),
    plugins: [throwHtmlParseError()],
  }
  let merged = mergeConfig(options, config || {})
  // applied after the merge so the playground's own config cannot turn it off —
  // the whole point of the bundled-dev run is to force the mode everywhere
  if (isBundledDev) {
    merged = mergeConfig(merged, { experimental: { bundledDev: true } })
  }
  return merged
}

export async function startDefaultServe(): Promise<void> {
  setupConsoleWarnCollector(serverLogs)

  if (!isBuild) {
    process.env.VITE_INLINE = 'inline-serve'
    const config = await loadConfig({ command: 'serve', mode: 'development' })
    viteServer = server = await (await createServer(config)).listen()
    viteTestUrl = stripTrailingSlashIfNeeded(
      server.resolvedUrls.local[0],
      server.config.base,
    )
    await page.goto(viteTestUrl)
    // bundled dev serves a self-reloading fallback page until the first
    // bundle completes; tests must not assert against that placeholder.
    // Wait server-side for the first build to settle (success or error) so
    // slow builds (e.g. many HTML inputs) don't race a fixed page timeout.
    // A playground whose first bundle fails keeps the fallback page, and its
    // tests are expected to handle that state themselves.
    // hmr-full-bundle-mode is exempt — it asserts the fallback page itself.
    if (isBundledDev && testName !== 'hmr-full-bundle-mode') {
      // `initialBuildCompleted` / `lastBuildError` are private — the harness
      // reaches in rather than widening the public API for tests only.
      const bundledDev = server.environments.client.bundledDev as any
      if (bundledDev) {
        await vi.waitUntil(
          () => bundledDev.initialBuildCompleted || bundledDev.lastBuildError,
          { timeout: 40_000 },
        )
      }
      if (bundledDev?.initialBuildCompleted) {
        await page
          .waitForFunction(
            () => !(globalThis as any).__vite_is_fallback_page__,
            undefined,
            { timeout: 15_000 },
          )
          .catch(() => {})
        // TODO: workaround — an edit fired while no client is connected is
        // dropped (vitejs/vite#23028). Remove this wait once the server
        // buffers updates for clients that connect later. A page that never
        // loads the client runtime (e.g. SSR-rendered pages) cannot register
        // a client; a fully loaded page with no runtime counts as settled.
        await vi.waitUntil(
          async () => {
            const state = await page
              .evaluate(() => ({
                loaded: document.readyState === 'complete',
                hasRuntime: !!(globalThis as any).__rolldown_runtime__,
                clientId: (globalThis as any).__rolldown_runtime__?.clientId,
              }))
              .catch(() => undefined)
            if (!state) return false
            if (state.clientId && bundledDev.clients?.get(state.clientId)) {
              return true
            }
            return state.loaded && !state.hasRuntime
          },
          { timeout: 10_000 },
        )
      }
    }
  } else {
    process.env.VITE_INLINE = 'inline-build'
    let resolvedConfig: ResolvedConfig
    // determine build watch
    const resolvedPlugin: () => PluginOption = () => ({
      name: 'vite-plugin-watcher',
      configResolved(config) {
        resolvedConfig = config
      },
    })
    const buildConfig = mergeConfig(
      await loadConfig({ command: 'build', mode: 'production' }),
      {
        plugins: [resolvedPlugin()],
      },
    )
    if (buildConfig.builder) {
      const builder = await createBuilder(buildConfig)
      await builder.buildApp()
    } else {
      const rollupOutput = await build(buildConfig)
      const isWatch = !!resolvedConfig!.build.watch
      // in build watch,call startStaticServer after the build is complete
      if (isWatch) {
        watcher = rollupOutput as RolldownWatcher
        await notifyRebuildComplete(watcher)
      }
      if (buildConfig.__test__) {
        buildConfig.__test__()
      }
    }

    const previewConfig = await loadConfig({
      command: 'serve',
      mode: 'development',
      isPreview: true,
    })
    const _nodeEnv = process.env.NODE_ENV
    const previewServer = await preview(previewConfig)
    // prevent preview change NODE_ENV
    process.env.NODE_ENV = _nodeEnv
    viteTestUrl = stripTrailingSlashIfNeeded(
      previewServer.resolvedUrls.local[0],
      previewServer.config.base,
    )
    await page.goto(viteTestUrl)
  }
}

/**
 * Send the rebuild complete message in build watch
 */
export async function notifyRebuildComplete(
  watcher: RolldownWatcher,
): Promise<void> {
  let resolveFn: undefined | (() => void)
  const callback = (event: RolldownWatcherEvent): void => {
    if (event.code === 'END') {
      resolveFn?.()
    }
  }
  watcher.on('event', callback)
  await new Promise<void>((resolve) => {
    resolveFn = resolve
  })

  watcher.off('event', callback)
}

export function createInMemoryLogger(logs: string[]): Logger {
  const loggedErrors = new WeakSet<Error | RollupError>()
  const warnedMessages = new Set<string>()

  const logger: Logger = {
    hasWarned: false,
    hasErrorLogged: (err) => loggedErrors.has(err),
    clearScreen: () => {},
    info(msg) {
      logs.push(msg)
    },
    warn(msg) {
      logs.push(msg)
      logger.hasWarned = true
    },
    warnOnce(msg) {
      if (warnedMessages.has(msg)) return
      logs.push(msg)
      logger.hasWarned = true
      warnedMessages.add(msg)
    },
    error(msg, opts) {
      logs.push(msg)
      if (opts?.error) {
        loggedErrors.add(opts.error)
      }
    },
  }

  return logger
}

function setupConsoleWarnCollector(logs: string[]) {
  const warn = console.warn
  console.warn = (...args) => {
    logs.push(args.join(' '))
    return warn.call(console, ...args)
  }
}

export function slash(p: string): string {
  return p.replace(/\\/g, '/')
}

function stripTrailingSlashIfNeeded(url: string, base: string): string {
  if (base === '/') {
    return url.replace(/\/$/, '')
  }
  return url
}

declare module 'vite' {
  export interface UserConfig {
    /**
     * special test only hook
     *
     * runs after build and before preview
     */
    __test__?: () => void
  }
}

declare module 'vitest' {
  export interface ProvidedContext {
    wsEndpoint: string
  }
}


use std::{ffi::OsString, io};

/// Returns the hostname of the current system.
///
/// It is unusual, although technically possible, for this routine to return
/// an error. It is difficult to list out the error conditions, but one such
/// possibility is platform support.
///
/// # Platform specific behavior
///
/// On Windows, this currently uses the "physical DNS hostname" computer name.
/// This may change in the future.
///
/// On Unix, this returns the result of the `gethostname` function from the
/// `libc` linked into the program.
pub fn hostname() -> io::Result<OsString> {
    #[cfg(windows)]
    {
        use winapi_util::sysinfo::{ComputerNameKind, get_computer_name};
        get_computer_name(ComputerNameKind::PhysicalDnsHostname)
    }
    #[cfg(unix)]
    {
        gethostname()
    }
    #[cfg(not(any(windows, unix)))]
    {
        Err(io::Error::new(
            io::ErrorKind::Other,
            "hostname could not be found on unsupported platform",
        ))
    }
}

#[cfg(unix)]
fn gethostname() -> io::Result<OsString> {
    use std::os::unix::ffi::OsStringExt;

    // SAFETY: There don't appear to be any safety requirements for calling
    // sysconf.
    let limit = unsafe { libc::sysconf(libc::_SC_HOST_NAME_MAX) };
    if limit == -1 {
        // It is in theory possible for sysconf to return -1 for a limit but
        // *not* set errno, in which case, io::Error::last_os_error is
        // indeterminate. But untangling that is super annoying because std
        // doesn't expose any unix-specific APIs for inspecting the errno. (We
        // could do it ourselves, but it just doesn't seem worth doing?)
        return Err(io::Error::last_os_error());
    }
    let Ok(maxlen) = usize::try_from(limit) else {
        let msg = format!("host name max limit ({}) overflowed usize", limit);
        return Err(io::Error::new(io::ErrorKind::Other, msg));
    };
    // maxlen here includes the NUL terminator.
    let mut buf = vec![0; maxlen];
    // SAFETY: The pointer we give is valid as it is derived directly from a
    // Vec. Similarly, `maxlen` is the length of our Vec, and is thus valid
    // to write to.
    let rc = unsafe {
        libc::gethostname(buf.as_mut_ptr().cast::<libc::c_char>(), maxlen)
    };
    if rc == -1 {
        return Err(io::Error::last_os_error());
    }
    // POSIX says that if the hostname is bigger than `maxlen`, then it may
    // write a truncate name back that is not necessarily NUL terminated (wtf,
    // lol). So if we can't find a NUL terminator, then just give up.
    let Some(zeropos) = buf.iter().position(|&b| b == 0) else {
        let msg = "could not find NUL terminator in hostname";
        return Err(io::Error::new(io::ErrorKind::Other, msg));
    };
    buf.truncate(zeropos);
    buf.shrink_to_fit();
    Ok(OsString::from_vec(buf))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn print_hostname() {
        println!("{:?}", hostname().unwrap());
    }
}


/* eslint-disable n/prefer-node-protocol */
import assert from 'assert'
import { defineConfig } from 'vite'

let count = 0
export default defineConfig({
  plugins: [
    {
      name: 'plugin1',
      buildStart: {
        async handler() {
          await new Promise((r) => setTimeout(r, 100))
          count += 1
        },
      },
      transform: {
        order: 'post',
        handler(code) {
          return code.replace('__TRANSFORM3__', 'ok')
        },
      },
    },
    {
      name: 'plugin2',
      buildStart: {
        sequential: true,
        async handler() {
          assert(count === 1)
          await new Promise((r) => setTimeout(r, 100))
          count += 1
        },
      },
      transform: {
        handler(code) {
          return code.replace('__TRANSFORM1__', '__TRANSFORM2__')
        },
      },
    },
    {
      name: 'plugin3',
      buildStart: {
        async handler() {
          assert(count === 2)
          await new Promise((r) => setTimeout(r, 100))
          count += 1
        },
      },
      transform: {
        order: 'pre',
        handler(code) {
          return code.replace('__TRANSFORM__', '__TRANSFORM1__')
        },
      },
    },
    {
      name: 'plugin4',
      buildStart: {
        async handler() {
          assert(count === 2)
        },
      },
      transform: {
        handler(code) {
          return code.replace('__TRANSFORM2__', '__TRANSFORM3__')
        },
      },
    },
  ],
})


#include <iostream>
#include <nlohmann/json.hpp>

using json = nlohmann::json;

int main()
{
    // create JSON values
    json j_null;
    json j_boolean = true;
    json j_number_integer = 17;
    json j_number_float = 23.42;
    json j_object = {{"one", 1}, {"two", 2}};
    json j_array = {1, 2, 4, 8, 16};
    json j_string = "Hello, world";

    // call clear()
    j_null.clear();
    j_boolean.clear();
    j_number_integer.clear();
    j_number_float.clear();
    j_object.clear();
    j_array.clear();
    j_string.clear();

    // serialize the cleared values()
    std::cout << j_null << '\n';
    std::cout << j_boolean << '\n';
    std::cout << j_number_integer << '\n';
    std::cout << j_number_float << '\n';
    std::cout << j_object << '\n';
    std::cout << j_array << '\n';
    std::cout << j_string << '\n';
}