-
Notifications
You must be signed in to change notification settings - Fork 5.8k
feat(cli): add deno bump-version subcommand
#30562
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
JLCarveth
wants to merge
29
commits into
denoland:main
Choose a base branch
from
JLCarveth:deno-version
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+547
−1
Open
Changes from all commits
Commits
Show all changes
29 commits
Select commit
Hold shift + click to select a range
ad98095
WIP initial commit...
JLCarveth af7f03c
Merge branch 'denoland:main' into deno-version
JLCarveth 9dd616c
Make the git operations optional (git detection very rudimentary)
JLCarveth d7e9604
Formatting.
JLCarveth 57bfdd9
Merge branch 'main' into deno-version
JLCarveth 6163c23
Merge branch 'main' into deno-version
JLCarveth a0685e5
Initial tests for 'deno version'🦕
JLCarveth a0c621b
Merge branch 'main' into deno-version
JLCarveth bb4a354
Formatting.
JLCarveth 796aa5c
Formatting
JLCarveth 7cb2a1f
Merge branch 'main' into deno-version
JLCarveth 2fd88db
Merge branch 'main' into deno-version
JLCarveth 498e060
Remove git integration, rename to bump-version 🦕
JLCarveth 1dde40d
Formatting 🧹
JLCarveth 471a7c3
Unused import
JLCarveth 918ef35
Merge branch 'main' into deno-version
bartlomieju 08c415d
Added unstable warning to `deno bump-version`
JLCarveth b498c8c
Merge branch 'main' into deno-version
JLCarveth 33b24c0
Merge branch 'main' into deno-version
JLCarveth 53a89b9
Merge branch 'main' into deno-version
JLCarveth 6201306
Remove ConfigKind enum, set default version to 0.1.0, remove unimpl.
JLCarveth 48d9866
Merge branch 'main' into deno-version
JLCarveth 0c39879
Merge branch 'main' into deno-version
bartlomieju 9ed4a82
add to proper --help heading
bartlomieju ad1a6c9
Merge branch 'main' into deno-version
bartlomieju 3223679
fix: prioritize deno.json over package.json
JLCarveth c1d9bc0
Simplify find config function
JLCarveth 2946674
Merge branch 'main' into deno-version
JLCarveth a5b909a
Merge branch 'main' into deno-version
bartlomieju File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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(¤t_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(()) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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