Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
ad98095
WIP initial commit...
JLCarveth Aug 26, 2025
af7f03c
Merge branch 'denoland:main' into deno-version
JLCarveth Aug 29, 2025
9dd616c
Make the git operations optional (git detection very rudimentary)
JLCarveth Aug 29, 2025
d7e9604
Formatting.
JLCarveth Aug 29, 2025
57bfdd9
Merge branch 'main' into deno-version
JLCarveth Sep 9, 2025
6163c23
Merge branch 'main' into deno-version
JLCarveth Sep 11, 2025
a0685e5
Initial tests for 'deno version'🦕
JLCarveth Sep 12, 2025
a0c621b
Merge branch 'main' into deno-version
JLCarveth Sep 12, 2025
bb4a354
Formatting.
JLCarveth Sep 12, 2025
796aa5c
Formatting
JLCarveth Sep 12, 2025
7cb2a1f
Merge branch 'main' into deno-version
JLCarveth Sep 16, 2025
2fd88db
Merge branch 'main' into deno-version
JLCarveth Sep 23, 2025
498e060
Remove git integration, rename to bump-version 🦕
JLCarveth Sep 26, 2025
1dde40d
Formatting 🧹
JLCarveth Sep 26, 2025
471a7c3
Unused import
JLCarveth Sep 26, 2025
918ef35
Merge branch 'main' into deno-version
bartlomieju Oct 13, 2025
08c415d
Added unstable warning to `deno bump-version`
JLCarveth Oct 14, 2025
b498c8c
Merge branch 'main' into deno-version
JLCarveth Oct 14, 2025
33b24c0
Merge branch 'main' into deno-version
JLCarveth Oct 24, 2025
53a89b9
Merge branch 'main' into deno-version
JLCarveth Nov 3, 2025
6201306
Remove ConfigKind enum, set default version to 0.1.0, remove unimpl.
JLCarveth Nov 4, 2025
48d9866
Merge branch 'main' into deno-version
JLCarveth Nov 11, 2025
0c39879
Merge branch 'main' into deno-version
bartlomieju Nov 12, 2025
9ed4a82
add to proper --help heading
bartlomieju Nov 12, 2025
ad1a6c9
Merge branch 'main' into deno-version
bartlomieju Nov 12, 2025
3223679
fix: prioritize deno.json over package.json
JLCarveth Nov 13, 2025
c1d9bc0
Simplify find config function
JLCarveth Nov 13, 2025
2946674
Merge branch 'main' into deno-version
JLCarveth Dec 2, 2025
a5b909a
Merge branch 'main' into deno-version
bartlomieju Dec 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 68 additions & 1 deletion cli/args/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,22 @@ pub struct RemoveFlags {
pub lockfile_only: bool,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct VersionFlags {
pub increment: Option<VersionIncrement>,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub enum VersionIncrement {
Major,
Minor,
Patch,
Premajor,
Preminor,
Prepatch,
Prerelease,
}

#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct BenchFlags {
pub files: FileFlags,
Expand Down Expand Up @@ -583,6 +599,7 @@ pub enum DenoSubcommand {
Types,
Upgrade(UpgradeFlags),
Vendor,
BumpVersion(VersionFlags),
Publish(PublishFlags),
Help(HelpFlags),
}
Expand Down Expand Up @@ -1539,11 +1556,13 @@ static DENO_HELP: &str = cstr!(
<y>Dependency management:</>
<g>add</> Add dependencies
<p(245)>deno add jsr:@std/assert | deno add npm:express</>
<g>bump-version</> Update version in the configuration file
<g>install</> Installs dependencies either in the local project or globally to a bin directory
<g>uninstall</> Uninstalls a dependency or an executable script in the installation root's bin directory
<g>outdated</> Find and update outdated dependencies
<g>approve-scripts</> Approve npm lifecycle scripts
<g>remove</> Remove dependencies from the configuration file
<g>publish</> Publish the current working directory's package or workspace
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you move this back? I don't believe this is dependency management?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this, I think it's fine


<y>Tooling:</>
<g>bench</> Run benchmarks
Expand All @@ -1564,7 +1583,6 @@ static DENO_HELP: &str = cstr!(
<g>init</> Initialize a new project
<g>test</> Run tests
<p(245)>deno test | deno test test.ts</>
<g>publish</> Publish the current working directory's package or workspace
<g>upgrade</> Upgrade deno executable to given version
<p(245)>deno upgrade | deno upgrade 1.45.0 | deno upgrade canary</>
{after-help}
Expand Down Expand Up @@ -1764,6 +1782,7 @@ pub fn flags_from_vec_with_initial_cwd(
"uninstall" => uninstall_parse(&mut flags, &mut m),
"update" => outdated_parse(&mut flags, &mut m, true)?,
"upgrade" => upgrade_parse(&mut flags, &mut m),
"bump-version" => bump_version_parse(&mut flags, &mut m),
"vendor" => vendor_parse(&mut flags, &mut m),
"publish" => publish_parse(&mut flags, &mut m)?,
_ => unreachable!(),
Expand Down Expand Up @@ -2028,6 +2047,7 @@ pub fn clap_root() -> Command {
.subcommand(types_subcommand())
.subcommand(update_subcommand())
.subcommand(upgrade_subcommand())
.subcommand(bump_version_subcommand())
.subcommand(vendor_subcommand());

let help = help_subcommand(&cmd);
Expand Down Expand Up @@ -4203,6 +4223,35 @@ different location, use the <c>--output</> flag:
})
}

fn bump_version_subcommand() -> Command {
command(
"bump-version",
cstr!(
"Update version in the configuration file.
<p(245)>deno bump-version patch</> # 1.0.0 -> 1.0.1
<p(245)>deno bump-version minor</> # 1.0.0 -> 1.1.0
<p(245)>deno bump-version major</> # 1.0.0 -> 2.0.0"
),
UnstableArgsConfig::None,
)
.defer(|cmd| {
cmd.arg(
Arg::new("increment")
.help("Version increment type")
.value_parser([
"major",
"minor",
"patch",
"premajor",
"preminor",
"prepatch",
"prerelease",
])
.index(1),
)
})
}

fn vendor_subcommand() -> Command {
command("vendor",
"`deno vendor` was removed in Deno 2.
Expand Down Expand Up @@ -5481,6 +5530,24 @@ fn remove_parse(flags: &mut Flags, matches: &mut ArgMatches) {
});
}

fn bump_version_parse(flags: &mut Flags, matches: &mut ArgMatches) {
let increment =
matches
.remove_one::<String>("increment")
.and_then(|s| match s.as_str() {
"major" => Some(VersionIncrement::Major),
"minor" => Some(VersionIncrement::Minor),
"patch" => Some(VersionIncrement::Patch),
"premajor" => Some(VersionIncrement::Premajor),
"preminor" => Some(VersionIncrement::Preminor),
"prepatch" => Some(VersionIncrement::Prepatch),
"prerelease" => Some(VersionIncrement::Prerelease),
_ => None,
});

flags.subcommand = DenoSubcommand::BumpVersion(VersionFlags { increment });
}

fn outdated_parse(
flags: &mut Flags,
matches: &mut ArgMatches,
Expand Down
7 changes: 7 additions & 0 deletions cli/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,13 @@ async fn run_subcommand(
"This deno was built without the \"upgrade\" feature. Please upgrade using the installation method originally used to install Deno.",
1,
),
DenoSubcommand::BumpVersion(version_flags) => spawn_subcommand(async {
log::warn!(
"⚠️ {} is experimental and subject to change",
colors::cyan("deno bump-version")
);
tools::bump_version::bump_version_command(flags, version_flags)
}),
DenoSubcommand::Vendor => exit_with_message(
"⚠️ `deno vendor` was removed in Deno 2.\n\nSee the Deno 1.x to 2.x Migration Guide for migration instructions: https://docs.deno.com/runtime/manual/advanced/migrate_deprecations",
1,
Expand Down
240 changes: 240 additions & 0 deletions cli/tools/bump_version.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
// Copyright 2018-2025 the Deno authors. MIT license.

use std::path::PathBuf;
use std::sync::Arc;

use deno_core::anyhow::Context;
use deno_core::anyhow::bail;
use deno_core::error::AnyError;
use deno_semver::SmallStackString;
use deno_semver::Version;
use jsonc_parser::cst::CstObject;
use jsonc_parser::cst::CstRootNode;
use jsonc_parser::json;
use log::info;

use crate::args::CliOptions;
use crate::args::Flags;
use crate::args::VersionFlags;
use crate::args::VersionIncrement;
use crate::factory::CliFactory;

struct ConfigUpdater {
cst: CstRootNode,
root_object: CstObject,
path: PathBuf,
modified: bool,
}

impl ConfigUpdater {
fn new(config_file_path: PathBuf) -> Result<Self, AnyError> {
let config_file_contents = std::fs::read_to_string(&config_file_path)
.with_context(|| {
format!("Reading config file '{}'", config_file_path.display())
})?;
let cst = CstRootNode::parse(&config_file_contents, &Default::default())
.with_context(|| {
format!("Parsing config file '{}'", config_file_path.display())
})?;
let root_object = cst.object_value_or_set();
Ok(Self {
cst,
root_object,
path: config_file_path,
modified: false,
})
}

fn display_path(&self) -> String {
deno_path_util::url_from_file_path(&self.path)
.map(|u| u.to_string())
.unwrap_or_else(|_| self.path.display().to_string())
}

fn contents(&self) -> String {
self.cst.to_string()
}

fn get_version(&self) -> Option<String> {
self
.root_object
.get("version")?
.value()?
.as_string_lit()
.and_then(|s| s.decoded_value().ok())
}

fn set_version(&mut self, version: &str) {
let version_prop = self.root_object.get("version");
match version_prop {
Some(prop) => {
prop.set_value(json!(version));
self.modified = true;
}
None => {
// Insert the version property at the beginning for better organization
self.root_object.insert(0, "version", json!(version));
self.modified = true;
}
}
}

fn commit(&self) -> Result<(), AnyError> {
if !self.modified {
return Ok(());
}

let new_text = self.contents();
std::fs::write(&self.path, new_text).with_context(|| {
format!("failed writing to '{}'", self.path.display())
})?;
Ok(())
}
}

fn increment_version(
current: &Version,
increment: &VersionIncrement,
) -> Result<Version, AnyError> {
let new_version = match increment {
VersionIncrement::Major => Version {
major: current.major + 1,
minor: 0,
patch: 0,
pre: Default::default(),
build: Default::default(),
},
VersionIncrement::Minor => Version {
major: current.major,
minor: current.minor + 1,
patch: 0,
pre: Default::default(),
build: Default::default(),
},
VersionIncrement::Patch => Version {
major: current.major,
minor: current.minor,
patch: current.patch + 1,
pre: Default::default(),
build: Default::default(),
},

VersionIncrement::Premajor
| VersionIncrement::Preminor
| VersionIncrement::Prepatch => {
let mut version = match increment {
VersionIncrement::Premajor => Version {
major: current.major + 1,
minor: 0,
patch: 0,
..Default::default()
},
VersionIncrement::Preminor => Version {
major: current.major,
minor: current.minor + 1,
patch: 0,
..Default::default()
},
VersionIncrement::Prepatch => Version {
major: current.major,
minor: current.minor,
patch: current.patch + 1,
..Default::default()
},
_ => unreachable!(),
};
version.pre = vec![SmallStackString::from_static("0")].into();
version
}

VersionIncrement::Prerelease => {
let mut new_version = current.clone();
if new_version.pre.is_empty() {
new_version.patch += 1;
new_version.pre = vec![SmallStackString::from_static("0")].into();
} else {
let mut pre_vec = new_version.pre.iter().cloned().collect::<Vec<_>>();
if let Some(last) = pre_vec.last_mut() {
if let Ok(num) = last.parse::<u64>() {
*last = SmallStackString::from_string((num + 1).to_string());
} else {
pre_vec.push(SmallStackString::from_static("0"));
}
}
new_version.pre = pre_vec.into();
}
new_version
}
};

Ok(new_version)
}

fn find_config_file(
cli_options: &CliOptions,
) -> Result<ConfigUpdater, AnyError> {
let start_dir = &cli_options.start_dir;

// Check for deno.json first - it takes priority
if let Some(deno_json) = start_dir.maybe_deno_json() {
let config_path = deno_path_util::url_to_file_path(&deno_json.specifier)
.context("Failed to convert deno.json URL to path")?;
return ConfigUpdater::new(config_path);
} else if let Some(pkg_json) = start_dir.maybe_pkg_json() {
// Only fall back to package.json if deno.json doesn't exist
return ConfigUpdater::new(pkg_json.path.clone());
}

bail!("No deno.json or package.json found in the current directory")
}

pub fn bump_version_command(
flags: Arc<Flags>,
version_flags: VersionFlags,
) -> Result<(), AnyError> {
let factory = CliFactory::from_flags(flags);
let cli_options = factory.cli_options()?;

// Find and load config file
let mut config = find_config_file(cli_options)?;

// Get the current version from the config file
let current_version = if let Some(version_str) = config.get_version() {
Version::parse_standard(&version_str).with_context(|| {
format!(
"Failed to parse version '{}' in {}",
version_str,
config.display_path()
)
})?
} else {
if version_flags.increment.is_none() {
// If no increment specified and no version found, just show current state
info!("No version found in configuration file");
return Ok(());
}
// Default to 0.1.0 if no version is found but increment is specified
Version::parse_standard("0.1.0")
.with_context(|| "Failed to create default version")?
};

let new_version = match &version_flags.increment {
Some(increment) => increment_version(&current_version, increment)?,
None => {
// Just show the current version
info!("{}", current_version);
return Ok(());
}
};

// Update version in config file
config.set_version(&new_version.to_string());
config.commit()?;
info!("Updated version in {}", config.display_path());

info!(
"Version updated from {} to {}",
current_version, new_version
);
Ok(())
}
1 change: 1 addition & 0 deletions cli/tools/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright 2018-2025 the Deno authors. MIT license.

pub mod bench;
pub mod bump_version;
pub mod bundle;
pub mod check;
pub mod clean;
Expand Down
Loading
Loading