Merge branch 'master' into wasm-opt

master
ashley williams 6 years ago committed by GitHub
commit 57bdb3f798
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .github/PULL_REQUEST_TEMPLATE.md
  2. 4
      .travis.yml
  3. 776
      Cargo.lock
  4. 6
      Cargo.toml
  5. 77
      README.md
  6. 2
      docs/src/SUMMARY.md
  7. 7
      docs/src/commands/index.md
  8. 50
      docs/src/commands/new.md
  9. 18
      docs/src/commands/test.md
  10. 13
      docs/src/quickstart.md
  11. 206
      src/bindgen.rs
  12. 2
      src/build/mod.rs
  13. 5
      src/child.rs
  14. 69
      src/command/build.rs
  15. 28
      src/command/generate.rs
  16. 39
      src/command/mod.rs
  17. 4
      src/command/pack.rs
  18. 8
      src/command/publish/mod.rs
  19. 111
      src/command/test.rs
  20. 27
      src/command/utils.rs
  21. 1
      src/emoji.rs
  22. 25
      src/generate.rs
  23. 24
      src/install/krate.rs
  24. 245
      src/install/mod.rs
  25. 43
      src/install/mode.rs
  26. 18
      src/install/tool.rs
  27. 3
      src/lib.rs
  28. 25
      src/main.rs
  29. 56
      src/manifest/mod.rs
  30. 2
      src/manifest/npm/esmodules.rs
  31. 16
      src/npm.rs
  32. 18
      src/test/webdriver.rs
  33. 65
      tests/all/build.rs
  34. 19
      tests/all/download.rs
  35. 21
      tests/all/generate.rs
  36. 3
      tests/all/main.rs
  37. 70
      tests/all/manifest.rs
  38. 33
      tests/all/utils/fixture.rs
  39. 8
      tests/all/utils/manifest.rs

@ -2,7 +2,7 @@ Make sure these boxes are checked! 📦✅
- [ ] You have the latest version of `rustfmt` installed
```bash
$ rustup component add rustfmt-preview --toolchain nightly
$ rustup component add rustfmt
```
- [ ] You ran `cargo fmt` on the code base before submitting
- [ ] You reference which issue is being closed in the PR text

@ -52,8 +52,8 @@ matrix:
- cargo fmt --version
- cargo fmt --all -- --check
- rustup component add clippy-preview
- cargo clippy --version
- cargo clippy
# - cargo clippy --version
# - cargo clippy
- name: Book
rust: stable

776
Cargo.lock generated

File diff suppressed because it is too large Load Diff

@ -11,7 +11,7 @@ documentation = "https://rustwasm.github.io/wasm-pack/"
[dependencies]
atty = "0.2.11"
cargo_metadata = "0.6.0"
cargo_metadata = "0.8.0"
console = "0.6.1"
dialoguer = "0.3.0"
curl = "0.4.13"
@ -23,6 +23,8 @@ glob = "0.2"
log = "0.4.6"
openssl = { version = '0.10.11', optional = true }
parking_lot = "0.6"
reqwest = "0.9.14"
semver = "0.9.0"
serde = "1.0.74"
serde_derive = "1.0.74"
serde_ignored = "0.0.4"
@ -37,7 +39,7 @@ walkdir = "2"
chrono = "0.4.6"
[dev-dependencies]
assert_cmd = "0.10.2"
assert_cmd = "0.11"
lazy_static = "1.1.0"
predicates = "1.0.0"
tempfile = "3"

@ -1,10 +1,29 @@
# 📦✨ wasm-pack
> Your favorite rust -> wasm workflow tool!
<div align="center">
[![Build Status](https://travis-ci.com/rustwasm/wasm-pack.svg?branch=master)](https://travis-ci.com/rustwasm/wasm-pack)
[![Build status](https://ci.appveyor.com/api/projects/status/iv1qtnqtv168ef8h?svg=true)](https://ci.appveyor.com/project/ashleygwilliams/wasm-pack-071k0)
[![crates.io](https://meritbadge.herokuapp.com/wasm-pack)](https://crates.io/crates/wasm-pack)
<h1>📦✨ wasm-pack</h1>
<p>
<strong>Your favorite Rust → Wasm workflow tool!</strong>
</p>
<p>
<a href="https://travis-ci.com/rustwasm/wasm-pack"><img alt="Build Status" src="https://travis-ci.com/rustwasm/wasm-pack.svg?branch=master"/></a>
<a href="https://ci.appveyor.com/project/ashleygwilliams/wasm-pack-071k0"><img alt="Build status" src="https://ci.appveyor.com/api/projects/status/iv1qtnqtv168ef8h?svg=true"/></a>
<a href="https://crates.io/crates/wasm-pack"><img alt="crates.io" src="https://meritbadge.herokuapp.com/wasm-pack"/></a>
</p>
<h3>
<a href="https://rustwasm.github.io/docs/wasm-pack/">Docs</a>
<span> | </span>
<a href="https://github.com/rustwasm/wasm-pack/blob/master/CONTRIBUTING.md">Contributing</a>
<span> | </span>
<a href="https://discordapp.com/channels/442252698964721669/443151097398296587">Chat</a>
</h3>
<sub>Built with 🦀🕸 by <a href="https://rustwasm.github.io/">The Rust and WebAssembly Working Group</a></sub>
</div>
## About
This tool seeks to be a one-stop shop for building and working with rust-
generated WebAssembly that you would like to interop with JavaScript, in the
@ -30,17 +49,23 @@ This project requires Rust 1.30.0 or later.
- [Development Environment](https://rustwasm.github.io/wasm-pack/book/prerequisites/index.html)
- [Installation](https://rustwasm.github.io/wasm-pack/installer)
- [Project Setup](https://rustwasm.github.io/wasm-pack/book/project-setup/index.html)
## ⚡ Quickstart Guide
Visit the [quickstart quide] in our documentation.
[quickstart guide]: https://rustwasm.github.io/wasm-pack/book/quickstart.html
## 🎙 Commands
- [`new`](https://rustwasm.github.io/wasm-pack/book/commands/new.html): Generate a new RustWasm project using a template
- [`build`](https://rustwasm.github.io/wasm-pack/book/commands/build.html): Generate an npm wasm pkg from a rustwasm crate
- [`test`](https://rustwasm.github.io/wasm-pack/book/commands/test.html): Run browser tests
- [`pack` and `publish`](https://rustwasm.github.io/wasm-pack/book/commands/pack-and-publish.html): Create a tarball of your rustwasm pkg and/or publish to a registry
## 📝 Logging
`wasm-pack` uses [`env_logger`] to produces logs when `wasm-pack` runs.
`wasm-pack` uses [`env_logger`] to produce logs when `wasm-pack` runs.
To configure your log level, use the `RUST_LOG` environment variable. For example:
@ -67,41 +92,3 @@ This project was started by [ashleygwilliams] and is co-maintained by [ashleygwi
[ashleygwilliams]: https://github.com/ashleygwilliams
[drager]: https://github.com/drager
[rustwasm Working Group]: https://github.com/rustwasm/team
## ⚡ Quickstart Guide
1. Write a crate in Rust.
2. Add `wasm-bindgen` to your `Cargo.toml`:
```toml
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
```
3. Add this to the top of your `src/lib.rs`:
```rust
use wasm_bindgen::prelude::*;
```
4. Annotate your public functions with `#[wasm_bindgen]`, for example:
```rust
#[wasm_bindgen]
extern {
pub fn alert(s: &str);
}
#[wasm_bindgen]
pub fn greet(name: &str) {
alert(&format!("Hello, {}!", name));
}
```
5. Install this tool: `cargo install wasm-pack`
6. Run `wasm-pack build`, optionally, pass a path to a dir or a scope (see above for details)
7. This tool generates files in a `pkg` dir
8. To publish to npm, run `wasm-pack publish`. You may need to login to the
registry you want to publish to. You can login using `wasm-pack login`.

@ -1,10 +1,12 @@
# Summary
- [Introduction](./introduction.md)
- [Quickstart](./quickstart.md)
- [Prerequisites](./prerequisites/index.md)
- [npm (optional)](./prerequisites/npm.md)
- [Non-`rustup` setups](./prerequisites/non-rustup-setups.md)
- [Commands](./commands/index.md)
- [`new`](./commands/new.md)
- [`build`](./commands/build.md)
- [`test`](./commands/test.md)
- [`pack` and `publish`](./commands/pack-and-publish.md)

@ -3,9 +3,14 @@
`wasm-pack` has several commands to help you during the process of building
a Rust-generated WebAssembly project.
- `init`: This command has been deprecated in favor of `build`.
- `new`: This command generates a new project for you using a template. [Learn more][generate]
- `build`: This command builds a `pkg` directory for you with compiled wasm and generated JS. [Learn more][build]
- `pack` and `publish`: These command will create a tarball, and optionally publish it to a registry, such as npm. [Learn more][pack-pub]
### Deprecated Commands
- `init`: This command has been deprecated in favor of `build`.
[build]: ./build.html
[new]: ./new.html
[pack-pub]: ./pack-and-publish.html

@ -0,0 +1,50 @@
# wasm-pack new
The `wasm-pack new` command creates a new RustWasm project for you,
using [`cargo-generate`] under the hood.
It takes 3 parameters, name, template, and mode:
```
wasm-pack new <name> --template <template> --mode <normal|noinstall|force>
```
The template will default to the `rustwasm/wasm-pack-template`.
## Name
The `wasm-pack new` command must be given a name argument, e.g.:
```
wasm-pack new myproject
```
## Template
The `wasm-pack new` command can be given an optional template argument, e.g.:
```
wasm-pack new myproject --template https://github.com/rustwasm/wasm-pack-template
```
The template can be an address to a git repo that contains a [`cargo-generate`]
template.
[`cargo-generate`]: https://github.com/ashleygwilliams/cargo-generate
## Mode
The `wasm-pack new` command can be given an optional mode argument, e.g.:
```
wasm-pack new myproject --mode noinstall
```
The mode pass can be either "normal", "noinstall", or "force". "normal is passed by
degault.
`noinstall` means that wasm-pack should not attempt to install any underlying tools.
If a necessary tool cannot be found, the command will error.
`force` means that wasm-pack should not check the local Rust version. If a local Rust
is an unacceptable Rust version, the command will error.

@ -67,14 +67,15 @@ $ tree crates/foo
   ├── lib.rs
└── tests
├── diff_patch.rs
   └── node.rs
```
```
# Run all tests in tests/diff_patch.rs
wasm-pack test crates/foo --firefox --headless -- --tests diff_patch
# Run all tests in tests/diff_patch.rs in Firefox
wasm-pack test crates/foo --firefox --headless -- --test diff_patch
# Run all tests in tests/diff_patch.rs that contain the word "replace"
wasm-pack test crates/foo --firefox --headless -- --tests diff_patch replace
wasm-pack test crates/foo --firefox --headless -- --test diff_patch replace
# Run all tests inside of a `tests` module inside of src/lib/diff.rs
wasm-pack test crates/foo --firefox --headless -- --lib diff::tests
@ -82,3 +83,14 @@ wasm-pack test crates/foo --firefox --headless -- --lib diff::tests
# Same as the above, but only if they contain the word replace
wasm-pack test crates/foo --firefox --headless -- --lib diff::tests::replace
```
Note that you can also filter tests by location in which they're supposed to
run. For example:
```
# Run all tests which are intended to execute in Node.js
wasm-pack test --node
# Run all tests which are intended to be executed in a browser
wasm-pack test --firefox --headless
```

@ -0,0 +1,13 @@
# Quickstart
1. Install `rust` using [`rustup`].
1. [Install this tool.]
1. Run `wasm-pack new hello-wasm`.
1. `cd hello-wasm`
1. Run `wasm-pack build`.
1. This tool generates files in a `pkg` dir
1. To publish to npm, run `wasm-pack publish`. You may need to login to the
registry you want to publish to. You can login using `wasm-pack login`.
[`rustup`]: https://rustup.rs/
[Install this tool.]: https://rustwasm.github.io/wasm-pack/installer/

@ -1,171 +1,14 @@
//! Functionality related to installing and running `wasm-bindgen`.
//! Functionality related to running `wasm-bindgen`.
use binary_install::{Cache, Download};
use binary_install::Download;
use child;
use command::build::{BuildProfile, Target};
use emoji;
use failure::{self, ResultExt};
use log::debug;
use log::{info, warn};
use install;
use manifest::CrateData;
use std::env;
use std::fs;
use semver;
use std::path::{Path, PathBuf};
use std::process::Command;
use target;
use which::which;
use PBAR;
/// Install the `wasm-bindgen` CLI.
///
/// Prefers an existing local install, if any exists. Then checks if there is a
/// global install on `$PATH` that fits the bill. Then attempts to download a
/// tarball from the GitHub releases page, if this target has prebuilt
/// binaries. Finally, falls back to `cargo install`.
pub fn install_wasm_bindgen(
cache: &Cache,
version: &str,
install_permitted: bool,
) -> Result<Download, failure::Error> {
// If `wasm-bindgen` is installed globally and it has the right version, use
// that. Assume that other tools are installed next to it.
//
// This situation can arise if `wasm-bindgen` is already installed via
// `cargo install`, for example.
if let Ok(path) = which("wasm-bindgen") {
debug!("found global wasm-bindgen binary at: {}", path.display());
if wasm_bindgen_version_check(&path, version) {
return Ok(Download::at(path.parent().unwrap()));
}
}
let msg = format!("{}Installing wasm-bindgen...", emoji::DOWN_ARROW);
PBAR.info(&msg);
let dl = download_prebuilt_wasm_bindgen(&cache, version, install_permitted);
match dl {
Ok(dl) => return Ok(dl),
Err(e) => {
warn!(
"could not download pre-built `wasm-bindgen`: {}. Falling back to `cargo install`.",
e
);
}
}
cargo_install_wasm_bindgen(&cache, version, install_permitted)
}
/// Downloads a precompiled copy of wasm-bindgen, if available.
pub fn download_prebuilt_wasm_bindgen(
cache: &Cache,
version: &str,
install_permitted: bool,
) -> Result<Download, failure::Error> {
let url = match prebuilt_url(version) {
Some(url) => url,
None => bail!("no prebuilt wasm-bindgen binaries are available for this platform"),
};
let binaries = &["wasm-bindgen", "wasm-bindgen-test-runner"];
match cache.download(install_permitted, "wasm-bindgen", binaries, &url)? {
Some(download) => Ok(download),
None => bail!("wasm-bindgen v{} is not installed!", version),
}
}
/// Returns the URL of a precompiled version of wasm-bindgen, if we have one
/// available for our host platform.
fn prebuilt_url(version: &str) -> Option<String> {
let target = if target::LINUX && target::x86_64 {
"x86_64-unknown-linux-musl"
} else if target::MACOS && target::x86_64 {
"x86_64-apple-darwin"
} else if target::WINDOWS && target::x86_64 {
"x86_64-pc-windows-msvc"
} else {
return None;
};
Some(format!(
"https://github.com/rustwasm/wasm-bindgen/releases/download/{0}/wasm-bindgen-{0}-{1}.tar.gz",
version,
target
))
}
/// Use `cargo install` to install the `wasm-bindgen` CLI locally into the given
/// crate.
pub fn cargo_install_wasm_bindgen(
cache: &Cache,
version: &str,
install_permitted: bool,
) -> Result<Download, failure::Error> {
debug!(
"Attempting to use a `cargo install`ed version of `wasm-bindgen={}`",
version
);
let dirname = format!("wasm-bindgen-cargo-install-{}", version);
let destination = cache.join(dirname.as_ref());
if destination.exists() {
debug!(
"`cargo install`ed `wasm-bindgen={}` already exists at {}",
version,
destination.display()
);
return Ok(Download::at(&destination));
}
if !install_permitted {
bail!("wasm-bindgen v{} is not installed!", version)
}
// Run `cargo install` to a temporary location to handle ctrl-c gracefully
// and ensure we don't accidentally use stale files in the future
let tmp = cache.join(format!(".{}", dirname).as_ref());
drop(fs::remove_dir_all(&tmp));
debug!(
"cargo installing wasm-bindgen to tempdir: {}",
tmp.display()
);
fs::create_dir_all(&tmp)
.context("failed to create temp dir for `cargo install wasm-bindgen`")?;
let mut cmd = Command::new("cargo");
cmd.arg("install")
.arg("--force")
.arg("wasm-bindgen-cli")
.arg("--version")
.arg(version)
.arg("--root")
.arg(&tmp);
child::run(cmd, "cargo install").context("Installing wasm-bindgen with cargo")?;
// `cargo install` will put the installed binaries in `$root/bin/*`, but we
// just want them in `$root/*` directly (which matches how the tarballs are
// laid out, and where the rest of our code expects them to be). So we do a
// little renaming here.
for f in ["wasm-bindgen", "wasm-bindgen-test-runner"].iter().cloned() {
let from = tmp
.join("bin")
.join(f)
.with_extension(env::consts::EXE_EXTENSION);
let to = tmp.join(from.file_name().unwrap());
fs::rename(&from, &to).with_context(|_| {
format!(
"failed to move {} to {} for `cargo install`ed `wasm-bindgen`",
from.display(),
to.display()
)
})?;
}
// Finally, move the `tmp` directory into our binary cache.
fs::rename(&tmp, &destination)?;
Ok(Download::at(&destination))
}
/// Run the `wasm-bindgen` CLI to generate bindings for the current crate's
/// `.wasm`.
@ -175,7 +18,7 @@ pub fn wasm_bindgen_build(
out_dir: &Path,
out_name: &Option<String>,
disable_dts: bool,
target: &Target,
target: Target,
profile: BuildProfile,
) -> Result<(), failure::Error> {
let release_or_debug = match profile {
@ -197,14 +40,21 @@ pub fn wasm_bindgen_build(
} else {
"--typescript"
};
let bindgen_path = bindgen.binary("wasm-bindgen")?;
let target_arg = match target {
Target::Nodejs => "--nodejs",
Target::NoModules => "--no-modules",
Target::Web => "--web",
Target::Web => {
if supports_web_target(&bindgen_path)? {
"--web"
} else {
bail!("Your current version of wasm-bindgen does not support the 'web' target. Please update your project to wasm-bindgen version >= 0.2.39.")
}
}
Target::Bundler => "--browser",
};
let bindgen_path = bindgen.binary("wasm-bindgen")?;
let mut cmd = Command::new(bindgen_path);
let mut cmd = Command::new(&bindgen_path);
cmd.arg(&wasm_path)
.arg("--out-dir")
.arg(out_dir)
@ -231,23 +81,11 @@ pub fn wasm_bindgen_build(
}
/// Check if the `wasm-bindgen` dependency is locally satisfied.
fn wasm_bindgen_version_check(bindgen_path: &PathBuf, dep_version: &str) -> bool {
let mut cmd = Command::new(bindgen_path);
cmd.arg("--version");
child::run_capture_stdout(cmd, "wasm-bindgen")
.map(|stdout| {
stdout
.trim()
.split_whitespace()
.nth(1)
.map(|v| {
info!(
"Checking installed `wasm-bindgen` version == expected version: {} == {}",
v, dep_version
);
v == dep_version
})
.unwrap_or(false)
})
.unwrap_or(false)
fn supports_web_target(cli_path: &PathBuf) -> Result<bool, failure::Error> {
let cli_version = semver::Version::parse(&install::get_cli_version(
&install::Tool::WasmBindgen,
cli_path,
)?)?;
let expected_version = semver::Version::parse("0.2.39")?;
Ok(cli_version >= expected_version)
}

@ -81,7 +81,7 @@ fn wasm_pack_local_version() -> Option<String> {
pub fn cargo_build_wasm(
path: &Path,
profile: BuildProfile,
extra_options: &Vec<String>,
extra_options: &[String],
) -> Result<(), Error> {
let msg = format!("{}Compiling to Wasm...", emoji::CYCLONE);
PBAR.info(&msg);

@ -4,6 +4,7 @@
//! properly logged and their output is logged as well.
use failure::Error;
use install::Tool;
use log::info;
use std::process::{Command, Stdio};
@ -23,7 +24,7 @@ pub fn new_command(program: &str) -> Command {
}
}
/// Run the given command and return its stdout.
/// Run the given command and return on success.
pub fn run(mut command: Command, command_name: &str) -> Result<(), Error> {
info!("Running {:?}", command);
@ -42,7 +43,7 @@ pub fn run(mut command: Command, command_name: &str) -> Result<(), Error> {
}
/// Run the given command and return its stdout.
pub fn run_capture_stdout(mut command: Command, command_name: &str) -> Result<String, Error> {
pub fn run_capture_stdout(mut command: Command, command_name: &Tool) -> Result<String, Error> {
info!("Running {:?}", command);
let output = command

@ -5,9 +5,10 @@ use binary_install::{Cache, Download};
use bindgen;
use build;
use cache;
use command::utils::{create_pkg_dir, set_crate_path};
use command::utils::{create_pkg_dir, get_crate_path};
use emoji;
use failure::Error;
use install::{self, InstallMode, Tool};
use license;
use lockfile::Lockfile;
use log::info;
@ -27,7 +28,7 @@ pub struct Build {
pub disable_dts: bool,
pub target: Target,
pub profile: BuildProfile,
pub mode: BuildMode,
pub mode: InstallMode,
pub out_dir: PathBuf,
pub out_name: Option<String>,
pub bindgen: Option<Download>,
@ -35,47 +36,6 @@ pub struct Build {
pub extra_options: Vec<String>,
}
/// The `BuildMode` determines which mode of initialization we are running, and
/// what build and install steps we perform.
#[derive(Clone, Copy, Debug)]
pub enum BuildMode {
/// Perform all the build and install steps.
Normal,
/// Don't install tools like `wasm-bindgen`, just use the global
/// environment's existing versions to do builds.
Noinstall,
/// Skip the rustc version check
Force,
}
impl BuildMode {
fn install_permitted(&self) -> bool {
match self {
BuildMode::Normal => true,
BuildMode::Force => true,
BuildMode::Noinstall => false,
}
}
}
impl Default for BuildMode {
fn default() -> BuildMode {
BuildMode::Normal
}
}
impl FromStr for BuildMode {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Error> {
match s {
"no-install" => Ok(BuildMode::Noinstall),
"normal" => Ok(BuildMode::Normal),
"force" => Ok(BuildMode::Force),
_ => bail!("Unknown build mode: {}", s),
}
}
}
/// What sort of output we're going to be generating and flags we're invoking
/// `wasm-bindgen` with.
#[derive(Clone, Copy, Debug)]
@ -129,7 +89,7 @@ pub enum BuildProfile {
/// Everything required to configure and run the `wasm-pack build` command.
#[derive(Debug, StructOpt)]
pub struct BuildOptions {
/// The path to the Rust crate.
/// The path to the Rust crate. If not set, searches up the path from the current directory.
#[structopt(parse(from_os_str))]
pub path: Option<PathBuf>,
@ -139,15 +99,15 @@ pub struct BuildOptions {
#[structopt(long = "mode", short = "m", default_value = "normal")]
/// Sets steps to be run. [possible values: no-install, normal, force]
pub mode: BuildMode,
pub mode: InstallMode,
#[structopt(long = "no-typescript")]
/// By default a *.d.ts file is generated for the generated JS file, but
/// this flag will disable generating this TypeScript file.
pub disable_dts: bool,
#[structopt(long = "target", short = "t", default_value = "browser")]
/// Sets the target environment. [possible values: browser, nodejs, web, no-modules]
#[structopt(long = "target", short = "t", default_value = "bundler")]
/// Sets the target environment. [possible values: bundler, nodejs, web, no-modules]
pub target: Target,
#[structopt(long = "debug")]
@ -185,7 +145,7 @@ impl Default for BuildOptions {
Self {
path: None,
scope: None,
mode: BuildMode::default(),
mode: InstallMode::default(),
disable_dts: false,
target: Target::default(),
debug: false,
@ -204,7 +164,7 @@ type BuildStep = fn(&mut Build) -> Result<(), Error>;
impl Build {
/// Construct a build command from the given options.
pub fn try_from_opts(build_opts: BuildOptions) -> Result<Self, Error> {
let crate_path = set_crate_path(build_opts.path)?;
let crate_path = get_crate_path(build_opts.path)?;
let crate_data = manifest::CrateData::new(&crate_path, build_opts.out_name.clone())?;
let out_dir = crate_path.join(PathBuf::from(build_opts.out_dir));
@ -266,7 +226,7 @@ impl Build {
Ok(())
}
fn get_process_steps(mode: BuildMode) -> Vec<(&'static str, BuildStep)> {
fn get_process_steps(mode: InstallMode) -> Vec<(&'static str, BuildStep)> {
macro_rules! steps {
($($name:ident),+) => {
{
@ -279,7 +239,7 @@ impl Build {
}
let mut steps = Vec::new();
match &mode {
BuildMode::Force => {}
InstallMode::Force => {}
_ => {
steps.extend(steps![
step_check_rustc_version,
@ -350,7 +310,7 @@ impl Build {
&self.out_dir,
&self.scope,
self.disable_dts,
&self.target,
self.target,
)?;
info!(
"Wrote a package.json at {:#?}.",
@ -378,7 +338,8 @@ impl Build {
let lockfile = Lockfile::new(&self.crate_data)?;
let bindgen_version = lockfile.require_wasm_bindgen()?;
info!("Installing wasm-bindgen-cli...");
let bindgen = bindgen::install_wasm_bindgen(
let bindgen = install::download_prebuilt_or_cargo_install(
Tool::WasmBindgen,
&self.cache,
&bindgen_version,
self.mode.install_permitted(),
@ -396,7 +357,7 @@ impl Build {
&self.out_dir,
&self.out_name,
self.disable_dts,
&self.target,
self.target,
self.profile,
)?;
info!("wasm bindings were built at {:#?}.", &self.out_dir);

@ -0,0 +1,28 @@
use cache;
use failure::Error;
use generate;
use install::{self, Tool};
use log::info;
use std::result;
use PBAR;
/// Executes the 'cargo-generate' command in the current directory
/// which generates a new rustwasm project from a template.
pub fn generate(
template: String,
name: String,
install_permitted: bool,
) -> result::Result<(), Error> {
info!("Generating a new rustwasm project...");
let download = install::download_prebuilt_or_cargo_install(
Tool::CargoGenerate,
&cache::get_wasm_pack_cache()?,
"latest",
install_permitted,
)?;
generate::generate(&template, &name, &download)?;
let msg = format!("🐑 Generated new project at /{}", name);
PBAR.info(&msg);
Ok(())
}

@ -1,6 +1,8 @@
//! CLI command structures, parsing, and execution.
#![allow(clippy::redundant_closure)]
pub mod build;
mod generate;
mod login;
mod pack;
/// Data structures and functions for publishing a package.
@ -9,10 +11,12 @@ pub mod test;
pub mod utils;
use self::build::{Build, BuildOptions};
use self::generate::generate;
use self::login::login;
use self::pack::pack;
use self::publish::{access::Access, publish};
use self::test::{Test, TestOptions};
use crate::install::InstallMode;
use failure::Error;
use log::info;
use std::path::PathBuf;
@ -28,23 +32,40 @@ pub enum Command {
#[structopt(name = "pack")]
/// 🍱 create a tar of your npm package but don't publish!
Pack {
/// The path to the Rust crate.
/// The path to the Rust crate. If not set, searches up the path from the current dirctory.
#[structopt(parse(from_os_str))]
path: Option<PathBuf>,
},
#[structopt(name = "new")]
/// 🐑 create a new project with a template
Generate {
/// The name of the project
name: String,
/// The URL to the template
#[structopt(
long = "template",
short = "temp",
default_value = "https://github.com/rustwasm/wasm-pack-template"
)]
template: String,
#[structopt(long = "mode", short = "m", default_value = "normal")]
/// Should we install or check the presence of binary tools. [possible values: no-install, normal, force]
mode: InstallMode,
},
#[structopt(name = "publish")]
/// 🎆 pack up your npm package and publish!
Publish {
#[structopt(long = "target", short = "t", default_value = "browser")]
/// Sets the target environment. [possible values: browser, nodejs, no-modules]
#[structopt(long = "target", short = "t", default_value = "bundler")]
/// Sets the target environment. [possible values: bundler, nodejs, web, no-modules]
target: String,
/// The access level for the package to be published
#[structopt(long = "access", short = "a")]
access: Option<Access>,
/// The path to the Rust crate.
/// The path to the Rust crate. If not set, searches up the path from the current dirctory.
#[structopt(parse(from_os_str))]
path: Option<PathBuf>,
},
@ -100,6 +121,16 @@ pub fn run_wasm_pack(command: Command) -> result::Result<(), Error> {
info!("Path: {:?}", &path);
pack(path)
}
Command::Generate {
template,
name,
mode,
} => {
info!("Running generate command...");
info!("Template: {:?}", &template);
info!("Name: {:?}", &name);
generate(template, name, mode.install_permitted())
}
Command::Publish {
target,
path,

@ -1,4 +1,4 @@
use command::utils::{find_pkg_directory, set_crate_path};
use command::utils::{find_pkg_directory, get_crate_path};
use failure::Error;
use log::info;
use npm;
@ -9,7 +9,7 @@ use PBAR;
/// Executes the 'npm pack' command on the 'pkg' directory
/// which creates a tarball that can be published to the NPM registry
pub fn pack(path: Option<PathBuf>) -> result::Result<(), Error> {
let crate_path = set_crate_path(path)?;
let crate_path = get_crate_path(path)?;
info!("Packing up the npm package...");
let pkg_directory = find_pkg_directory(&crate_path).ok_or_else(|| {

@ -3,7 +3,7 @@ pub mod access;
use self::access::Access;
use command::build::{Build, BuildOptions, Target};
use command::utils::{find_pkg_directory, set_crate_path};
use command::utils::{find_pkg_directory, get_crate_path};
use dialoguer::{Confirmation, Input, Select};
use failure::Error;
use log::info;
@ -20,7 +20,7 @@ pub fn publish(
path: Option<PathBuf>,
access: Option<Access>,
) -> result::Result<(), Error> {
let crate_path = set_crate_path(path)?;
let crate_path = get_crate_path(path)?;
info!("Publishing the npm package...");
info!("npm info located in the npm debug log");
@ -41,8 +41,8 @@ pub fn publish(
.interact()?;
let out_dir = format!("{}/pkg", out_dir);
let target = Select::new()
.with_prompt("target[default: browser]")
.items(&["browser", "nodejs", "no-modules"])
.with_prompt("target[default: bundler]")
.items(&["bundler", "nodejs", "web", "no-modules"])
.default(0)
.interact()?
.to_string();

@ -1,13 +1,12 @@
//! Implementation of the `wasm-pack test` command.
use super::build::BuildMode;
use binary_install::Cache;
use bindgen;
use build;
use cache;
use command::utils::set_crate_path;
use command::utils::get_crate_path;
use console::style;
use failure::Error;
use install::{self, InstallMode, Tool};
use lockfile::Lockfile;
use log::info;
use manifest;
@ -19,7 +18,7 @@ use test::{self, webdriver};
/// Everything required to configure the `wasm-pack test` command.
pub struct TestOptions {
#[structopt(parse(from_os_str))]
/// The path to the Rust crate.
/// The path to the Rust crate. If not set, searches up the path from the current dirctory.
pub path: Option<PathBuf>,
#[structopt(long = "node")]
@ -69,7 +68,7 @@ pub struct TestOptions {
#[structopt(long = "mode", short = "m", default_value = "normal")]
/// Sets steps to be run. [possible values: no-install, normal]
pub mode: BuildMode,
pub mode: InstallMode,
#[structopt(long = "release", short = "r")]
/// Build with the release profile.
@ -86,7 +85,7 @@ pub struct Test {
crate_data: manifest::CrateData,
cache: Cache,
node: bool,
mode: BuildMode,
mode: InstallMode,
firefox: bool,
geckodriver: Option<PathBuf>,
chrome: bool,
@ -119,7 +118,7 @@ impl Test {
extra_options,
} = test_opts;
let crate_path = set_crate_path(path)?;
let crate_path = get_crate_path(path)?;
let crate_data = manifest::CrateData::new(&crate_path, None)?;
let any_browser = chrome || firefox || safari;
@ -188,7 +187,7 @@ impl Test {
($($name:ident $(if $e:expr)* ,)*) => (steps![$($name $(if $e)* ),*])
}
match self.mode {
BuildMode::Normal => steps![
InstallMode::Normal => steps![
step_check_rustc_version,
step_check_for_wasm_target,
step_build_tests,
@ -201,7 +200,7 @@ impl Test {
step_get_safaridriver if self.safari && self.safaridriver.is_none(),
step_test_safari if self.safari,
],
BuildMode::Force => steps![
InstallMode::Force => steps![
step_check_for_wasm_target,
step_build_tests,
step_install_wasm_bindgen,
@ -213,7 +212,7 @@ impl Test {
step_get_safaridriver if self.safari && self.safaridriver.is_none(),
step_test_safari if self.safari,
],
BuildMode::Noinstall => steps![
InstallMode::Noinstall => steps![
step_build_tests,
step_install_wasm_bindgen,
step_test_node if self.node,
@ -269,22 +268,12 @@ impl Test {
)
}
let install_permitted = match self.mode {
BuildMode::Normal => {
info!("Ensuring wasm-bindgen-cli is installed...");
true
}
BuildMode::Force => {
info!("Ensuring wasm-bindgen-cli is installed...");
true
}
BuildMode::Noinstall => {
info!("Searching for existing wasm-bindgen-cli install...");
false
}
};
let dl = bindgen::install_wasm_bindgen(&self.cache, &bindgen_version, install_permitted)?;
let dl = install::download_prebuilt_or_cargo_install(
Tool::WasmBindgen,
&self.cache,
&bindgen_version,
self.mode.install_permitted(),
)?;
self.test_runner_path = Some(dl.binary("wasm-bindgen-test-runner")?);
@ -298,10 +287,13 @@ impl Test {
test::cargo_test_wasm(
&self.crate_path,
self.release,
Some((
"CARGO_TARGET_WASM32_UNKNOWN_UNKNOWN_RUNNER",
&self.test_runner_path.as_ref().unwrap(),
)),
vec![
(
"CARGO_TARGET_WASM32_UNKNOWN_UNKNOWN_RUNNER",
&**self.test_runner_path.as_ref().unwrap(),
),
("WASM_BINDGEN_TEST_ONLY_NODE", "1".as_ref()),
],
&self.extra_options,
)?;
info!("Finished running tests in node.");
@ -326,22 +318,8 @@ impl Test {
chromedriver
);
let test_runner = self
.test_runner_path
.as_ref()
.unwrap()
.display()
.to_string();
let test_runner = test_runner.as_str();
info!("Using wasm-bindgen test runner at {}", test_runner);
let mut envs = vec![
("CARGO_TARGET_WASM32_UNKNOWN_UNKNOWN_RUNNER", test_runner),
("CHROMEDRIVER", chromedriver),
];
if !self.headless {
envs.push(("NO_HEADLESS", "1"));
}
let mut envs = self.webdriver_env();
envs.push(("CHROMEDRIVER", chromedriver));
test::cargo_test_wasm(&self.crate_path, self.release, envs, &self.extra_options)?;
Ok(())
@ -365,22 +343,8 @@ impl Test {
geckodriver
);
let test_runner = self
.test_runner_path
.as_ref()
.unwrap()
.display()
.to_string();
let test_runner = test_runner.as_str();
info!("Using wasm-bindgen test runner at {}", test_runner);
let mut envs = vec![
("CARGO_TARGET_WASM32_UNKNOWN_UNKNOWN_RUNNER", test_runner),
("GECKODRIVER", geckodriver),
];
if !self.headless {
envs.push(("NO_HEADLESS", "1"));
}
let mut envs = self.webdriver_env();
envs.push(("GECKODRIVER", geckodriver));
test::cargo_test_wasm(&self.crate_path, self.release, envs, &self.extra_options)?;
Ok(())
@ -401,24 +365,23 @@ impl Test {
safaridriver
);
let test_runner = self
.test_runner_path
.as_ref()
.unwrap()
.display()
.to_string();
let test_runner = test_runner.as_str();
info!("Using wasm-bindgen test runner at {}", test_runner);
let mut envs = self.webdriver_env();
envs.push(("SAFARIDRIVER", safaridriver));
test::cargo_test_wasm(&self.crate_path, self.release, envs, &self.extra_options)?;
Ok(())
}
fn webdriver_env(&self) -> Vec<(&'static str, &str)> {
let test_runner = self.test_runner_path.as_ref().unwrap().to_str().unwrap();
info!("Using wasm-bindgen test runner at {}", test_runner);
let mut envs = vec![
("CARGO_TARGET_WASM32_UNKNOWN_UNKNOWN_RUNNER", test_runner),
("SAFARIDRIVER", safaridriver),
("WASM_BINDGEN_TEST_ONLY_WEB", "1"),
];
if !self.headless {
envs.push(("NO_HEADLESS", "1"));
}
test::cargo_test_wasm(&self.crate_path, self.release, envs, &self.extra_options)?;
Ok(())
envs
}
}

@ -1,4 +1,5 @@
//! Utility functions for commands.
#![allow(clippy::redundant_closure)]
use failure;
use std::fs;
@ -8,8 +9,30 @@ use walkdir::WalkDir;
/// If an explicit path is given, then use it, otherwise assume the current
/// directory is the crate path.
pub fn set_crate_path(path: Option<PathBuf>) -> Result<PathBuf, failure::Error> {
Ok(path.unwrap_or_else(|| PathBuf::from(".")))
pub fn get_crate_path(path: Option<PathBuf>) -> Result<PathBuf, failure::Error> {
match path {
Some(p) => Ok(p),
None => find_manifest_from_cwd(),
}
}
/// Search up the path for the manifest file from the current working directory
/// If we don't find the manifest file then return back the current working directory
/// to provide the appropriate error
fn find_manifest_from_cwd() -> Result<PathBuf, failure::Error> {
let mut parent_path = std::env::current_dir()?;
let mut manifest_path = parent_path.join("Cargo.toml");
loop {
if !manifest_path.is_file() {
if parent_path.pop() {
manifest_path = parent_path.join("Cargo.toml");
} else {
return Ok(PathBuf::from("."));
}
} else {
return Ok(parent_path.to_owned());
}
}
}
/// Construct our `pkg` directory in the crate.

@ -27,3 +27,4 @@ pub static ERROR: Emoji = Emoji("⛔ ", "");
pub static INFO: Emoji = Emoji("ℹ ", "");
pub static WRENCH: Emoji = Emoji("🔧 ", "");
pub static CRAB: Emoji = Emoji("🦀 ", "");
pub static SHEEP: Emoji = Emoji("🐑 ", "");

@ -0,0 +1,25 @@
//! Functionality related to running `cargo-generate`.
use binary_install::Download;
use child;
use emoji;
use failure::{self, ResultExt};
use std::process::Command;
/// Run `cargo generate` in the current directory to create a new
/// project from a template
pub fn generate(template: &str, name: &str, download: &Download) -> Result<(), failure::Error> {
let bin_path = download.binary("cargo-generate")?;
let mut cmd = Command::new(&bin_path);
cmd.arg("generate");
cmd.arg("--git").arg(&template);
cmd.arg("--name").arg(&name);
println!(
"{} Generating a new rustwasm project with name '{}'...",
emoji::SHEEP,
name
);
child::run(cmd, "cargo-generate").context("Running cargo-generate")?;
Ok(())
}

@ -0,0 +1,24 @@
use install::Tool;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
pub struct Krate {
pub max_version: String,
}
#[derive(Debug, Deserialize)]
pub struct KrateResponse {
#[serde(rename = "crate")]
pub krate: Krate,
}
impl Krate {
pub fn new(name: &Tool) -> Result<Krate, failure::Error> {
let krate_address = format!("https://crates.io/api/v1/crates/{}", name);
let client = reqwest::Client::new();
let mut res = client.get(&krate_address).send()?;
let kr: KrateResponse = serde_json::from_str(&res.text()?)?;
Ok(kr.krate)
}
}

@ -0,0 +1,245 @@
//! Functionality related to installing prebuilt binaries and/or running cargo install.
use self::krate::Krate;
use binary_install::{Cache, Download};
use child;
use emoji;
use failure::{self, ResultExt};
use log::debug;
use log::{info, warn};
use std::env;
use std::fs;
use std::path::PathBuf;
use std::process::Command;
use target;
use which::which;
use PBAR;
mod krate;
mod mode;
mod tool;
pub use self::mode::InstallMode;
pub use self::tool::Tool;
/// Install a cargo CLI tool
///
/// Prefers an existing local install, if any exists. Then checks if there is a
/// global install on `$PATH` that fits the bill. Then attempts to download a
/// tarball from the GitHub releases page, if this target has prebuilt
/// binaries. Finally, falls back to `cargo install`.
pub fn download_prebuilt_or_cargo_install(
tool: Tool,
cache: &Cache,
version: &str,
install_permitted: bool,
) -> Result<Download, failure::Error> {
// If the tool is installed globally and it has the right version, use
// that. Assume that other tools are installed next to it.
//
// This situation can arise if the tool is already installed via
// `cargo install`, for example.
if let Ok(path) = which(tool.to_string()) {
debug!("found global {} binary at: {}", tool, path.display());
if check_version(&tool, &path, version)? {
return Ok(Download::at(path.parent().unwrap()));
}
}
let msg = format!("{}Installing {}...", emoji::DOWN_ARROW, tool);
PBAR.info(&msg);
let dl = download_prebuilt(&tool, &cache, version, install_permitted);
match dl {
Ok(dl) => return Ok(dl),
Err(e) => {
warn!(
"could not download pre-built `{}`: {}. Falling back to `cargo install`.",
tool, e
);
}
}
cargo_install(tool, &cache, version, install_permitted)
}
/// Check if the tool dependency is locally satisfied.
pub fn check_version(
tool: &Tool,
path: &PathBuf,
expected_version: &str,
) -> Result<bool, failure::Error> {
let expected_version = if expected_version == "latest" {
let krate = Krate::new(tool)?;
krate.max_version
} else {
expected_version.to_string()
};
let v = get_cli_version(tool, path)?;
info!(
"Checking installed `{}` version == expected version: {} == {}",
tool, v, &expected_version
);
Ok(v == expected_version)
}
/// Fetches the version of a CLI tool
pub fn get_cli_version(tool: &Tool, path: &PathBuf) -> Result<String, failure::Error> {
let mut cmd = Command::new(path);
cmd.arg("--version");
let stdout = child::run_capture_stdout(cmd, tool)?;
let version = stdout.trim().split_whitespace().nth(1);
match version {
Some(v) => Ok(v.to_string()),
None => bail!("Something went wrong! We couldn't determine your version of the wasm-bindgen CLI. We were supposed to set that up for you, so it's likely not your fault! You should file an issue: https://github.com/rustwasm/wasm-pack/issues/new?template=bug_report.md.")
}
}
/// Downloads a precompiled copy of the tool, if available.
pub fn download_prebuilt(
tool: &Tool,
cache: &Cache,
version: &str,
install_permitted: bool,
) -> Result<Download, failure::Error> {
let url = match prebuilt_url(tool, version) {
Ok(url) => url,
Err(e) => bail!(
"no prebuilt {} binaries are available for this platform: {}",
tool,
e,
),
};
match tool {
Tool::WasmBindgen => {
let binaries = &["wasm-bindgen", "wasm-bindgen-test-runner"];
match cache.download(install_permitted, "wasm-bindgen", binaries, &url)? {
Some(download) => Ok(download),
None => bail!("wasm-bindgen v{} is not installed!", version),
}
}
Tool::CargoGenerate => {
let binaries = &["cargo-generate"];
match cache.download(install_permitted, "cargo-generate", binaries, &url)? {
Some(download) => Ok(download),
None => bail!("cargo-generate v{} is not installed!", version),
}
}
}
}
/// Returns the URL of a precompiled version of wasm-bindgen, if we have one
/// available for our host platform.
fn prebuilt_url(tool: &Tool, version: &str) -> Result<String, failure::Error> {
let target = if target::LINUX && target::x86_64 {
"x86_64-unknown-linux-musl"
} else if target::MACOS && target::x86_64 {
"x86_64-apple-darwin"
} else if target::WINDOWS && target::x86_64 {
"x86_64-pc-windows-msvc"
} else {
bail!("Unrecognized target!")
};
match tool {
Tool::WasmBindgen => {
Ok(format!(
"https://github.com/rustwasm/wasm-bindgen/releases/download/{0}/wasm-bindgen-{0}-{1}.tar.gz",
version,
target
))
},
Tool::CargoGenerate => {
Ok(format!(
"https://github.com/ashleygwilliams/cargo-generate/releases/download/v{0}/cargo-generate-v{0}-{1}.tar.gz",
Krate::new(&Tool::CargoGenerate)?.max_version,
target
))
}
}
}
/// Use `cargo install` to install the tool locally into the given
/// crate.
pub fn cargo_install(
tool: Tool,
cache: &Cache,
version: &str,
install_permitted: bool,
) -> Result<Download, failure::Error> {
debug!(
"Attempting to use a `cargo install`ed version of `{}={}`",
tool, version,
);
let dirname = format!("{}-cargo-install-{}", tool, version);
let destination = cache.join(dirname.as_ref());
if destination.exists() {
debug!(
"`cargo install`ed `{}={}` already exists at {}",
tool,
version,
destination.display()
);
return Ok(Download::at(&destination));
}
if !install_permitted {
bail!("{} v{} is not installed!", tool, version)
}
// Run `cargo install` to a temporary location to handle ctrl-c gracefully
// and ensure we don't accidentally use stale files in the future
let tmp = cache.join(format!(".{}", dirname).as_ref());
drop(fs::remove_dir_all(&tmp));
debug!("cargo installing {} to tempdir: {}", tool, tmp.display(),);
let context = format!("failed to create temp dir for `cargo install {}`", tool);
fs::create_dir_all(&tmp).context(context)?;
let crate_name = match tool {
Tool::WasmBindgen => "wasm-bindgen-cli".to_string(),
_ => tool.to_string(),
};
let mut cmd = Command::new("cargo");
cmd.arg("install")
.arg("--force")
.arg(crate_name)
.arg("--version")
.arg(version)
.arg("--root")
.arg(&tmp);
let context = format!("Installing {} with cargo", tool);
child::run(cmd, "cargo install").context(context)?;
// `cargo install` will put the installed binaries in `$root/bin/*`, but we
// just want them in `$root/*` directly (which matches how the tarballs are
// laid out, and where the rest of our code expects them to be). So we do a
// little renaming here.
let binaries = match tool {
Tool::WasmBindgen => vec!["wasm-bindgen", "wasm-bindgen-test-runner"],
Tool::CargoGenerate => vec!["cargo-genrate"],
};
for b in binaries.iter().cloned() {
let from = tmp
.join("bin")
.join(b)
.with_extension(env::consts::EXE_EXTENSION);
let to = tmp.join(from.file_name().unwrap());
fs::rename(&from, &to).with_context(|_| {
format!(
"failed to move {} to {} for `cargo install`ed `{}`",
from.display(),
to.display(),
b
)
})?;
}
// Finally, move the `tmp` directory into our binary cache.
fs::rename(&tmp, &destination)?;
Ok(Download::at(&destination))
}

@ -0,0 +1,43 @@
use std::str::FromStr;
/// The `InstallMode` determines which mode of initialization we are running, and
/// what install steps we perform.
#[derive(Clone, Copy, Debug)]
pub enum InstallMode {
/// Perform all the install steps.
Normal,
/// Don't install tools like `wasm-bindgen`, just use the global
/// environment's existing versions to do builds.
Noinstall,
/// Skip the rustc version check
Force,
}
impl Default for InstallMode {
fn default() -> InstallMode {
InstallMode::Normal
}
}
impl FromStr for InstallMode {
type Err = failure::Error;
fn from_str(s: &str) -> Result<Self, failure::Error> {
match s {
"no-install" => Ok(InstallMode::Noinstall),
"normal" => Ok(InstallMode::Normal),
"force" => Ok(InstallMode::Force),
_ => bail!("Unknown build mode: {}", s),
}
}
}
impl InstallMode {
/// Determines if installation is permitted during a function call based on --mode flag
pub fn install_permitted(self) -> bool {
match self {
InstallMode::Normal => true,
InstallMode::Force => true,
InstallMode::Noinstall => false,
}
}
}

@ -0,0 +1,18 @@
use std::fmt;
/// Represents the set of CLI tools wasm-pack uses
pub enum Tool {
/// cargo-generate CLI tool
CargoGenerate,
/// wasm-bindgen CLI tools
WasmBindgen,
}
impl fmt::Display for Tool {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Tool::CargoGenerate => write!(f, "cargo-generate"),
Tool::WasmBindgen => write!(f, "wasm-bindgen"),
}
}
}

@ -9,6 +9,7 @@ extern crate strsim;
extern crate failure;
extern crate glob;
extern crate parking_lot;
extern crate semver;
extern crate serde;
extern crate which;
#[macro_use]
@ -31,6 +32,8 @@ pub mod cache;
pub mod child;
pub mod command;
pub mod emoji;
pub mod generate;
pub mod install;
pub mod license;
pub mod lockfile;
pub mod manifest;

@ -1,3 +1,5 @@
#![allow(clippy::redundant_closure, clippy::redundant_pattern_matching)]
extern crate atty;
extern crate env_logger;
#[macro_use]
@ -94,18 +96,15 @@ fn setup_panic_hooks() {
let default_hook = panic::take_hook();
match env::var("RUST_BACKTRACE") {
Err(_) => {
panic::set_hook(Box::new(move |info: &panic::PanicInfo| {
// First call the default hook that prints to standard error.
default_hook(info);
// Then call human_panic.
let file_path = human_panic::handle_dump(&meta, info);
human_panic::print_msg(file_path, &meta)
.expect("human-panic: printing error message to console failed");
}));
}
Ok(_) => {}
if let Err(_) = env::var("RUST_BACKTRACE") {
panic::set_hook(Box::new(move |info: &panic::PanicInfo| {
// First call the default hook that prints to standard error.
default_hook(info);
// Then call human_panic.
let file_path = human_panic::handle_dump(&meta, info);
human_panic::print_msg(file_path, &meta)
.expect("human-panic: printing error message to console failed");
}));
}
}

@ -1,6 +1,10 @@
//! Reading and writing Cargo.toml and package.json manifests.
#![allow(clippy::new_ret_no_self, clippy::needless_pass_by_value)]
#![allow(
clippy::new_ret_no_self,
clippy::needless_pass_by_value,
clippy::redundant_closure
)]
mod npm;
@ -26,6 +30,8 @@ use toml;
use PBAR;
const WASM_PACK_METADATA_KEY: &str = "package.metadata.wasm-pack";
const WASM_PACK_VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION");
const WASM_PACK_REPO_URL: &str = "https://github.com/rustwasm/wasm-pack";
/// Store for metadata learned about a crate
pub struct CrateData {
@ -175,7 +181,7 @@ impl Crate {
fn override_stamp_file(
current_time: DateTime<offset::Local>,
version: &String,
version: &str,
) -> Result<(), failure::Error> {
let path = env::current_exe()?;
@ -212,20 +218,23 @@ impl Crate {
}
/// Read the stamp file and return value assigned to a certain key.
fn return_stamp_file_value(file: &String, word: &str) -> Option<String> {
fn return_stamp_file_value(file: &str, word: &str) -> Option<String> {
let created = file
.lines()
.find(|line| line.starts_with(word))
.and_then(|l| l.split_whitespace().nth(1));
let value = created.map(|s| s.to_string());
value
created.map(|s| s.to_string())
}
/// Call to the crates.io api and return the latest version of `wasm-pack`
fn check_wasm_pack_latest_version() -> Result<Crate, Error> {
let mut easy = easy::Easy2::new(Collector(Vec::new()));
easy.useragent(&format!(
"wasm-pack/{} ({})",
WASM_PACK_VERSION.unwrap_or_else(|| "unknown"),
WASM_PACK_REPO_URL
))?;
easy.get(true)?;
easy.url("https://crates.io/api/v1/crates/wasm-pack")?;
easy.perform()?;
@ -376,8 +385,9 @@ impl CrateData {
)
}
let data =
cargo_metadata::metadata(Some(&manifest_path)).map_err(error_chain_to_failure)?;
let data = cargo_metadata::MetadataCommand::new()
.manifest_path(&manifest_path)
.exec()?;
let manifest_and_keys = CrateData::parse_crate_data(&manifest_path)?;
CrateData::warn_for_unused_keys(&manifest_and_keys);
@ -389,24 +399,12 @@ impl CrateData {
.position(|pkg| pkg.name == manifest.package.name)
.ok_or_else(|| format_err!("failed to find package in metadata"))?;
return Ok(CrateData {
Ok(CrateData {
data,
manifest,
current_idx,
out_name,
});
fn error_chain_to_failure(err: cargo_metadata::Error) -> Error {
let errors = err.iter().collect::<Vec<_>>();
let mut err: Error = match errors.last() {
Some(e) => format_err!("{}", e),
None => return format_err!("{}", err),
};
for e in errors[..errors.len() - 1].iter().rev() {
err = err.context(e.to_string()).into();
}
err
}
})
}
/// Read the `manifest_path` file and deserializes it using the toml Deserializer.
@ -534,7 +532,7 @@ impl CrateData {
out_dir: &Path,
scope: &Option<String>,
disable_dts: bool,
target: &Target,
target: Target,
) -> Result<(), Error> {
let pkg_file_path = out_dir.join("package.json");
let npm_data = match target {
@ -622,7 +620,7 @@ impl CrateData {
name: data.name,
collaborators: pkg.authors.clone(),
description: self.manifest.package.description.clone(),
version: pkg.version.clone(),
version: pkg.version.to_string(),
license: self.license(),
repository: self
.manifest
@ -655,7 +653,7 @@ impl CrateData {
name: data.name,
collaborators: pkg.authors.clone(),
description: self.manifest.package.description.clone(),
version: pkg.version.clone(),
version: pkg.version.to_string(),
license: self.license(),
repository: self
.manifest
@ -670,7 +668,7 @@ impl CrateData {
module: data.main,
homepage: data.homepage,
types: data.dts_file,
side_effects: "false".to_string(),
side_effects: false,
})
}
@ -684,7 +682,7 @@ impl CrateData {
name: data.name,
collaborators: pkg.authors.clone(),
description: self.manifest.package.description.clone(),
version: pkg.version.clone(),
version: pkg.version.to_string(),
license: self.license(),
repository: self
.manifest
@ -699,7 +697,7 @@ impl CrateData {
module: data.main,
homepage: data.homepage,
types: data.dts_file,
side_effects: "false".to_string(),
side_effects: false,
})
}
@ -718,7 +716,7 @@ impl CrateData {
name: data.name,
collaborators: pkg.authors.clone(),
description: self.manifest.package.description.clone(),
version: pkg.version.clone(),
version: pkg.version.to_string(),
license: self.license(),
repository: self
.manifest

@ -20,5 +20,5 @@ pub struct ESModulesPackage {
#[serde(skip_serializing_if = "Option::is_none")]
pub types: Option<String>,
#[serde(rename = "sideEffects")]
pub side_effects: String,
pub side_effects: bool,
}

@ -20,10 +20,7 @@ pub fn npm_pack(path: &str) -> Result<(), failure::Error> {
pub fn npm_publish(path: &str, access: Option<Access>) -> Result<(), failure::Error> {
let mut cmd = child::new_command("npm");
match access {
Some(a) => cmd
.current_dir(path)
.arg("publish")
.arg(&format!("{}", a.to_string())),
Some(a) => cmd.current_dir(path).arg("publish").arg(&a.to_string()),
None => cmd.current_dir(path).arg("publish"),
};
@ -38,14 +35,14 @@ pub fn npm_login(
always_auth: bool,
auth_type: &Option<String>,
) -> Result<(), failure::Error> {
let mut args = vec![format!("login"), format!("--registry={}", registry)];
let mut args = vec!["login".to_string(), format!("--registry={}", registry)];
if let Some(scope) = scope {
args.push(format!("--scope={}", scope));
}
if always_auth {
args.push(format!("--always_auth"));
args.push("--always_auth".to_string());
}
if let Some(auth_type) = auth_type {
@ -58,8 +55,9 @@ pub fn npm_login(
cmd.args(args);
info!("Running {:?}", cmd);
match cmd.status()?.success() {
true => Ok(()),
false => bail!("Login to registry {} failed", registry),
if cmd.status()?.success() {
Ok(())
} else {
bail!("Login to registry {} failed", registry)
}
}

@ -1,8 +1,8 @@
//! Getting WebDriver client binaries.
use binary_install::Cache;
use command::build::BuildMode;
use failure;
use install::InstallMode;
use std::path::PathBuf;
use target;
use PBAR;
@ -29,16 +29,12 @@ fn get_and_notify(
/// binary is found.
pub fn get_or_install_chromedriver(
cache: &Cache,
mode: BuildMode,
mode: InstallMode,
) -> Result<PathBuf, failure::Error> {
if let Ok(path) = which::which("chromedriver") {
return Ok(path);
}
let installation_allowed = match mode {
BuildMode::Noinstall => false,
_ => true,
};
install_chromedriver(cache, installation_allowed)
install_chromedriver(cache, mode.install_permitted())
}
/// Download and install a pre-built `chromedriver` binary.
@ -74,16 +70,12 @@ pub fn install_chromedriver(
/// binary is found.
pub fn get_or_install_geckodriver(
cache: &Cache,
mode: BuildMode,
mode: InstallMode,
) -> Result<PathBuf, failure::Error> {
if let Ok(path) = which::which("geckodriver") {
return Ok(path);
}
let installation_allowed = match mode {
BuildMode::Noinstall => false,
_ => true,
};
install_geckodriver(cache, installation_allowed)
install_geckodriver(cache, mode.install_permitted())
}
/// Download and install a pre-built `geckodriver` binary.

@ -9,6 +9,7 @@ fn build_in_non_crate_directory_doesnt_panic() {
fixture
.wasm_pack()
.arg("build")
.arg(".")
.assert()
.failure()
.stderr(predicates::str::contains("missing a `Cargo.toml`"));
@ -103,6 +104,54 @@ fn renamed_crate_name_works() {
fixture.wasm_pack().arg("build").assert().success();
}
#[test]
fn dash_dash_web_target_has_error_on_old_bindgen() {
let fixture = utils::fixture::Fixture::new();
fixture
.readme()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
authors = []
[lib]
crate-type = ["cdylib"]
name = 'bar'
[dependencies]
wasm-bindgen = "=0.2.37"
"#,
)
.file(
"src/lib.rs",
r#"
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn one() -> u32 { 1 }
"#,
)
.install_local_wasm_bindgen();
let cmd = fixture
.wasm_pack()
.arg("build")
.arg("--target")
.arg("web")
.assert()
.failure();
let output = String::from_utf8(cmd.get_output().stderr.clone()).unwrap();
assert!(
output.contains("0.2.39"),
"Output did not contain '0.2.39', output was {}",
output
);
}
#[test]
fn it_should_build_nested_project_with_transitive_dependencies() {
let fixture = utils::fixture::transitive_dependencies();
@ -174,11 +223,15 @@ fn build_with_and_without_wasm_bindgen_debug() {
#[wasm_bindgen]
impl MyThing {
#[wasm_bindgen(constructor)]
pub fn new() -> MyThing {
MyThing {}
}
}
pub fn take(self) {}
#[wasm_bindgen]
pub fn take(foo: MyThing) {
drop(foo);
}
"#,
)
@ -192,10 +245,14 @@ fn build_with_and_without_wasm_bindgen_debug() {
.success();
let contents = fs::read_to_string(fixture.path.join("pkg/whatever.js")).unwrap();
let contains_move_assertions =
contents.contains("throw new Error('Attempt to use a moved value')");
assert_eq!(
contents.contains("throw new Error('Attempt to use a moved value')"),
debug,
"Should only contain moved value assertions when debug assertions are enabled"
contains_move_assertions, debug,
"Should contain moved value assertions iff debug assertions are enabled. \
Contains move assertions? {}. \
Is a debug JS glue build? {}.",
contains_move_assertions, debug,
);
}
}

@ -1,6 +1,6 @@
use binary_install::Cache;
use tempfile;
use wasm_pack::bindgen;
use wasm_pack::install::{self, Tool};
#[test]
#[cfg(any(
@ -11,7 +11,7 @@ use wasm_pack::bindgen;
fn can_download_prebuilt_wasm_bindgen() {
let dir = tempfile::TempDir::new().unwrap();
let cache = Cache::at(dir.path());
let dl = bindgen::download_prebuilt_wasm_bindgen(&cache, "0.2.37", true).unwrap();
let dl = install::download_prebuilt(&Tool::WasmBindgen, &cache, "0.2.37", true).unwrap();
assert!(dl.binary("wasm-bindgen").unwrap().is_file());
assert!(dl.binary("wasm-bindgen-test-runner").unwrap().is_file())
}
@ -26,7 +26,7 @@ fn downloading_prebuilt_wasm_bindgen_handles_http_errors() {
let dir = tempfile::TempDir::new().unwrap();
let bad_version = "0.2.37-some-trailing-version-stuff-that-does-not-exist";
let cache = Cache::at(dir.path());
let result = bindgen::download_prebuilt_wasm_bindgen(&cache, bad_version, true);
let result = install::download_prebuilt(&Tool::WasmBindgen, &cache, bad_version, true);
assert!(result.is_err());
let error = result.err().unwrap();
@ -35,3 +35,16 @@ fn downloading_prebuilt_wasm_bindgen_handles_http_errors() {
.iter_chain()
.any(|e| e.to_string().contains(bad_version)));
}
#[test]
#[cfg(any(
all(target_os = "linux", target_arch = "x86_64"),
all(target_os = "macos", target_arch = "x86_64"),
all(windows, target_arch = "x86_64"),
))]
fn can_download_prebuilt_cargo_generate() {
let dir = tempfile::TempDir::new().unwrap();
let cache = Cache::at(dir.path());
let dl = install::download_prebuilt(&Tool::CargoGenerate, &cache, "latest", true).unwrap();
assert!(dl.binary("cargo-generate").unwrap().is_file());
}

@ -0,0 +1,21 @@
use assert_cmd::prelude::*;
use utils;
#[test]
fn new_with_no_name_errors() {
let fixture = utils::fixture::not_a_crate();
fixture.install_local_cargo_generate();
fixture.wasm_pack().arg("new").assert().failure();
}
#[test]
fn new_with_name_succeeds() {
let fixture = utils::fixture::not_a_crate();
fixture.install_local_cargo_generate();
fixture
.wasm_pack()
.arg("new")
.arg("hello")
.assert()
.success();
}

@ -11,8 +11,9 @@ extern crate structopt;
extern crate tempfile;
extern crate wasm_pack;
mod bindgen;
mod build;
mod download;
mod generate;
mod license;
mod lockfile;
mod manifest;

@ -4,6 +4,7 @@ use std::fs;
use std::path::PathBuf;
use utils::{self, fixture};
use wasm_pack::command::build::Target;
use wasm_pack::command::utils::get_crate_path;
use wasm_pack::{self, license, manifest};
#[test]
@ -78,7 +79,7 @@ fn it_creates_a_package_json_default_path() {
let crate_data = manifest::CrateData::new(&fixture.path, None).unwrap();
wasm_pack::command::utils::create_pkg_dir(&out_dir).unwrap();
assert!(crate_data
.write_package_json(&out_dir, &None, false, &Target::Bundler)
.write_package_json(&out_dir, &None, false, Target::Bundler)
.is_ok());
let package_json_path = &fixture.path.join("pkg").join("package.json");
fs::metadata(package_json_path).unwrap();
@ -92,7 +93,7 @@ fn it_creates_a_package_json_default_path() {
);
assert_eq!(pkg.module, "js_hello_world.js");
assert_eq!(pkg.types, "js_hello_world.d.ts");
assert_eq!(pkg.side_effects, "false");
assert_eq!(pkg.side_effects, false);
let actual_files: HashSet<String> = pkg.files.into_iter().collect();
let expected_files: HashSet<String> = [
@ -113,7 +114,7 @@ fn it_creates_a_package_json_provided_path() {
let crate_data = manifest::CrateData::new(&fixture.path, None).unwrap();
wasm_pack::command::utils::create_pkg_dir(&out_dir).unwrap();
assert!(crate_data
.write_package_json(&out_dir, &None, false, &Target::Bundler)
.write_package_json(&out_dir, &None, false, Target::Bundler)
.is_ok());
let package_json_path = &fixture.path.join("pkg").join("package.json");
fs::metadata(package_json_path).unwrap();
@ -141,7 +142,7 @@ fn it_creates_a_package_json_provided_path_with_scope() {
let crate_data = manifest::CrateData::new(&fixture.path, None).unwrap();
wasm_pack::command::utils::create_pkg_dir(&out_dir).unwrap();
assert!(crate_data
.write_package_json(&out_dir, &Some("test".to_string()), false, &Target::Bundler,)
.write_package_json(&out_dir, &Some("test".to_string()), false, Target::Bundler,)
.is_ok());
let package_json_path = &fixture.path.join("pkg").join("package.json");
fs::metadata(package_json_path).unwrap();
@ -169,7 +170,7 @@ fn it_creates_a_pkg_json_with_correct_files_on_node() {
let crate_data = manifest::CrateData::new(&fixture.path, None).unwrap();
wasm_pack::command::utils::create_pkg_dir(&out_dir).unwrap();
assert!(crate_data
.write_package_json(&out_dir, &None, false, &Target::Nodejs)
.write_package_json(&out_dir, &None, false, Target::Nodejs)
.is_ok());
let package_json_path = &out_dir.join("package.json");
fs::metadata(package_json_path).unwrap();
@ -204,7 +205,7 @@ fn it_creates_a_pkg_json_with_correct_files_on_nomodules() {
let crate_data = manifest::CrateData::new(&fixture.path, None).unwrap();
wasm_pack::command::utils::create_pkg_dir(&out_dir).unwrap();
assert!(crate_data
.write_package_json(&out_dir, &None, false, &Target::NoModules)
.write_package_json(&out_dir, &None, false, Target::NoModules)
.is_ok());
let package_json_path = &out_dir.join("package.json");
fs::metadata(package_json_path).unwrap();
@ -238,7 +239,7 @@ fn it_creates_a_package_json_with_correct_files_when_out_name_is_provided() {
let crate_data = manifest::CrateData::new(&fixture.path, Some("index".to_owned())).unwrap();
wasm_pack::command::utils::create_pkg_dir(&out_dir).unwrap();
assert!(crate_data
.write_package_json(&out_dir, &None, false, &Target::Bundler)
.write_package_json(&out_dir, &None, false, Target::Bundler)
.is_ok());
let package_json_path = &fixture.path.join("pkg").join("package.json");
fs::metadata(package_json_path).unwrap();
@ -252,7 +253,7 @@ fn it_creates_a_package_json_with_correct_files_when_out_name_is_provided() {
);
assert_eq!(pkg.module, "index.js");
assert_eq!(pkg.types, "index.d.ts");
assert_eq!(pkg.side_effects, "false");
assert_eq!(pkg.side_effects, false);
let actual_files: HashSet<String> = pkg.files.into_iter().collect();
let expected_files: HashSet<String> = ["index_bg.wasm", "index.d.ts", "index.js"]
@ -269,7 +270,7 @@ fn it_creates_a_pkg_json_in_out_dir() {
let crate_data = manifest::CrateData::new(&fixture.path, None).unwrap();
wasm_pack::command::utils::create_pkg_dir(&out_dir).unwrap();
assert!(crate_data
.write_package_json(&out_dir, &None, false, &Target::Bundler)
.write_package_json(&out_dir, &None, false, Target::Bundler)
.is_ok());
let package_json_path = &fixture.path.join(&out_dir).join("package.json");
@ -284,7 +285,7 @@ fn it_creates_a_package_json_with_correct_keys_when_types_are_skipped() {
let crate_data = manifest::CrateData::new(&fixture.path, None).unwrap();
wasm_pack::command::utils::create_pkg_dir(&out_dir).unwrap();
assert!(crate_data
.write_package_json(&out_dir, &None, true, &Target::Bundler)
.write_package_json(&out_dir, &None, true, Target::Bundler)
.is_ok());
let package_json_path = &out_dir.join("package.json");
fs::metadata(package_json_path).unwrap();
@ -345,7 +346,7 @@ fn it_sets_homepage_field_if_available_in_cargo_toml() {
wasm_pack::command::utils::create_pkg_dir(&out_dir).unwrap();
crate_data
.write_package_json(&out_dir, &None, true, &Target::Bundler)
.write_package_json(&out_dir, &None, true, Target::Bundler)
.unwrap();
let pkg = utils::manifest::read_package_json(&fixture.path, &out_dir).unwrap();
@ -361,7 +362,7 @@ fn it_sets_homepage_field_if_available_in_cargo_toml() {
wasm_pack::command::utils::create_pkg_dir(&out_dir).unwrap();
crate_data
.write_package_json(&out_dir, &None, true, &Target::Bundler)
.write_package_json(&out_dir, &None, true, Target::Bundler)
.unwrap();
let pkg = utils::manifest::read_package_json(&fixture.path, &out_dir).unwrap();
@ -462,7 +463,7 @@ fn it_lists_license_files_in_files_field_of_package_json() {
wasm_pack::command::utils::create_pkg_dir(&out_dir).unwrap();
license::copy_from_crate(&crate_data, &fixture.path, &out_dir).unwrap();
crate_data
.write_package_json(&out_dir, &None, false, &Target::Bundler)
.write_package_json(&out_dir, &None, false, Target::Bundler)
.unwrap();
let package_json_path = &fixture.path.join("pkg").join("package.json");
@ -481,3 +482,46 @@ fn it_lists_license_files_in_files_field_of_package_json() {
pkg.files,
);
}
#[test]
fn it_recurses_up_the_path_to_find_cargo_toml() {
let fixture = utils::fixture::Fixture::new();
fixture.hello_world_src_lib().file(
"Cargo.toml",
r#"
[package]
authors = ["The wasm-pack developers"]
description = "so awesome rust+wasm package"
license = "WTFPL"
name = "recurse-for-manifest-test"
repository = "https://github.com/rustwasm/wasm-pack.git"
version = "0.1.0"
homepage = "https://rustwasm.github.io/wasm-pack/"
"#,
);
let path = get_crate_path(None).unwrap();
let crate_data = manifest::CrateData::new(&path, None).unwrap();
let name = crate_data.crate_name();
assert_eq!(name, "wasm_pack");
}
#[test]
fn it_doesnt_recurse_up_the_path_to_find_cargo_toml_when_default() {
let fixture = utils::fixture::Fixture::new();
fixture.hello_world_src_lib().file(
"Cargo.toml",
r#"
[package]
authors = ["The wasm-pack developers"]
description = "so awesome rust+wasm package"
license = "WTFPL"
name = "recurse-for-manifest-test"
repository = "https://github.com/rustwasm/wasm-pack.git"
version = "0.1.0"
homepage = "https://rustwasm.github.io/wasm-pack/"
"#,
);
let path = get_crate_path(Some(PathBuf::from("src"))).unwrap();
let crate_data = manifest::CrateData::new(&path, None);
assert!(crate_data.is_err());
}

@ -8,6 +8,7 @@ use std::sync::{MutexGuard, Once, ONCE_INIT};
use std::thread;
use tempfile::TempDir;
use wasm_pack;
use wasm_pack::install::{self, Tool};
/// A test fixture in a temporary directory.
pub struct Fixture {
@ -224,12 +225,12 @@ impl Fixture {
let download = || {
if let Ok(download) =
wasm_pack::bindgen::download_prebuilt_wasm_bindgen(&cache, version, true)
install::download_prebuilt(&Tool::WasmBindgen, &cache, version, true)
{
return Ok(download);
}
wasm_pack::bindgen::cargo_install_wasm_bindgen(&cache, version, true)
install::cargo_install(Tool::WasmBindgen, &cache, version, true)
};
// Only one thread can perform the actual download, and then afterwards
@ -248,6 +249,32 @@ impl Fixture {
wasm_pack::wasm_opt::find_wasm_opt(&cache, true).unwrap();
});
}
/// Install a local cargo-generate for this fixture.
///
/// Takes care not to re-install for every fixture, but only the one time
/// for the whole test suite.
pub fn install_local_cargo_generate(&self) -> PathBuf {
static INSTALL_CARGO_GENERATE: Once = ONCE_INIT;
let cache = self.cache();
let download = || {
if let Ok(download) =
install::download_prebuilt(&Tool::CargoGenerate, &cache, "latest", true)
{
return Ok(download);
}
install::cargo_install(Tool::CargoGenerate, &cache, "latest", true)
};
// Only one thread can perform the actual download, and then afterwards
// everything will hit the cache so we can run the same path.
INSTALL_CARGO_GENERATE.call_once(|| {
download().unwrap();
});
download().unwrap().binary("cargo-generate").unwrap()
}
/// Download `geckodriver` and return its path.
///
@ -310,7 +337,7 @@ impl Fixture {
/// directory and using the test cache.
pub fn wasm_pack(&self) -> Command {
use assert_cmd::prelude::*;
let mut cmd = Command::main_binary().unwrap();
let mut cmd = Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap();
cmd.current_dir(&self.path);
cmd.env("WASM_PACK_CACHE", self.cache_dir());
cmd

@ -21,8 +21,8 @@ pub struct NpmPackage {
pub browser: String,
#[serde(default = "default_none")]
pub types: String,
#[serde(default = "default_none", rename = "sideEffects")]
pub side_effects: String,
#[serde(default = "default_false", rename = "sideEffects")]
pub side_effects: bool,
pub homepage: Option<String>,
}
@ -30,6 +30,10 @@ fn default_none() -> String {
"".to_string()
}
fn default_false() -> bool {
false
}
#[derive(Deserialize)]
pub struct Repository {
#[serde(rename = "type")]

Loading…
Cancel
Save