Skip to content

Commit

Permalink
Merge pull request #119 from mikrostew/notion-use
Browse files Browse the repository at this point in the history
Implement new version of `notion use`
  • Loading branch information
dherman authored Aug 21, 2018
2 parents 96fa89c + 849e7d1 commit 236b4cb
Show file tree
Hide file tree
Showing 15 changed files with 413 additions and 131 deletions.
18 changes: 18 additions & 0 deletions Cargo.lock

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

3 changes: 2 additions & 1 deletion crates/notion-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ term_size = "0.3.0"
indicatif = "0.9.0"
console = "0.6.1"
readext = "0.1.0"
serde_json = "1.0.3"
serde_json = { version = "1.0.3", features = ["preserve_order"] }
serde = "1.0.27"
serde_derive = "1.0.27"
node-archive = { path = "../node-archive" }
Expand All @@ -27,3 +27,4 @@ cfg-if = "0.1"
winfolder = "0.1"
tempfile = "3.0.2"
os_info = "0.7.0"
detect-indent = { "git" = "https://github.com/stefanpenner/detect-indent-rs", "branch" = "master" }
14 changes: 14 additions & 0 deletions crates/notion-core/fixtures/no_toolchain/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "basic-project",
"version": "0.0.7",
"description": "Testing that manifest pulls things out of this correctly",
"license": "To Kill",
"dependencies": {
"@namespace/some-dep": "0.2.4",
"rsvp": "^3.5.0"
},
"devDependencies": {
"@namespaced/something-else": "^6.3.7",
"eslint": "~4.8.0"
}
}
12 changes: 12 additions & 0 deletions crates/notion-core/src/catalog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,12 @@ impl Catalog {
Ok(fetched)
}

/// Resolves a Node version matching the specified semantic versioning requirements.
pub fn resolve_node(&self, matching: &VersionReq, config: &Config) -> Fallible<Version> {
let distro = self.node.resolve_remote(&matching, config.node.as_ref())?;
Ok(distro.version().clone())
}

/// Uninstalls a specific Node version from the local catalog.
pub fn uninstall_node(&mut self, version: &Version) -> Fallible<()> {
if self.node.contains(version) {
Expand Down Expand Up @@ -176,6 +182,12 @@ impl Catalog {
Ok(fetched)
}

/// Resolves a Yarn version matching the specified semantic versioning requirements.
pub fn resolve_yarn(&self, matching: &VersionReq, config: &Config) -> Fallible<Version> {
let distro = self.yarn.resolve_remote(&matching, config.yarn.as_ref())?;
Ok(distro.version().clone())
}

/// Uninstalls a specific Yarn version from the local catalog.
pub fn uninstall_yarn(&mut self, version: &Version) -> Fallible<()> {
if self.yarn.contains(version) {
Expand Down
17 changes: 16 additions & 1 deletion crates/notion-core/src/distro/node.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Provides the `Installer` type, which represents a provisioned Node installer.

use std::fs::{rename, File};
use std::path::PathBuf;
use std::string::ToString;

use super::{Distro, Fetched};
Expand All @@ -21,6 +22,20 @@ pub struct NodeDistro {
version: Version,
}

/// Check if the cached file is valid. It may have been corrupted or interrupted in the middle of
/// downloading.
fn cache_is_valid(cache_file: &PathBuf) -> bool {
if cache_file.is_file() {
if let Ok(file) = File::open(cache_file) {
match node_archive::load(file) {
Ok(_) => return true,
Err(_) => return false,
}
}
}
false
}

impl Distro for NodeDistro {
/// Provision a Node distribution from the public Node distributor (`https://nodejs.org`).
fn public(version: Version) -> Fallible<Self> {
Expand All @@ -34,7 +49,7 @@ impl Distro for NodeDistro {
let archive_file = path::node_archive_file(&version.to_string());
let cache_file = path::node_cache_dir()?.join(&archive_file);

if cache_file.is_file() {
if cache_is_valid(&cache_file) {
return NodeDistro::cached(version, File::open(cache_file).unknown()?);
}

Expand Down
17 changes: 16 additions & 1 deletion crates/notion-core/src/distro/yarn.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Provides the `Installer` type, which represents a provisioned Node installer.

use std::fs::{rename, File};
use std::path::PathBuf;
use std::string::ToString;

use super::{Distro, Fetched};
Expand All @@ -22,6 +23,20 @@ pub struct YarnDistro {
version: Version,
}

/// Check if the cached file is valid. It may have been corrupted or interrupted in the middle of
/// downloading.
fn cache_is_valid(cache_file: &PathBuf) -> bool {
if cache_file.is_file() {
if let Ok(file) = File::open(cache_file) {
match node_archive::load(file) {
Ok(_) => return true,
Err(_) => return false,
}
}
}
false
}

impl Distro for YarnDistro {
/// Provision a distribution from the public Yarn distributor (`https://yarnpkg.com`).
fn public(version: Version) -> Fallible<Self> {
Expand All @@ -35,7 +50,7 @@ impl Distro for YarnDistro {
let archive_file = path::yarn_archive_file(&version.to_string());
let cache_file = path::yarn_cache_dir()?.join(&archive_file);

if cache_file.is_file() {
if cache_is_valid(&cache_file) {
return YarnDistro::cached(version, File::open(cache_file).unknown()?);
}

Expand Down
1 change: 1 addition & 0 deletions crates/notion-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

extern crate cmdline_words_parser;
extern crate console;
extern crate detect_indent;
extern crate indicatif;
extern crate lazycell;
extern crate node_archive;
Expand Down
135 changes: 101 additions & 34 deletions crates/notion-core/src/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,33 @@

use std::collections::HashMap;
use std::fs::File;
use std::path::Path;
use std::io::Read;
use std::path::{Path, PathBuf};

use detect_indent;
use notion_fail::{Fallible, ResultExt};
use semver::VersionReq;
use serde::Serialize;
use serde_json;

use serial;

/// A Node manifest file.
pub struct Manifest {
/// The requested version of Node, under the `notion.node` key.
/// A toolchain manifest.
pub struct ToolchainManifest {
/// The requested version of Node, under the `toolchain.node` key.
pub node: VersionReq,
/// The requested version of Yarn, under the `notion.yarn` key.
/// The pinned version of Node as a string.
pub node_str: String,
/// The requested version of Yarn, under the `toolchain.yarn` key.
pub yarn: Option<VersionReq>,
/// The pinned version of Yarn as a string.
pub yarn_str: Option<String>,
}

/// A Node manifest file.
pub struct Manifest {
/// The `toolchain` section.
pub toolchain: Option<ToolchainManifest>,
/// The `dependencies` section.
pub dependencies: HashMap<String, String>,
/// The `devDependencies` section.
Expand All @@ -24,11 +37,74 @@ pub struct Manifest {

impl Manifest {
/// Loads and parses a Node manifest for the project rooted at the specified path.
pub fn for_dir(project_root: &Path) -> Fallible<Option<Manifest>> {
pub fn for_dir(project_root: &Path) -> Fallible<Manifest> {
// if package.json doesn't exist, this fails, OK
let file = File::open(project_root.join("package.json")).unknown()?;
let serial: serial::manifest::Manifest = serde_json::de::from_reader(file).unknown()?;
serial.into_manifest()
}

/// Returns whether this manifest contains a toolchain section (at least Node is pinned).
pub fn has_toolchain(&self) -> bool {
self.toolchain.is_some()
}

/// Returns the pinned version of Node as a VersionReq, if any.
pub fn node(&self) -> Option<VersionReq> {
self.toolchain.as_ref().map(|t| t.node.clone())
}

/// Returns the pinned verison of Node as a String, if any.
pub fn node_str(&self) -> Option<String> {
self.toolchain.as_ref().map(|t| t.node_str.clone())
}

/// Returns the pinned verison of Yarn as a VersionReq, if any.
pub fn yarn(&self) -> Option<VersionReq> {
self.toolchain
.as_ref()
.map(|t| t.yarn.clone())
.unwrap_or(None)
}

/// Returns the pinned verison of Yarn as a String, if any.
pub fn yarn_str(&self) -> Option<String> {
self.toolchain
.as_ref()
.map(|t| t.yarn_str.clone())
.unwrap_or(None)
}

/// Writes the input ToolchainManifest to package.json, adding the "toolchain" key if
/// necessary.
pub fn update_toolchain(
toolchain: serial::manifest::ToolchainManifest,
package_file: PathBuf,
) -> Fallible<()> {
// parse the entire package.json file into a Value
let file = File::open(&package_file).unknown()?;
let mut v: serde_json::Value = serde_json::from_reader(file).unknown()?;

// detect indentation in package.json
let mut contents = String::new();
let mut indent_file = File::open(&package_file).unknown()?;
indent_file.read_to_string(&mut contents).unknown()?;
let indent = detect_indent::detect_indent(&contents);

if let Some(map) = v.as_object_mut() {
// update the "toolchain" key
let toolchain_value = serde_json::to_value(toolchain).unknown()?;
map.insert("toolchain".to_string(), toolchain_value);

// serialize the updated contents back to package.json
let file = File::create(package_file).unknown()?;
let formatter =
serde_json::ser::PrettyFormatter::with_indent(indent.indent().as_bytes());
let mut ser = serde_json::Serializer::with_formatter(file, formatter);
map.serialize(&mut ser).unknown()?;
}
Ok(())
}
}

// unit tests
Expand All @@ -51,39 +127,21 @@ pub mod tests {
#[test]
fn gets_node_version() {
let project_path = fixture_path("basic");
let version = match Manifest::for_dir(&project_path) {
Ok(manifest) => manifest.unwrap().node,
_ => panic!(
"Error: Could not get manifest for project {:?}",
project_path
),
};
let version = Manifest::for_dir(&project_path).expect("Could not get manifest").node().unwrap();
assert_eq!(version, VersionReq::parse("=6.11.1").unwrap());
}

#[test]
fn gets_yarn_version() {
let project_path = fixture_path("basic");
let version = match Manifest::for_dir(&project_path) {
Ok(manifest) => manifest.unwrap().yarn,
_ => panic!(
"Error: Could not get manifest for project {:?}",
project_path
),
};
let version = Manifest::for_dir(&project_path).expect("Could not get manifest").yarn();
assert_eq!(version.unwrap(), VersionReq::parse("=1.2").unwrap());
}

#[test]
fn gets_dependencies() {
let project_path = fixture_path("basic");
let dependencies = match Manifest::for_dir(&project_path) {
Ok(manifest) => manifest.unwrap().dependencies,
_ => panic!(
"Error: Could not get manifest for project {:?}",
project_path
),
};
let dependencies = Manifest::for_dir(&project_path).expect("Could not get manifest").dependencies;
let mut expected_deps = HashMap::new();
expected_deps.insert("@namespace/some-dep".to_string(), "0.2.4".to_string());
expected_deps.insert("rsvp".to_string(), "^3.5.0".to_string());
Expand All @@ -93,13 +151,7 @@ pub mod tests {
#[test]
fn gets_dev_dependencies() {
let project_path = fixture_path("basic");
let dev_dependencies = match Manifest::for_dir(&project_path) {
Ok(manifest) => manifest.unwrap().dev_dependencies,
_ => panic!(
"Error: Could not get manifest for project {:?}",
project_path
),
};
let dev_dependencies = Manifest::for_dir(&project_path).expect("Could not get manifest").dev_dependencies;
let mut expected_deps = HashMap::new();
expected_deps.insert(
"@namespaced/something-else".to_string(),
Expand All @@ -108,4 +160,19 @@ pub mod tests {
expected_deps.insert("eslint".to_string(), "~4.8.0".to_string());
assert_eq!(dev_dependencies, expected_deps);
}

#[test]
fn node_for_no_toolchain() {
let project_path = fixture_path("no_toolchain");
let manifest = Manifest::for_dir(&project_path).expect("Could not get manifest");
assert_eq!(manifest.node(), None);
}

#[test]
fn yarn_for_no_toolchain() {
let project_path = fixture_path("no_toolchain");
let manifest = Manifest::for_dir(&project_path).expect("Could not get manifest");
assert_eq!(manifest.yarn(), None);
}

}
Loading

0 comments on commit 236b4cb

Please sign in to comment.