Add experimental `uv sync --output-format json` (#13689)

This is a continuation of the work in 

* #12405 

I have:
* moved to an architecture where the human output is derived from the
json structs to centralize more of the printing state/logic
* cleaned up some of the names/types
* added tests
* removed the restriction that this output is --dry-run only

I have not yet added package info, which was TBD in their design.

---------

Co-authored-by: x0rw <mahdi.svt5@gmail.com>
Co-authored-by: Zanie Blue <contact@zanie.dev>
Co-authored-by: John Mumm <jtfmumm@gmail.com>
This commit is contained in:
Aria Desires 2025-07-14 10:53:39 -04:00 committed by GitHub
parent df44199ceb
commit 34fbc06ad6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 1389 additions and 604 deletions

View File

@ -46,6 +46,15 @@ pub enum PythonListFormat {
Json,
}
#[derive(Debug, Default, Clone, Copy, clap::ValueEnum)]
pub enum SyncFormat {
/// Display the result in a human-readable format.
#[default]
Text,
/// Display the result in JSON format.
Json,
}
#[derive(Debug, Default, Clone, clap::ValueEnum)]
pub enum ListFormat {
/// Display the list of packages in a human-readable table.
@ -3207,6 +3216,10 @@ pub struct SyncArgs {
#[arg(long, conflicts_with = "all_extras", value_parser = extra_name_with_clap_error)]
pub extra: Option<Vec<ExtraName>>,
/// Select the output format.
#[arg(long, value_enum, default_value_t = SyncFormat::default())]
pub output_format: SyncFormat,
/// Include all optional dependencies.
///
/// When two or more extras are declared as conflicting in `tool.uv.conflicts`, using this flag

View File

@ -398,6 +398,12 @@ impl From<Box<Path>> for PortablePathBuf {
}
}
impl<'a> From<&'a Path> for PortablePathBuf {
fn from(path: &'a Path) -> Self {
Box::<Path>::from(path).into()
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for PortablePathBuf {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>

View File

@ -1408,6 +1408,14 @@ impl ProjectEnvironment {
Self::WouldCreate(..) => Err(ProjectError::DroppedEnvironment),
}
}
/// Return the path to the actual target, if this was a dry run environment.
pub(crate) fn dry_run_target(&self) -> Option<&Path> {
match self {
Self::WouldReplace(path, _, _) | Self::WouldCreate(path, _, _) => Some(path),
Self::Created(_) | Self::Existing(_) | Self::Replaced(_) => None,
}
}
}
impl std::ops::Deref for ProjectEnvironment {
@ -1588,6 +1596,14 @@ impl ScriptEnvironment {
Self::WouldCreate(..) => Err(ProjectError::DroppedEnvironment),
}
}
/// Return the path to the actual target, if this was a dry run environment.
pub(crate) fn dry_run_target(&self) -> Option<&Path> {
match self {
Self::WouldReplace(path, _, _) | Self::WouldCreate(path, _, _) => Some(path),
Self::Created(_) | Self::Existing(_) | Self::Replaced(_) => None,
}
}
}
impl std::ops::Deref for ScriptEnvironment {

View File

@ -6,9 +6,10 @@ use std::sync::Arc;
use anyhow::{Context, Result};
use itertools::Itertools;
use owo_colors::OwoColorize;
use serde::Serialize;
use tracing::warn;
use uv_cache::Cache;
use uv_cli::SyncFormat;
use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder};
use uv_configuration::{
Concurrency, Constraints, DependencyGroups, DependencyGroupsWithDefaults, DryRun, EditableMode,
@ -19,7 +20,7 @@ use uv_dispatch::BuildDispatch;
use uv_distribution_types::{
DirectorySourceDist, Dist, Index, Requirement, Resolution, ResolvedDist, SourceDist,
};
use uv_fs::Simplified;
use uv_fs::{PortablePathBuf, Simplified};
use uv_installer::SitePackages;
use uv_normalize::{DefaultExtras, DefaultGroups, PackageName};
use uv_pep508::{MarkerTree, VersionOrUrl};
@ -77,7 +78,14 @@ pub(crate) async fn sync(
cache: &Cache,
printer: Printer,
preview: PreviewMode,
output_format: SyncFormat,
) -> Result<ExitStatus> {
if preview.is_enabled() && matches!(output_format, SyncFormat::Json) {
warn_user!(
"The `--output-format json` option is experimental and the schema may change without warning. Pass `--preview` to disable this warning."
);
}
// Identify the target.
let workspace_cache = WorkspaceCache::default();
let target = if let Some(script) = script {
@ -180,103 +188,16 @@ pub(crate) async fn sync(
})
.ok();
// Notify the user of any environment changes.
match &environment {
SyncEnvironment::Project(ProjectEnvironment::Existing(environment))
if dry_run.enabled() =>
{
writeln!(
printer.stderr(),
"{}",
format!(
"Discovered existing environment at: {}",
environment.root().user_display().bold()
)
.dimmed()
)?;
}
SyncEnvironment::Project(ProjectEnvironment::WouldReplace(root, ..))
if dry_run.enabled() =>
{
writeln!(
printer.stderr(),
"{}",
format!(
"Would replace existing virtual environment at: {}",
root.user_display().bold()
)
.dimmed()
)?;
}
SyncEnvironment::Project(ProjectEnvironment::WouldCreate(root, ..))
if dry_run.enabled() =>
{
writeln!(
printer.stderr(),
"{}",
format!(
"Would create virtual environment at: {}",
root.user_display().bold()
)
.dimmed()
)?;
}
SyncEnvironment::Script(ScriptEnvironment::Existing(environment)) => {
if dry_run.enabled() {
writeln!(
printer.stderr(),
"{}",
format!(
"Discovered existing environment at: {}",
environment.root().user_display().bold()
)
.dimmed()
)?;
} else {
writeln!(
printer.stderr(),
"Using script environment at: {}",
environment.root().user_display().cyan()
)?;
}
}
SyncEnvironment::Script(ScriptEnvironment::Replaced(environment)) if !dry_run.enabled() => {
writeln!(
printer.stderr(),
"Recreating script environment at: {}",
environment.root().user_display().cyan()
)?;
}
SyncEnvironment::Script(ScriptEnvironment::Created(environment)) if !dry_run.enabled() => {
writeln!(
printer.stderr(),
"Creating script environment at: {}",
environment.root().user_display().cyan()
)?;
}
SyncEnvironment::Script(ScriptEnvironment::WouldReplace(root, ..)) if dry_run.enabled() => {
writeln!(
printer.stderr(),
"{}",
format!(
"Would replace existing script environment at: {}",
root.user_display().bold()
)
.dimmed()
)?;
}
SyncEnvironment::Script(ScriptEnvironment::WouldCreate(root, ..)) if dry_run.enabled() => {
writeln!(
printer.stderr(),
"{}",
format!(
"Would create script environment at: {}",
root.user_display().bold()
)
.dimmed()
)?;
}
_ => {}
let sync_report = SyncReport {
dry_run: dry_run.enabled(),
environment: EnvironmentReport::from(&environment),
action: SyncAction::from(&environment),
target: TargetName::from(&target),
};
// Show the intermediate results if relevant
if let Some(message) = sync_report.format(output_format) {
writeln!(printer.stderr(), "{message}")?;
}
// Special-case: we're syncing a script that doesn't have an associated lockfile. In that case,
@ -340,7 +261,23 @@ pub(crate) async fn sync(
)
.await
{
Ok(..) => return Ok(ExitStatus::Success),
Ok(..) => {
// Generate a report for the script without a lockfile
let report = Report {
schema: SchemaReport::default(),
target: TargetName::from(&target),
project: None,
script: Some(ScriptReport::from(script)),
sync: sync_report,
lock: None,
dry_run: dry_run.enabled(),
};
if let Some(output) = report.format(output_format) {
writeln!(printer.stdout(), "{output}")?;
}
return Ok(ExitStatus::Success);
}
// TODO(zanieb): We should respect `--output-format json` for the error case
Err(ProjectError::Operation(err)) => {
return diagnostics::OperationDiagnostic::native_tls(
network_settings.native_tls,
@ -387,46 +324,7 @@ pub(crate) async fn sync(
.execute(lock_target)
.await
{
Ok(result) => {
if dry_run.enabled() {
match result {
LockResult::Unchanged(..) => {
writeln!(
printer.stderr(),
"{}",
format!(
"Found up-to-date lockfile at: {}",
lock_target.lock_path().user_display().bold()
)
.dimmed()
)?;
}
LockResult::Changed(None, ..) => {
writeln!(
printer.stderr(),
"{}",
format!(
"Would create lockfile at: {}",
lock_target.lock_path().user_display().bold()
)
.dimmed()
)?;
}
LockResult::Changed(Some(..), ..) => {
writeln!(
printer.stderr(),
"{}",
format!(
"Would update lockfile at: {}",
lock_target.lock_path().user_display().bold()
)
.dimmed()
)?;
}
}
}
Outcome::Success(result.into_lock())
}
Ok(result) => Outcome::Success(result),
Err(ProjectError::Operation(err)) => {
return diagnostics::OperationDiagnostic::native_tls(network_settings.native_tls)
.report(err)
@ -440,6 +338,25 @@ pub(crate) async fn sync(
Err(err) => return Err(err.into()),
};
let lock_report = LockReport::from((&lock_target, &mode, &outcome));
if let Some(message) = lock_report.format(output_format) {
writeln!(printer.stderr(), "{message}")?;
}
let report = Report {
schema: SchemaReport::default(),
target: TargetName::from(&target),
project: target.project().map(ProjectReport::from),
script: target.script().map(ScriptReport::from),
sync: sync_report,
lock: Some(lock_report),
dry_run: dry_run.enabled(),
};
if let Some(output) = report.format(output_format) {
writeln!(printer.stdout(), "{output}")?;
}
// Identify the installation target.
let sync_target =
identify_installation_target(&target, outcome.lock(), all_packages, package.as_ref());
@ -490,7 +407,7 @@ pub(crate) async fn sync(
#[allow(clippy::large_enum_variant)]
enum Outcome {
/// The `lock` operation was successful.
Success(Lock),
Success(LockResult),
/// The `lock` operation successfully resolved, but failed due to a mismatch (e.g., with `--locked`).
LockMismatch(Box<Lock>),
}
@ -499,7 +416,7 @@ impl Outcome {
/// Return the [`Lock`] associated with this outcome.
fn lock(&self) -> &Lock {
match self {
Self::Success(lock) => lock,
Self::Success(lock) => lock.lock(),
Self::LockMismatch(lock) => lock,
}
}
@ -563,6 +480,22 @@ enum SyncTarget {
Script(Pep723Script),
}
impl SyncTarget {
fn project(&self) -> Option<&VirtualProject> {
match self {
Self::Project(project) => Some(project),
Self::Script(_) => None,
}
}
fn script(&self) -> Option<&Pep723Script> {
match self {
Self::Project(_) => None,
Self::Script(script) => Some(script),
}
}
}
#[derive(Debug)]
enum SyncEnvironment {
/// A Python environment for a project.
@ -571,6 +504,15 @@ enum SyncEnvironment {
Script(ScriptEnvironment),
}
impl SyncEnvironment {
fn dry_run_target(&self) -> Option<&Path> {
match self {
Self::Project(env) => env.dry_run_target(),
Self::Script(env) => env.dry_run_target(),
}
}
}
impl Deref for SyncEnvironment {
type Target = PythonEnvironment;
@ -892,3 +834,392 @@ fn store_credentials_from_target(target: InstallTarget<'_>) {
}
}
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "snake_case")]
struct WorkspaceReport {
/// The workspace directory path.
path: PortablePathBuf,
}
impl From<&Workspace> for WorkspaceReport {
fn from(workspace: &Workspace) -> Self {
Self {
path: workspace.install_path().as_path().into(),
}
}
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "snake_case")]
struct ProjectReport {
//
path: PortablePathBuf,
workspace: WorkspaceReport,
}
impl From<&VirtualProject> for ProjectReport {
fn from(project: &VirtualProject) -> Self {
Self {
path: project.root().into(),
workspace: WorkspaceReport::from(project.workspace()),
}
}
}
impl From<&SyncTarget> for TargetName {
fn from(target: &SyncTarget) -> Self {
match target {
SyncTarget::Project(_) => TargetName::Project,
SyncTarget::Script(_) => TargetName::Script,
}
}
}
#[derive(Serialize, Debug)]
struct ScriptReport {
/// The path to the script.
path: PortablePathBuf,
}
impl From<&Pep723Script> for ScriptReport {
fn from(script: &Pep723Script) -> Self {
Self {
path: script.path.as_path().into(),
}
}
}
#[derive(Serialize, Debug, Default)]
#[serde(rename_all = "snake_case")]
enum SchemaVersion {
/// An unstable, experimental schema.
#[default]
Preview,
}
#[derive(Serialize, Debug, Default)]
struct SchemaReport {
/// The version of the schema.
version: SchemaVersion,
}
/// A report of the uv sync operation
#[derive(Debug, Serialize)]
#[serde(rename_all = "snake_case")]
struct Report {
/// The schema of this report.
schema: SchemaReport,
/// The target of the sync operation, either a project or a script.
target: TargetName,
/// The report for a [`TargetName::Project`], if applicable.
#[serde(skip_serializing_if = "Option::is_none")]
project: Option<ProjectReport>,
/// The report for a [`TargetName::Script`], if applicable.
#[serde(skip_serializing_if = "Option::is_none")]
script: Option<ScriptReport>,
/// The report for the sync operation.
sync: SyncReport,
/// The report for the lock operation.
lock: Option<LockReport>,
/// Whether this is a dry run.
dry_run: bool,
}
/// The kind of target
#[derive(Debug, Serialize, Clone, Copy)]
#[serde(rename_all = "snake_case")]
enum TargetName {
Project,
Script,
}
impl std::fmt::Display for TargetName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TargetName::Project => write!(f, "project"),
TargetName::Script => write!(f, "script"),
}
}
}
/// Represents the action taken during a sync.
#[derive(Serialize, Debug)]
#[serde(rename_all = "snake_case")]
enum SyncAction {
/// The environment was checked and required no updates.
Check,
/// The environment was updated.
Update,
/// The environment was replaced.
Replace,
/// A new environment was created.
Create,
}
impl From<&SyncEnvironment> for SyncAction {
fn from(env: &SyncEnvironment) -> Self {
match &env {
SyncEnvironment::Project(ProjectEnvironment::Existing(..)) => SyncAction::Check,
SyncEnvironment::Project(ProjectEnvironment::Created(..)) => SyncAction::Create,
SyncEnvironment::Project(ProjectEnvironment::WouldCreate(..)) => SyncAction::Create,
SyncEnvironment::Project(ProjectEnvironment::WouldReplace(..)) => SyncAction::Replace,
SyncEnvironment::Project(ProjectEnvironment::Replaced(..)) => SyncAction::Update,
SyncEnvironment::Script(ScriptEnvironment::Existing(..)) => SyncAction::Check,
SyncEnvironment::Script(ScriptEnvironment::Created(..)) => SyncAction::Create,
SyncEnvironment::Script(ScriptEnvironment::WouldCreate(..)) => SyncAction::Create,
SyncEnvironment::Script(ScriptEnvironment::WouldReplace(..)) => SyncAction::Replace,
SyncEnvironment::Script(ScriptEnvironment::Replaced(..)) => SyncAction::Update,
}
}
}
impl SyncAction {
fn message(&self, target: TargetName, dry_run: bool) -> Option<&'static str> {
let message = if dry_run {
match self {
SyncAction::Check => "Would use",
SyncAction::Update => "Would update",
SyncAction::Replace => "Would replace",
SyncAction::Create => "Would create",
}
} else {
// For projects, we omit some of these messages when we're not in dry-run mode
let is_project = matches!(target, TargetName::Project);
match self {
SyncAction::Check | SyncAction::Update | SyncAction::Create if is_project => {
return None;
}
SyncAction::Check => "Using",
SyncAction::Update => "Updating",
SyncAction::Replace => "Replacing",
SyncAction::Create => "Creating",
}
};
Some(message)
}
}
/// Represents the action taken during a lock.
#[derive(Serialize, Debug)]
#[serde(rename_all = "snake_case")]
enum LockAction {
/// The lockfile was used without checking.
Use,
/// The lockfile was checked and required no updates.
Check,
/// The lockfile was updated.
Update,
/// A new lockfile was created.
Create,
}
impl LockAction {
fn message(&self, dry_run: bool) -> Option<&'static str> {
let message = if dry_run {
match self {
LockAction::Use => return None,
LockAction::Check => "Found up-to-date",
LockAction::Update => "Would update",
LockAction::Create => "Would create",
}
} else {
return None;
};
Some(message)
}
}
#[derive(Serialize, Debug)]
struct PythonReport {
path: PortablePathBuf,
version: uv_pep508::StringVersion,
implementation: String,
}
impl From<&uv_python::Interpreter> for PythonReport {
fn from(interpreter: &uv_python::Interpreter) -> Self {
Self {
path: interpreter.sys_executable().into(),
version: interpreter.python_full_version().clone(),
implementation: interpreter.implementation_name().to_string(),
}
}
}
impl PythonReport {
/// Set the path for this Python report.
#[must_use]
fn with_path(mut self, path: PortablePathBuf) -> Self {
self.path = path;
self
}
}
#[derive(Serialize, Debug)]
struct EnvironmentReport {
/// The path to the environment.
path: PortablePathBuf,
/// The Python interpreter for the environment.
python: PythonReport,
}
impl From<&PythonEnvironment> for EnvironmentReport {
fn from(env: &PythonEnvironment) -> Self {
Self {
python: PythonReport::from(env.interpreter()),
path: env.root().into(),
}
}
}
impl From<&SyncEnvironment> for EnvironmentReport {
fn from(env: &SyncEnvironment) -> Self {
let report = EnvironmentReport::from(&**env);
// Replace the path if necessary; we construct a temporary virtual environment during dry
// run invocations and want to report the path we _would_ use.
if let Some(path) = env.dry_run_target() {
report.with_path(path.into())
} else {
report
}
}
}
impl EnvironmentReport {
/// Set the path for this environment report.
#[must_use]
fn with_path(mut self, path: PortablePathBuf) -> Self {
let python_path = &self.python.path;
if let Ok(python_path) = python_path.as_ref().strip_prefix(self.path) {
let new_path = path.as_ref().to_path_buf().join(python_path);
self.python = self.python.with_path(new_path.as_path().into());
}
self.path = path;
self
}
}
/// The report for a sync operation.
#[derive(Serialize, Debug)]
struct SyncReport {
/// The environment.
environment: EnvironmentReport,
/// The action performed during the sync, e.g., what was done to the environment.
action: SyncAction,
// We store these fields so the report can format itself self-contained, but the outer
// [`Report`] is intended to include these in user-facing output
#[serde(skip)]
dry_run: bool,
#[serde(skip)]
target: TargetName,
}
impl SyncReport {
fn format(&self, output_format: SyncFormat) -> Option<String> {
match output_format {
// This is an intermediate report, when using JSON, it's only rendered at the end
SyncFormat::Json => None,
SyncFormat::Text => self.to_human_readable_string(),
}
}
fn to_human_readable_string(&self) -> Option<String> {
let Self {
environment,
action,
dry_run,
target,
} = self;
let action = action.message(*target, *dry_run)?;
let message = format!(
"{action} {target} environment at: {path}",
path = environment.path.user_display().cyan(),
);
if *dry_run {
return Some(message.dimmed().to_string());
}
Some(message)
}
}
/// The report for a lock operation.
#[derive(Debug, Serialize)]
struct LockReport {
/// The path to the lockfile
path: PortablePathBuf,
/// Whether the lockfile was preserved, created, or updated.
action: LockAction,
// We store this field so the report can format itself self-contained, but the outer
// [`Report`] is intended to include this in user-facing output
#[serde(skip)]
dry_run: bool,
}
impl From<(&LockTarget<'_>, &LockMode<'_>, &Outcome)> for LockReport {
fn from((target, mode, outcome): (&LockTarget, &LockMode, &Outcome)) -> Self {
Self {
path: target.lock_path().deref().into(),
action: match outcome {
Outcome::Success(result) => {
match result {
LockResult::Unchanged(..) => match mode {
// When `--frozen` is used, we don't check the lockfile
LockMode::Frozen => LockAction::Use,
LockMode::DryRun(_) | LockMode::Locked(_) | LockMode::Write(_) => {
LockAction::Check
}
},
LockResult::Changed(None, ..) => LockAction::Create,
LockResult::Changed(Some(_), ..) => LockAction::Update,
}
}
// TODO(zanieb): We don't have a way to report the outcome of the lock yet
Outcome::LockMismatch(_) => LockAction::Check,
},
dry_run: matches!(mode, LockMode::DryRun(_)),
}
}
}
impl LockReport {
fn format(&self, output_format: SyncFormat) -> Option<String> {
match output_format {
SyncFormat::Json => None,
SyncFormat::Text => self.to_human_readable_string(),
}
}
fn to_human_readable_string(&self) -> Option<String> {
let Self {
path,
action,
dry_run,
} = self;
let action = action.message(*dry_run)?;
let message = format!(
"{action} lockfile at: {path}",
path = path.user_display().cyan(),
);
if *dry_run {
return Some(message.dimmed().to_string());
}
Some(message)
}
}
impl Report {
fn format(&self, output_format: SyncFormat) -> Option<String> {
match output_format {
SyncFormat::Json => serde_json::to_string_pretty(self).ok(),
SyncFormat::Text => None,
}
}
}

View File

@ -1818,6 +1818,7 @@ async fn run_project(
&cache,
printer,
globals.preview,
args.output_format,
))
.await
}

View File

@ -11,8 +11,8 @@ use uv_cli::{
PipCheckArgs, PipCompileArgs, PipFreezeArgs, PipInstallArgs, PipListArgs, PipShowArgs,
PipSyncArgs, PipTreeArgs, PipUninstallArgs, PythonFindArgs, PythonInstallArgs, PythonListArgs,
PythonListFormat, PythonPinArgs, PythonUninstallArgs, PythonUpgradeArgs, RemoveArgs, RunArgs,
SyncArgs, ToolDirArgs, ToolInstallArgs, ToolListArgs, ToolRunArgs, ToolUninstallArgs, TreeArgs,
VenvArgs, VersionArgs, VersionBump, VersionFormat,
SyncArgs, SyncFormat, ToolDirArgs, ToolInstallArgs, ToolListArgs, ToolRunArgs,
ToolUninstallArgs, TreeArgs, VenvArgs, VersionArgs, VersionBump, VersionFormat,
};
use uv_cli::{
AuthorFrom, BuildArgs, ExportArgs, PublishArgs, PythonDirArgs, ResolverInstallerArgs,
@ -1154,6 +1154,7 @@ pub(crate) struct SyncSettings {
pub(crate) install_mirrors: PythonInstallMirrors,
pub(crate) refresh: Refresh,
pub(crate) settings: ResolverInstallerSettings,
pub(crate) output_format: SyncFormat,
}
impl SyncSettings {
@ -1194,6 +1195,7 @@ impl SyncSettings {
python_platform,
check,
no_check,
output_format,
} = args;
let install_mirrors = filesystem
.clone()
@ -1213,6 +1215,7 @@ impl SyncSettings {
};
Self {
output_format,
locked,
frozen,
dry_run,

View File

@ -210,7 +210,7 @@ impl TestContext {
pub fn with_filtered_python_names(mut self) -> Self {
if cfg!(windows) {
self.filters
.push(("python.exe".to_string(), "python".to_string()));
.push((r"python\.exe".to_string(), "python".to_string()));
} else {
self.filters
.push((r"python\d.\d\d".to_string(), "python".to_string()));

File diff suppressed because it is too large Load Diff

View File

@ -1114,7 +1114,12 @@ uv sync [OPTIONS]
</dd><dt id="uv-sync--only-group"><a href="#uv-sync--only-group"><code>--only-group</code></a> <i>only-group</i></dt><dd><p>Only include dependencies from the specified dependency group.</p>
<p>The project and its dependencies will be omitted.</p>
<p>May be provided multiple times. Implies <code>--no-default-groups</code>.</p>
</dd><dt id="uv-sync--package"><a href="#uv-sync--package"><code>--package</code></a> <i>package</i></dt><dd><p>Sync for a specific package in the workspace.</p>
</dd><dt id="uv-sync--output-format"><a href="#uv-sync--output-format"><code>--output-format</code></a> <i>output-format</i></dt><dd><p>Select the output format</p>
<p>[default: text]</p><p>Possible values:</p>
<ul>
<li><code>text</code>: Display the result in a human-readable format</li>
<li><code>json</code>: Display the result in JSON format</li>
</ul></dd><dt id="uv-sync--package"><a href="#uv-sync--package"><code>--package</code></a> <i>package</i></dt><dd><p>Sync for a specific package in the workspace.</p>
<p>The workspace's environment (<code>.venv</code>) is updated to reflect the subset of dependencies declared by the specified workspace member package.</p>
<p>If the workspace member does not exist, uv will exit with an error.</p>
</dd><dt id="uv-sync--prerelease"><a href="#uv-sync--prerelease"><code>--prerelease</code></a> <i>prerelease</i></dt><dd><p>The strategy to use when considering pre-release versions.</p>