Skip to content

Commit

Permalink
feat(add): add oro add command for adding new deps
Browse files Browse the repository at this point in the history
Fixes: #238
  • Loading branch information
zkat committed Apr 13, 2023
1 parent 3fa23e4 commit 7ed9f77
Show file tree
Hide file tree
Showing 17 changed files with 440 additions and 35 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Expand Up @@ -22,6 +22,8 @@ node-maintainer = { version = "=0.3.19", path = "./crates/node-maintainer" }
oro-client = { version = "=0.3.19", path = "./crates/oro-client" }
oro-common = { version = "=0.3.19", path = "./crates/oro-common" }
oro-config = { version = "=0.3.19", path = "./crates/oro-config" }
oro-package-spec = { version = "=0.3.19", path = "./crates/oro-package-spec" }
oro-pretty-json = { version = "=0.3.19", path = "./crates/oro-pretty-json" }

# Regular deps
async-std = { workspace = true, features = ["attributes", "tokio1", "unstable"] }
Expand Down
1 change: 1 addition & 0 deletions book/src/SUMMARY.md
Expand Up @@ -13,6 +13,7 @@

# Commands

- [add](./commands/add.md)
- [apply](./commands/apply.md)
- [ping](./commands/ping.md)
- [reapply](./commands/reapply.md)
Expand Down
1 change: 1 addition & 0 deletions book/src/commands/add.md
@@ -0,0 +1 @@
{{#include ../../../tests/snapshots/help__add.snap:8:}}
14 changes: 14 additions & 0 deletions crates/oro-package-spec/src/lib.rs
Expand Up @@ -69,6 +69,20 @@ impl PackageSpec {
}
}

pub fn target_mut(&mut self) -> &mut PackageSpec {
use PackageSpec::*;
match self {
Alias { spec, .. } => {
if spec.is_alias() {
spec.target_mut()
} else {
spec
}
}
_ => self,
}
}

pub fn requested(&self) -> String {
use PackageSpec::*;
match self {
Expand Down
4 changes: 3 additions & 1 deletion crates/oro-pretty-json/src/lib.rs
Expand Up @@ -73,7 +73,9 @@ fn detect_indentation(json: &str) -> Option<(char, usize)> {
fn detect_line_end(json: &str) -> Option<(String, bool)> {
json.find(['\r', '\n'])
.map(|idx| {
let c = json.get(idx..idx + 1).expect("we already know there's a char there");
let c = json
.get(idx..idx + 1)
.expect("we already know there's a char there");
if c == "\r" && json.get(idx..idx + 2) == Some("\r\n") {
return "\r\n".into();
}
Expand Down
4 changes: 2 additions & 2 deletions src/apply.rs → src/apply_args.rs
Expand Up @@ -15,7 +15,7 @@ use url::Url;
/// the right state to execute, based on your declared dependencies.
#[derive(Debug, Args)]
#[command(next_help_heading = "Apply Options")]
pub struct Apply {
pub struct ApplyArgs {
/// Prevent all apply operations from executing.
#[arg(
long = "no-apply",
Expand Down Expand Up @@ -109,7 +109,7 @@ pub struct Apply {
pub emoji: bool,
}

impl Apply {
impl ApplyArgs {
pub async fn execute(&self) -> Result<()> {
let total_time = std::time::Instant::now();

Expand Down
171 changes: 171 additions & 0 deletions src/commands/add.rs
@@ -0,0 +1,171 @@
use async_trait::async_trait;
use clap::Args;
use miette::{IntoDiagnostic, Result};
use nassun::PackageResolution;
use oro_package_spec::{PackageSpec, VersionSpec};
use oro_pretty_json::Formatted;

use crate::apply_args::ApplyArgs;
use crate::commands::OroCommand;
use crate::nassun_args::NassunArgs;

/// Adds one or more dependencies to the target package.
#[derive(Debug, Args)]
pub struct AddCmd {
/// Specifiers for packages to add.
#[arg(required = true)]
specs: Vec<String>,

/// Prefix to prepend to package versions for resolved NPM dependencies.
///
/// For example, if you do `oro add foo@1.2.3 --prefix ~`, this will write `"foo": "~1.2.3"` to your `package.json`.
#[arg(long, default_value = "^")]
prefix: String,

/// Add packages as devDependencies.
#[arg(long, short = 'D')]
dev: bool,

/// Add packages as optionalDependencies.
#[arg(long, short = 'O', visible_alias = "optional")]
opt: bool,

#[command(flatten)]
apply: ApplyArgs,
}

#[async_trait]
impl OroCommand for AddCmd {
async fn execute(self) -> Result<()> {
let mut manifest = oro_pretty_json::from_str(
&async_std::fs::read_to_string(self.apply.root.join("package.json"))
.await
.into_diagnostic()?,
)
.into_diagnostic()?;
let nassun = NassunArgs::from_apply_args(&self.apply).to_nassun();
use PackageResolution as Pr;
use PackageSpec as Ps;
let mut count = 0;
for spec in &self.specs {
let pkg = nassun.resolve(spec).await?;
let name = pkg.name();
let requested: PackageSpec = spec.parse()?;
let resolved_spec = match requested.target() {
Ps::Alias { .. } => {
unreachable!(".target() ensures this alias is fully resolved");
}
Ps::Git(info) => {
format!("{info}")
}
Ps::Dir { path } => {
{
// TODO: make relative to root?
path.to_string_lossy().to_string()
}
}
Ps::Npm { .. } => {
let mut from = pkg.from().clone();
let resolved = pkg.resolved();
let version = if let Pr::Npm { version, .. } = resolved {
version
} else {
unreachable!("No other type of spec should be here.");
};
match from.target_mut() {
Ps::Npm { requested, .. } => {
// We use Tag in a hacky way here to have some level of "preserved" formatting.
*requested =
Some(VersionSpec::Tag(format!("{}{version}", self.prefix)));
}
_ => {
unreachable!("No other type of spec should be here.");
}
}
from.requested()
}
};
tracing::info!(
"{}Resolved {spec} to {name}@{resolved_spec}.",
if self.apply.emoji { "🔍 " } else { "" }
);
self.remove_from_manifest(&mut manifest, name);
self.add_to_manifest(&mut manifest, name, &resolved_spec);
count += 1;
}

async_std::fs::write(
self.apply.root.join("package.json"),
oro_pretty_json::to_string_pretty(&manifest).into_diagnostic()?,
)
.await
.into_diagnostic()?;

tracing::info!(
"{}Updated package.json with {count} new {}.",
if self.apply.emoji { "📝 " } else { "" },
if count == 1 {
self.dep_kind_str_singular()
} else {
self.dep_kind_str()
}
);

// TODO: Force locked = false here, once --locked is supported.
// Using `oro add` with `--locked` doesn't make sense.
// self.apply.locked = false;

// Then, we apply the change.
self.apply.execute().await
}
}

impl AddCmd {
fn add_to_manifest(&self, mani: &mut Formatted, name: &str, spec: &str) {
let deps = self.dep_kind_str();
tracing::debug!("Adding {name}@{spec} to {deps}.");
mani.value[deps][name] =
serde_json::to_value(spec).expect("Value is always a valid string");
}

fn remove_from_manifest(&self, mani: &mut Formatted, name: &str) {
for ty in [
"dependencies",
"devDependencies",
"optionalDependencies",
"peerDependencies",
] {
if mani.value[ty].is_object() {
if let Some(obj) = mani.value[ty].as_object_mut() {
if obj.contains_key(name) {
tracing::debug!(
"Removing {name}@{} from {ty}.",
obj[name].as_str().unwrap_or("")
);
obj.remove(name);
}
}
}
}
}

fn dep_kind_str(&self) -> &'static str {
if self.dev {
"devDependencies"
} else if self.opt {
"optionalDependencies"
} else {
"dependencies"
}
}

fn dep_kind_str_singular(&self) -> &'static str {
if self.dev {
"devDependency"
} else if self.opt {
"optionalDependency"
} else {
"dependency"
}
}
}
4 changes: 2 additions & 2 deletions src/commands/apply.rs
Expand Up @@ -2,7 +2,7 @@ use async_trait::async_trait;
use clap::Args;
use miette::Result;

use crate::apply::Apply;
use crate::apply_args::ApplyArgs;
use crate::commands::OroCommand;

/// Applies the current project's requested dependencies to `node_modules/`,
Expand All @@ -17,7 +17,7 @@ use crate::commands::OroCommand;
#[clap(visible_aliases(["a", "ap", "app"]))]
pub struct ApplyCmd {
#[command(flatten)]
apply: Apply,
apply: ApplyArgs,
}

#[async_trait]
Expand Down
1 change: 1 addition & 0 deletions src/commands/mod.rs
@@ -1,6 +1,7 @@
use async_trait::async_trait;
use miette::Result;

pub mod add;
pub mod apply;
pub mod ping;
pub mod reapply;
Expand Down
4 changes: 2 additions & 2 deletions src/commands/reapply.rs
Expand Up @@ -2,15 +2,15 @@ use async_trait::async_trait;
use clap::Args;
use miette::{IntoDiagnostic, Result};

use crate::apply::Apply;
use crate::apply_args::ApplyArgs;
use crate::commands::OroCommand;

/// Removes the existing `node_modules`, if any, and reapplies it from
/// scratch. You can use this to make sure you have a pristine `node_modules`.
#[derive(Debug, Args)]
pub struct ReapplyCmd {
#[command(flatten)]
apply: Apply,
apply: ApplyArgs,
}

#[async_trait]
Expand Down
32 changes: 5 additions & 27 deletions src/commands/view.rs
@@ -1,16 +1,13 @@
use std::path::PathBuf;

use async_trait::async_trait;
use clap::Args;
use colored::*;
use humansize::{file_size_opts, FileSize};
use miette::{IntoDiagnostic, Result, WrapErr};
use nassun::NassunOpts;
use oro_common::{Bin, Manifest, NpmUser, Person, PersonField, VersionMetadata};
use term_grid::{Cell, Direction, Filling, Grid, GridOptions};
use url::Url;

use crate::commands::OroCommand;
use crate::nassun_args::NassunArgs;

#[derive(Debug, Args)]
/// Get information about a package.
Expand All @@ -20,36 +17,17 @@ pub struct ViewCmd {
#[arg()]
pkg: String,

#[arg(from_global)]
registry: Url,

#[arg(from_global)]
scoped_registries: Vec<(String, Url)>,

#[arg(from_global)]
root: Option<PathBuf>,

#[arg(from_global)]
cache: Option<PathBuf>,

#[arg(from_global)]
json: bool,

#[command(flatten)]
nassun_args: NassunArgs,
}

#[async_trait]
impl OroCommand for ViewCmd {
async fn execute(self) -> Result<()> {
let mut nassun_opts = NassunOpts::new().registry(self.registry);
for (scope, registry) in self.scoped_registries {
nassun_opts = nassun_opts.scope_registry(scope, registry);
}
if let Some(root) = self.root {
nassun_opts = nassun_opts.base_dir(root);
}
if let Some(cache) = self.cache {
nassun_opts = nassun_opts.cache(cache);
}
let pkg = nassun_opts.build().resolve(&self.pkg).await?;
let pkg = self.nassun_args.to_nassun().resolve(&self.pkg).await?;
let packument = pkg.packument().await?;
let metadata = pkg.metadata().await?;
// TODO: oro view pkg [<field>[.<subfield>...]]
Expand Down
6 changes: 5 additions & 1 deletion src/lib.rs
Expand Up @@ -57,8 +57,9 @@ use url::Url;

use commands::OroCommand;

mod apply;
mod apply_args;
mod commands;
mod nassun_args;

const MAX_RETAINED_LOGS: usize = 5;

Expand Down Expand Up @@ -435,6 +436,8 @@ where

#[derive(Debug, Subcommand)]
pub enum OroCmd {
Add(commands::add::AddCmd),

Apply(commands::apply::ApplyCmd),

Ping(commands::ping::PingCmd),
Expand All @@ -452,6 +455,7 @@ impl OroCommand for Orogene {
async fn execute(self) -> Result<()> {
log_command_line();
match self.subcommand {
OroCmd::Add(cmd) => cmd.execute().await,
OroCmd::Apply(cmd) => cmd.execute().await,
OroCmd::Ping(cmd) => cmd.execute().await,
OroCmd::Reapply(cmd) => cmd.execute().await,
Expand Down

0 comments on commit 7ed9f77

Please sign in to comment.