Skip to content

Commit 236b4cb

Browse files
authored
Merge pull request #119 from mikrostew/notion-use
Implement new version of `notion use`
2 parents 96fa89c + 849e7d1 commit 236b4cb

File tree

15 files changed

+413
-131
lines changed

15 files changed

+413
-131
lines changed

Cargo.lock

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/notion-core/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ term_size = "0.3.0"
1212
indicatif = "0.9.0"
1313
console = "0.6.1"
1414
readext = "0.1.0"
15-
serde_json = "1.0.3"
15+
serde_json = { version = "1.0.3", features = ["preserve_order"] }
1616
serde = "1.0.27"
1717
serde_derive = "1.0.27"
1818
node-archive = { path = "../node-archive" }
@@ -27,3 +27,4 @@ cfg-if = "0.1"
2727
winfolder = "0.1"
2828
tempfile = "3.0.2"
2929
os_info = "0.7.0"
30+
detect-indent = { "git" = "https://github.com/stefanpenner/detect-indent-rs", "branch" = "master" }
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"name": "basic-project",
3+
"version": "0.0.7",
4+
"description": "Testing that manifest pulls things out of this correctly",
5+
"license": "To Kill",
6+
"dependencies": {
7+
"@namespace/some-dep": "0.2.4",
8+
"rsvp": "^3.5.0"
9+
},
10+
"devDependencies": {
11+
"@namespaced/something-else": "^6.3.7",
12+
"eslint": "~4.8.0"
13+
}
14+
}

crates/notion-core/src/catalog.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,12 @@ impl Catalog {
126126
Ok(fetched)
127127
}
128128

129+
/// Resolves a Node version matching the specified semantic versioning requirements.
130+
pub fn resolve_node(&self, matching: &VersionReq, config: &Config) -> Fallible<Version> {
131+
let distro = self.node.resolve_remote(&matching, config.node.as_ref())?;
132+
Ok(distro.version().clone())
133+
}
134+
129135
/// Uninstalls a specific Node version from the local catalog.
130136
pub fn uninstall_node(&mut self, version: &Version) -> Fallible<()> {
131137
if self.node.contains(version) {
@@ -176,6 +182,12 @@ impl Catalog {
176182
Ok(fetched)
177183
}
178184

185+
/// Resolves a Yarn version matching the specified semantic versioning requirements.
186+
pub fn resolve_yarn(&self, matching: &VersionReq, config: &Config) -> Fallible<Version> {
187+
let distro = self.yarn.resolve_remote(&matching, config.yarn.as_ref())?;
188+
Ok(distro.version().clone())
189+
}
190+
179191
/// Uninstalls a specific Yarn version from the local catalog.
180192
pub fn uninstall_yarn(&mut self, version: &Version) -> Fallible<()> {
181193
if self.yarn.contains(version) {

crates/notion-core/src/distro/node.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Provides the `Installer` type, which represents a provisioned Node installer.
22
33
use std::fs::{rename, File};
4+
use std::path::PathBuf;
45
use std::string::ToString;
56

67
use super::{Distro, Fetched};
@@ -21,6 +22,20 @@ pub struct NodeDistro {
2122
version: Version,
2223
}
2324

25+
/// Check if the cached file is valid. It may have been corrupted or interrupted in the middle of
26+
/// downloading.
27+
fn cache_is_valid(cache_file: &PathBuf) -> bool {
28+
if cache_file.is_file() {
29+
if let Ok(file) = File::open(cache_file) {
30+
match node_archive::load(file) {
31+
Ok(_) => return true,
32+
Err(_) => return false,
33+
}
34+
}
35+
}
36+
false
37+
}
38+
2439
impl Distro for NodeDistro {
2540
/// Provision a Node distribution from the public Node distributor (`https://nodejs.org`).
2641
fn public(version: Version) -> Fallible<Self> {
@@ -34,7 +49,7 @@ impl Distro for NodeDistro {
3449
let archive_file = path::node_archive_file(&version.to_string());
3550
let cache_file = path::node_cache_dir()?.join(&archive_file);
3651

37-
if cache_file.is_file() {
52+
if cache_is_valid(&cache_file) {
3853
return NodeDistro::cached(version, File::open(cache_file).unknown()?);
3954
}
4055

crates/notion-core/src/distro/yarn.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Provides the `Installer` type, which represents a provisioned Node installer.
22
33
use std::fs::{rename, File};
4+
use std::path::PathBuf;
45
use std::string::ToString;
56

67
use super::{Distro, Fetched};
@@ -22,6 +23,20 @@ pub struct YarnDistro {
2223
version: Version,
2324
}
2425

26+
/// Check if the cached file is valid. It may have been corrupted or interrupted in the middle of
27+
/// downloading.
28+
fn cache_is_valid(cache_file: &PathBuf) -> bool {
29+
if cache_file.is_file() {
30+
if let Ok(file) = File::open(cache_file) {
31+
match node_archive::load(file) {
32+
Ok(_) => return true,
33+
Err(_) => return false,
34+
}
35+
}
36+
}
37+
false
38+
}
39+
2540
impl Distro for YarnDistro {
2641
/// Provision a distribution from the public Yarn distributor (`https://yarnpkg.com`).
2742
fn public(version: Version) -> Fallible<Self> {
@@ -35,7 +50,7 @@ impl Distro for YarnDistro {
3550
let archive_file = path::yarn_archive_file(&version.to_string());
3651
let cache_file = path::yarn_cache_dir()?.join(&archive_file);
3752

38-
if cache_file.is_file() {
53+
if cache_is_valid(&cache_file) {
3954
return YarnDistro::cached(version, File::open(cache_file).unknown()?);
4055
}
4156

crates/notion-core/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
extern crate cmdline_words_parser;
66
extern crate console;
7+
extern crate detect_indent;
78
extern crate indicatif;
89
extern crate lazycell;
910
extern crate node_archive;

crates/notion-core/src/manifest.rs

Lines changed: 101 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,33 @@
22
33
use std::collections::HashMap;
44
use std::fs::File;
5-
use std::path::Path;
5+
use std::io::Read;
6+
use std::path::{Path, PathBuf};
67

8+
use detect_indent;
79
use notion_fail::{Fallible, ResultExt};
810
use semver::VersionReq;
11+
use serde::Serialize;
912
use serde_json;
1013

1114
use serial;
1215

13-
/// A Node manifest file.
14-
pub struct Manifest {
15-
/// The requested version of Node, under the `notion.node` key.
16+
/// A toolchain manifest.
17+
pub struct ToolchainManifest {
18+
/// The requested version of Node, under the `toolchain.node` key.
1619
pub node: VersionReq,
17-
/// The requested version of Yarn, under the `notion.yarn` key.
20+
/// The pinned version of Node as a string.
21+
pub node_str: String,
22+
/// The requested version of Yarn, under the `toolchain.yarn` key.
1823
pub yarn: Option<VersionReq>,
24+
/// The pinned version of Yarn as a string.
25+
pub yarn_str: Option<String>,
26+
}
27+
28+
/// A Node manifest file.
29+
pub struct Manifest {
30+
/// The `toolchain` section.
31+
pub toolchain: Option<ToolchainManifest>,
1932
/// The `dependencies` section.
2033
pub dependencies: HashMap<String, String>,
2134
/// The `devDependencies` section.
@@ -24,11 +37,74 @@ pub struct Manifest {
2437

2538
impl Manifest {
2639
/// Loads and parses a Node manifest for the project rooted at the specified path.
27-
pub fn for_dir(project_root: &Path) -> Fallible<Option<Manifest>> {
40+
pub fn for_dir(project_root: &Path) -> Fallible<Manifest> {
41+
// if package.json doesn't exist, this fails, OK
2842
let file = File::open(project_root.join("package.json")).unknown()?;
2943
let serial: serial::manifest::Manifest = serde_json::de::from_reader(file).unknown()?;
3044
serial.into_manifest()
3145
}
46+
47+
/// Returns whether this manifest contains a toolchain section (at least Node is pinned).
48+
pub fn has_toolchain(&self) -> bool {
49+
self.toolchain.is_some()
50+
}
51+
52+
/// Returns the pinned version of Node as a VersionReq, if any.
53+
pub fn node(&self) -> Option<VersionReq> {
54+
self.toolchain.as_ref().map(|t| t.node.clone())
55+
}
56+
57+
/// Returns the pinned verison of Node as a String, if any.
58+
pub fn node_str(&self) -> Option<String> {
59+
self.toolchain.as_ref().map(|t| t.node_str.clone())
60+
}
61+
62+
/// Returns the pinned verison of Yarn as a VersionReq, if any.
63+
pub fn yarn(&self) -> Option<VersionReq> {
64+
self.toolchain
65+
.as_ref()
66+
.map(|t| t.yarn.clone())
67+
.unwrap_or(None)
68+
}
69+
70+
/// Returns the pinned verison of Yarn as a String, if any.
71+
pub fn yarn_str(&self) -> Option<String> {
72+
self.toolchain
73+
.as_ref()
74+
.map(|t| t.yarn_str.clone())
75+
.unwrap_or(None)
76+
}
77+
78+
/// Writes the input ToolchainManifest to package.json, adding the "toolchain" key if
79+
/// necessary.
80+
pub fn update_toolchain(
81+
toolchain: serial::manifest::ToolchainManifest,
82+
package_file: PathBuf,
83+
) -> Fallible<()> {
84+
// parse the entire package.json file into a Value
85+
let file = File::open(&package_file).unknown()?;
86+
let mut v: serde_json::Value = serde_json::from_reader(file).unknown()?;
87+
88+
// detect indentation in package.json
89+
let mut contents = String::new();
90+
let mut indent_file = File::open(&package_file).unknown()?;
91+
indent_file.read_to_string(&mut contents).unknown()?;
92+
let indent = detect_indent::detect_indent(&contents);
93+
94+
if let Some(map) = v.as_object_mut() {
95+
// update the "toolchain" key
96+
let toolchain_value = serde_json::to_value(toolchain).unknown()?;
97+
map.insert("toolchain".to_string(), toolchain_value);
98+
99+
// serialize the updated contents back to package.json
100+
let file = File::create(package_file).unknown()?;
101+
let formatter =
102+
serde_json::ser::PrettyFormatter::with_indent(indent.indent().as_bytes());
103+
let mut ser = serde_json::Serializer::with_formatter(file, formatter);
104+
map.serialize(&mut ser).unknown()?;
105+
}
106+
Ok(())
107+
}
32108
}
33109

34110
// unit tests
@@ -51,39 +127,21 @@ pub mod tests {
51127
#[test]
52128
fn gets_node_version() {
53129
let project_path = fixture_path("basic");
54-
let version = match Manifest::for_dir(&project_path) {
55-
Ok(manifest) => manifest.unwrap().node,
56-
_ => panic!(
57-
"Error: Could not get manifest for project {:?}",
58-
project_path
59-
),
60-
};
130+
let version = Manifest::for_dir(&project_path).expect("Could not get manifest").node().unwrap();
61131
assert_eq!(version, VersionReq::parse("=6.11.1").unwrap());
62132
}
63133

64134
#[test]
65135
fn gets_yarn_version() {
66136
let project_path = fixture_path("basic");
67-
let version = match Manifest::for_dir(&project_path) {
68-
Ok(manifest) => manifest.unwrap().yarn,
69-
_ => panic!(
70-
"Error: Could not get manifest for project {:?}",
71-
project_path
72-
),
73-
};
137+
let version = Manifest::for_dir(&project_path).expect("Could not get manifest").yarn();
74138
assert_eq!(version.unwrap(), VersionReq::parse("=1.2").unwrap());
75139
}
76140

77141
#[test]
78142
fn gets_dependencies() {
79143
let project_path = fixture_path("basic");
80-
let dependencies = match Manifest::for_dir(&project_path) {
81-
Ok(manifest) => manifest.unwrap().dependencies,
82-
_ => panic!(
83-
"Error: Could not get manifest for project {:?}",
84-
project_path
85-
),
86-
};
144+
let dependencies = Manifest::for_dir(&project_path).expect("Could not get manifest").dependencies;
87145
let mut expected_deps = HashMap::new();
88146
expected_deps.insert("@namespace/some-dep".to_string(), "0.2.4".to_string());
89147
expected_deps.insert("rsvp".to_string(), "^3.5.0".to_string());
@@ -93,13 +151,7 @@ pub mod tests {
93151
#[test]
94152
fn gets_dev_dependencies() {
95153
let project_path = fixture_path("basic");
96-
let dev_dependencies = match Manifest::for_dir(&project_path) {
97-
Ok(manifest) => manifest.unwrap().dev_dependencies,
98-
_ => panic!(
99-
"Error: Could not get manifest for project {:?}",
100-
project_path
101-
),
102-
};
154+
let dev_dependencies = Manifest::for_dir(&project_path).expect("Could not get manifest").dev_dependencies;
103155
let mut expected_deps = HashMap::new();
104156
expected_deps.insert(
105157
"@namespaced/something-else".to_string(),
@@ -108,4 +160,19 @@ pub mod tests {
108160
expected_deps.insert("eslint".to_string(), "~4.8.0".to_string());
109161
assert_eq!(dev_dependencies, expected_deps);
110162
}
163+
164+
#[test]
165+
fn node_for_no_toolchain() {
166+
let project_path = fixture_path("no_toolchain");
167+
let manifest = Manifest::for_dir(&project_path).expect("Could not get manifest");
168+
assert_eq!(manifest.node(), None);
169+
}
170+
171+
#[test]
172+
fn yarn_for_no_toolchain() {
173+
let project_path = fixture_path("no_toolchain");
174+
let manifest = Manifest::for_dir(&project_path).expect("Could not get manifest");
175+
assert_eq!(manifest.yarn(), None);
176+
}
177+
111178
}

0 commit comments

Comments
 (0)