From 69e5af8fe2da4cbea533f9a500817faed550232d Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 12 Sep 2018 11:54:49 -0700 Subject: [PATCH] Add an experimental installer for wasm-pack This commit kicks off the addition of installers for wasm-pack, with the main goals of being: * Users should have a one-click (ideally) solution to download and install wasm-pack. * We should ideally not have to worry about picking up "heavy" dependencies in wasm-pack like C++ or C code (if necessary). The general idea here is very similar to rustup (and in fact a good deal of code is copied from them!). The installation worklow for wasm-pack in theory after this commit looks like: 1. First, a users visits the installer landing page. A preview is available at https://alexcrichton.github.io/wasm-pack/installer/. Clearly this page needs a better stylesheet! 2. The user performs the instructions presented on the page. The website automatically detects what platform you're running on, basically giving you a curl/sh script or a Windows installer. There's various options for seeing other installers as well, and the fallback of `cargo install` is presented if we don't recognize the platform (or if we don't think we have precompiled binaries). 3a. On Unix, users execute a curl/sh script like: ``` curl https://alexcrichton.github.io/wasm-pack/installer/init.sh -sSf | sh ``` This command will download the shell script included in this PR, which will in turn do some further platform detection (like OSX vs Linux) and then download the most recent version of `wasm-pack` from GitHub releases. This is then extracted, the `wasm-pack` binary is renamed to `wasm-pack-init`, and then that binary is run. The binary will refuse by default to overwrite a previous installation, but that can be overridden with the `-f` flag. 3b. On Windows users download a binary. This binary, when run, will pop up a console window. This window will have the same output as the shell script installer, and will wait at the end of its execution to ensure the user has time to read everything. And... that's it! The deployment process for all this looks like so: * All CI builds will update the website and shell script (published to gh-pages). A small script at `docs/installer/build-installer.rs` fills in the current version number inferred from `Cargo.toml`. * Tagged CI builds will publish releases, as usual. * "Pushing a release" is done by bumping the version number in `Cargo.toml`. When bumped all online installers will now point to the new release. There will be a window of time, though, when the version number is bumped and the release hasn't finished building on CI. In this case users will get errors (unfortunately). This is all still a work-in-progress and feedback is definitely appreicated and welcome on all this! --- .appveyor.yml | 8 +- .gitignore | 1 + .travis.yml | 18 +-- Cargo.lock | 1 + Cargo.toml | 1 + docs/installer/build-installer.rs | 30 +++++ docs/installer/index.html | 106 +++++++++++++++++ docs/installer/init.sh | 190 ++++++++++++++++++++++++++++++ docs/installer/wasm-pack.js | 92 +++++++++++++++ src/installer.rs | 122 +++++++++++++++++++ src/main.rs | 20 +++- 11 files changed, 574 insertions(+), 15 deletions(-) create mode 100644 docs/installer/build-installer.rs create mode 100644 docs/installer/index.html create mode 100644 docs/installer/init.sh create mode 100644 docs/installer/wasm-pack.js create mode 100644 src/installer.rs diff --git a/.appveyor.yml b/.appveyor.yml index f50ea0e6..b14e7e60 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -19,23 +19,25 @@ test_script: before_deploy: - ps: | - $NAME = "wasm-pack-${env:APPVEYOR_REPO_TAG_NAME}-${env:TARGET}" + $NAME = "wasm-pack-${env:APPVEYOR_REPO_TAG_NAME}-x86_64-pc-windows-msvc" New-Item -Path $NAME -ItemType directory Copy-Item target/release/wasm-pack.exe "${NAME}/" + Copy-Item target/release/wasm-pack.exe wasm-pack-init.exe Copy-Item LICENSE-MIT "${NAME}/" Copy-Item LICENSE-APACHE "${NAME}/" Copy-Item README.md "${NAME}/" 7z a -ttar "${NAME}.tar" "${NAME}" 7z a "${NAME}.tar.gz" "${NAME}.tar" Push-AppveyorArtifact "${NAME}.tar.gz" + Push-AppveyorArtifact wasm-pack-init.exe deploy: - artifact: /.*\.tar.gz/ + artifact: /.*\.tar.gz/, /.*\.exe/ description: 'Appveyor Automated Release' provider: GitHub draft: false prerelease: false - autho_token: + auth_token: secure: iHsRUqwGf/Zh7OuYpHOWQL8buaOL+c8/6kXLRly8V2j0LCUo7CcDs0NxQ0vl2bhZ on: appveyor_repo_tag: true diff --git a/.gitignore b/.gitignore index 4b2ee66a..ea6c5a6e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ target/ tests/.crates.toml tests/bin wasm-pack.log +/build-installer diff --git a/.travis.yml b/.travis.yml index 5b143b2f..977b9d5f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,6 +14,9 @@ cache: directories: - /home/travis/.cargo +GH_TOKEN: &GH_TOKEN + secure: fx0rR5Ii1KcsydexE6QpkDbqItNdj3Lt6L5yFZaKKB/ejw9M555NkXA+0GZqV0sLZ54qfR8zTaXAf6eBFKgcG9etaCl7vTXqsvDrlssth82oki1zufP39uuoOy4WgFq8OfACOtUq7opDAgYmpaGzlFiny+c5j7asGwDtAU1Fc3JeJsvAnxHKg9+0spXFD6kBQd5CWpqDXv2rLFK0b8IM2fHAzd0PiJZQWqz//2Cj/r9rTiewtIzqigctAfOgFwYoQvfdM+0mKb4pefG+zXEGfxxQr4r5hqZ6UMO7hto3Jnm9LRjNR8dNaDQCqQ0bkdLTAMTC3nV/gZPM679yQU3KHueVjg9pleNzuKnuBgYmH9+BrlG1dW68kqA+6Xh+wIJYrLuagWhJDlCtiU6PM5QAbFg3mabPIBG3M2IHTrOVATme+iW5vpROARhgjbQEF235DyvZaT+Tml3+PY+PfcRax2DVUhvGQViv4tzppbT0PjjBlEbGct49cFLGdqZIJBiVrYW24I2QkENTnUgZsFIBuJlVCBHZwZlLo9ldVvu4XTMKw65z42zoTzobjtbC1QPEZPiaJXSxC7W569fqL/ORXwGToFk6rQjXwEqDP2okGiusR75LXrZD6qFibNpqeypRFtqOzntsOfTUGrlaN1yTt/6dz0V0j9uI7a9/CHVcblI= + DEPLOY_TO_GITHUB: &DEPLOY_TO_GITHUB before_deploy: - git config --local user.name "Ashley Williams" @@ -25,14 +28,11 @@ DEPLOY_TO_GITHUB: &DEPLOY_TO_GITHUB - tar czvf $name.tar.gz $name deploy: provider: releases - api_key: - secure: fx0rR5Ii1KcsydexE6QpkDbqItNdj3Lt6L5yFZaKKB/ejw9M555NkXA+0GZqV0sLZ54qfR8zTaXAf6eBFKgcG9etaCl7vTXqsvDrlssth82oki1zufP39uuoOy4WgFq8OfACOtUq7opDAgYmpaGzlFiny+c5j7asGwDtAU1Fc3JeJsvAnxHKg9+0spXFD6kBQd5CWpqDXv2rLFK0b8IM2fHAzd0PiJZQWqz//2Cj/r9rTiewtIzqigctAfOgFwYoQvfdM+0mKb4pefG+zXEGfxxQr4r5hqZ6UMO7hto3Jnm9LRjNR8dNaDQCqQ0bkdLTAMTC3nV/gZPM679yQU3KHueVjg9pleNzuKnuBgYmH9+BrlG1dW68kqA+6Xh+wIJYrLuagWhJDlCtiU6PM5QAbFg3mabPIBG3M2IHTrOVATme+iW5vpROARhgjbQEF235DyvZaT+Tml3+PY+PfcRax2DVUhvGQViv4tzppbT0PjjBlEbGct49cFLGdqZIJBiVrYW24I2QkENTnUgZsFIBuJlVCBHZwZlLo9ldVvu4XTMKw65z42zoTzobjtbC1QPEZPiaJXSxC7W569fqL/ORXwGToFk6rQjXwEqDP2okGiusR75LXrZD6qFibNpqeypRFtqOzntsOfTUGrlaN1yTt/6dz0V0j9uI7a9/CHVcblI= - file_glob: true + api_key: *GH_TOKEN file: wasm-pack-$TRAVIS_TAG-$TARGET.tar.gz skip_cleanup: true on: branch: master - condition: $DEPLOY = 1 tags: true matrix: @@ -59,18 +59,20 @@ matrix: - (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.1" mdbook) - cargo install-update -a script: - - cd docs && mdbook build + - (cd docs && mdbook build) + - rustc ./docs/installer/build-installer.rs + - ./build-installer deploy: provider: pages skip-cleanup: true - github-token: $GITHUB_TOKEN + github-token: *GH_TOKEN local-dir: docs/book keep-history: false on: branch: master # dist linux binary - - env: JOB=dist-linux TARGET=x86_64-unknown-linux-musl DEPLOY=1 + - env: JOB=dist-linux TARGET=x86_64-unknown-linux-musl rust: nightly before_script: rustup target add $TARGET script: cargo build --release --target $TARGET --locked --features vendored-openssl @@ -81,7 +83,7 @@ matrix: <<: *DEPLOY_TO_GITHUB # dist OSX binary - - env: JOB=dist-osx MACOSX_DEPLOYMENT_TARGET=10.7 DEPLOY=1 TARGET=x86_64-apple-darwin + - env: JOB=dist-osx MACOSX_DEPLOYMENT_TARGET=10.7 TARGET=x86_64-apple-darwin os: osx rust: nightly script: cargo build --release --target $TARGET --locked diff --git a/Cargo.lock b/Cargo.lock index 8aa26c7d..2b06be94 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -900,6 +900,7 @@ dependencies = [ name = "wasm-pack" version = "0.4.2" dependencies = [ + "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "console 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "copy_dir 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "curl 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index ac1d24fe..1113f156 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ readme = "README.md" categories = ["wasm"] [dependencies] +atty = "0.2.11" console = "0.6.1" curl = "0.4.13" failure = "0.1.2" diff --git a/docs/installer/build-installer.rs b/docs/installer/build-installer.rs new file mode 100644 index 00000000..a5aa5995 --- /dev/null +++ b/docs/installer/build-installer.rs @@ -0,0 +1,30 @@ +use std::fs; + +fn main() { + fs::create_dir_all("docs/book/installer").unwrap(); + fs::copy( + "docs/installer/wasm-pack.js", + "docs/book/installer/wasm-pack.js", + ).unwrap(); + let index = fs::read_to_string("docs/installer/index.html").unwrap(); + fs::write( + "docs/book/installer/index.html", + fixup(&index), + ).unwrap(); + + let init = fs::read_to_string("docs/installer/init.sh").unwrap(); + fs::write( + "docs/book/installer/init.sh", + fixup(&init), + ).unwrap(); +} + +fn fixup(input: &str) -> String { + let manifest = fs::read_to_string("Cargo.toml").unwrap(); + let version = manifest.lines() + .find(|line| line.starts_with("version =")) + .unwrap(); + let version = &version[version.find('"').unwrap() + 1..version.rfind('"').unwrap()]; + + input.replace("$VERSION", &format!("v{}", version)) +} diff --git a/docs/installer/index.html b/docs/installer/index.html new file mode 100644 index 00000000..f4463b28 --- /dev/null +++ b/docs/installer/index.html @@ -0,0 +1,106 @@ + + + + + wasm-pack + + + + +
+ Install wasm-pack! A tool with a blurb here. +
+ + + + + + + +
+
+

+ To install wasm-pack, if you are running Unix,
+ run the following in your terminal, then follow the onscreen + instructions. +

+
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
+
+ +
+ +
+

+ If you are running Windows 64-bit,
download and run + wasm-pack-init.exe + then follow the onscreen instructions. +

+
+ +
+ +
+

+ For all other platforms, run the following in your terminal: +

+
cargo install wasm-pack
+
+
+ + diff --git a/docs/installer/init.sh b/docs/installer/init.sh new file mode 100644 index 00000000..7dbc05a2 --- /dev/null +++ b/docs/installer/init.sh @@ -0,0 +1,190 @@ +#!/bin/bash +# Copyright 2016 The Rust Project Developers. See the COPYRIGHT +# file at the top-level directory of this distribution and at +# http://rust-lang.org/COPYRIGHT. +# +# Licensed under the Apache License, Version 2.0 or the MIT license +# , at your +# option. This file may not be copied, modified, or distributed +# except according to those terms. + +# This is just a little script that can be downloaded from the internet to +# install wasm-pack. It just does platform detection, downloads the installer +# and runs it. + +set -u + +UPDATE_ROOT="https://github.com/rustwasm/wasm-pack/releases/download/$VERSION" + +main() { + downloader --check + need_cmd uname + need_cmd mktemp + need_cmd chmod + need_cmd mkdir + need_cmd rm + need_cmd rmdir + need_cmd tar + need_cmd which + need_cmd dirname + + get_architecture || return 1 + local _arch="$RETVAL" + assert_nz "$_arch" "arch" + + local _ext="" + case "$_arch" in + *windows*) + _ext=".exe" + ;; + esac + + which rustup > /dev/null 2>&1 + need_ok "failed to find Rust installation, is rustup installed?" + local _rustup=`which rustup` + local _tardir="wasm-pack-$VERSION-${_arch}" + local _url="$UPDATE_ROOT/${_tardir}.tar.gz" + local _dir="$(mktemp -d 2>/dev/null || ensure mktemp -d -t wasm-pack)" + local _file="$_dir/input.tar.gz" + local _wasmpack="$_dir/wasm-pack$_ext" + local _wasmpackinit="$_dir/wasm-pack-init$_ext" + + printf '%s\n' 'info: downloading wasm-pack' 1>&2 + + ensure mkdir -p "$_dir" + downloader "$_url" "$_file" + if [ $? != 0 ]; then + say "failed to download $_url" + say "this may be a standard network error, but it may also indicate" + say "that wasm-pack's release process is not working. When in doubt" + say "please feel free to open an issue!" + exit 1 + fi + ensure tar xf "$_file" --strip-components 1 -C "$_dir" + mv "$_wasmpack" "$_wasmpackinit" + + # The installer may want to ask for confirmation on stdin for various + # operations. We were piped through `sh` though so we probably don't have + # access to a tty naturally. If it looks like we're attached to a terminal + # (`-t 1`) then pass the tty down to the installer explicitly. + if [ -t 1 ]; then + "$_wasmpackinit" "$@" < /dev/tty + else + "$_wasmpackinit" "$@" + fi + + local _retval=$? + + ignore rm -rf "$_dir" + + return "$_retval" +} + +get_architecture() { + local _ostype="$(uname -s)" + local _cputype="$(uname -m)" + + if [ "$_ostype" = Darwin -a "$_cputype" = i386 ]; then + # Darwin `uname -s` lies + if sysctl hw.optional.x86_64 | grep -q ': 1'; then + local _cputype=x86_64 + fi + fi + + case "$_ostype" in + Linux) + local _ostype=unknown-linux-musl + ;; + + Darwin) + local _ostype=apple-darwin + ;; + + MINGW* | MSYS* | CYGWIN*) + local _ostype=pc-windows-msvc + ;; + + *) + err "no precompiled binaries available for OS: $_ostype" + ;; + esac + + case "$_cputype" in + x86_64 | x86-64 | x64 | amd64) + local _cputype=x86_64 + ;; + *) + err "no precompiled binaries available for CPU architecture: $_cputype" + + esac + + local _arch="$_cputype-$_ostype" + + RETVAL="$_arch" +} + +say() { + echo "wasm-pack-init: $1" +} + +err() { + say "$1" >&2 + exit 1 +} + +need_cmd() { + if ! check_cmd "$1" + then err "need '$1' (command not found)" + fi +} + +check_cmd() { + command -v "$1" > /dev/null 2>&1 + return $? +} + +need_ok() { + if [ $? != 0 ]; then err "$1"; fi +} + +assert_nz() { + if [ -z "$1" ]; then err "assert_nz $2"; fi +} + +# Run a command that should never fail. If the command fails execution +# will immediately terminate with an error showing the failing +# command. +ensure() { + "$@" + need_ok "command failed: $*" +} + +# This is just for indicating that commands' results are being +# intentionally ignored. Usually, because it's being executed +# as part of error handling. +ignore() { + "$@" +} + +# This wraps curl or wget. Try curl first, if not installed, +# use wget instead. +downloader() { + if check_cmd curl + then _dld=curl + elif check_cmd wget + then _dld=wget + else _dld='curl or wget' # to be used in error message of need_cmd + fi + + if [ "$1" = --check ] + then need_cmd "$_dld" + elif [ "$_dld" = curl ] + then curl -sSfL "$1" -o "$2" + elif [ "$_dld" = wget ] + then wget "$1" -O "$2" + else err "Unknown downloader" # should not reach here + fi +} + +main "$@" || exit 1 diff --git a/docs/installer/wasm-pack.js b/docs/installer/wasm-pack.js new file mode 100644 index 00000000..5d87b703 --- /dev/null +++ b/docs/installer/wasm-pack.js @@ -0,0 +1,92 @@ +var platforms = ["default", "unknown", "win64", "unix"]; +var platform_override = null; + +function detect_platform() { + "use strict"; + + if (platform_override !== null) { + return platforms[platform_override]; + } + + var os = "unknown"; + + if (navigator.platform == "Linux x86_64") {os = "unix";} + if (navigator.platform == "Linux i686") {os = "unix";} + if (navigator.platform == "Linux i686 on x86_64") {os = "unix";} + if (navigator.platform == "Linux aarch64") {os = "unix";} + if (navigator.platform == "Linux armv6l") {os = "unix";} + if (navigator.platform == "Linux armv7l") {os = "unix";} + if (navigator.platform == "Linux armv8l") {os = "unix";} + if (navigator.platform == "Linux ppc64") {os = "unix";} + if (navigator.platform == "Linux mips") {os = "unix";} + if (navigator.platform == "Linux mips64") {os = "unix";} + if (navigator.platform == "Mac") {os = "unix";} + // if (navigator.platform == "Win32") {os = "win32";} + if (navigator.platform == "Win64" || + navigator.userAgent.indexOf("WOW64") != -1 || + navigator.userAgent.indexOf("Win64") != -1) { os = "win64"; } + if (navigator.platform == "FreeBSD x86_64") {os = "unix";} + if (navigator.platform == "FreeBSD amd64") {os = "unix";} + if (navigator.platform == "NetBSD x86_64") {os = "unix";} + if (navigator.platform == "NetBSD amd64") {os = "unix";} + + // I wish I knew by now, but I don't. Try harder. + if (os == "unknown") { + // if (navigator.appVersion.indexOf("Win")!=-1) {os = "win32";} + if (navigator.appVersion.indexOf("Mac")!=-1) {os = "unix";} + // rust-www/#692 - FreeBSD epiphany! + if (navigator.appVersion.indexOf("FreeBSD")!=-1) {os = "unix";} + } + + // Firefox Quantum likes to hide platform and appVersion but oscpu works + if (navigator.oscpu) { + // if (navigator.oscpu.indexOf("Win32")!=-1) {os = "win32";} + if (navigator.oscpu.indexOf("Win64")!=-1) {os = "win64";} + if (navigator.oscpu.indexOf("Mac")!=-1) {os = "unix";} + if (navigator.oscpu.indexOf("Linux")!=-1) {os = "unix";} + if (navigator.oscpu.indexOf("FreeBSD")!=-1) {os = "unix";} + if (navigator.oscpu.indexOf("NetBSD")!=-1) {os = "unix";} + } + + return os; +} + +function adjust_for_platform() { + "use strict"; + + var platform = detect_platform(); + + platforms.forEach(function (platform_elem) { + var platform_div = document.getElementById("platform-instructions-" + platform_elem); + platform_div.style.display = "none"; + if (platform == platform_elem || + (platform == 'unknown' && platform_elem == 'default')) { + platform_div.style.display = "block"; + } + }); +} + +function go_to_default_platform() { + platform_override = 0; + adjust_for_platform(); +} + +function set_up_default_platform_buttons() { + var defaults_buttons = document.getElementsByClassName('default-platform-button'); + for (var i = 0; i < defaults_buttons.length; i++) { + defaults_buttons[i].onclick = go_to_default_platform; + } +} + +function fill_in_bug_report_values() { + var nav_plat = document.getElementById("nav-plat"); + var nav_app = document.getElementById("nav-app"); + nav_plat.textContent = navigator.platform; + nav_app.textContent = navigator.appVersion; +} + +(function () { + adjust_for_platform(); + set_up_default_platform_buttons(); + fill_in_bug_report_values(); +}()); diff --git a/src/installer.rs b/src/installer.rs new file mode 100644 index 00000000..8d1f779a --- /dev/null +++ b/src/installer.rs @@ -0,0 +1,122 @@ +//! Self-installation of `wasm-pack` +//! +//! This module contains one public function which will self-install the +//! currently running executable as `wasm-pack`. Our goal is to install this in +//! a place that's already in `PATH`, ideally in an idiomatic location. To that +//! end we place `wasm-pack` next to the `rustup` executable in `PATH`. +//! +//! This installer is run directly (probably by clicking on it) on Windows, +//! meaning it will pop up a console (as we're a console app). Output goes to +//! the console and users interact with it through the console. On Unix this is +//! intended to be run from a shell script (docs/installer/init.sh) which is +//! downloaded via curl/sh, and then the shell script downloads this executable +//! and runs it. +//! +//! This may get more complicated over time (self upates anyone?) but for now +//! it's pretty simple! We're largely just moving over our currently running +//! executable to a different path. + +use std::env; +use std::fs; +use std::io; +use std::path::Path; +use std::process; + +use atty; +use failure::{Error, ResultExt}; +use which; + +pub fn install() -> ! { + if let Err(e) = do_install() { + eprintln!("{}", e); + for cause in e.iter_causes() { + eprintln!("Caused by: {}", cause); + } + } + + // On Windows we likely popped up a console for the installation. If we were + // to exit here immediately then the user wouldn't see any error that + // happened above or any successful message. Let's wait for them to say + // they've read everything and then continue. + if cfg!(windows) { + println!("Press enter to close this window..."); + let mut line = String::new(); + drop(io::stdin().read_line(&mut line)); + } + + process::exit(0); +} + +fn do_install() -> Result<(), Error> { + // Find `rustup.exe` in PATH, we'll be using its installation directory as + // our installation directory. + let rustup = match which::which("rustup") { + Ok(path) => path, + Err(_) => { + bail!( + "failed to find an installation of `rustup` in `PATH`, \ + is rustup already installed?" + ); + } + }; + let installation_dir = match rustup.parent() { + Some(parent) => parent, + None => bail!("can't install when `rustup` is at the root of the filesystem"), + }; + let destination = installation_dir + .join("wasm-pack") + .with_extension(env::consts::EXE_EXTENSION); + + if destination.exists() { + confirm_can_overwrite(&destination)?; + } + + // Our relatively simple install step! + let me = env::current_exe()?; + fs::copy(&me, &destination) + .with_context(|_| format!("failed to copy executable to `{}`", destination.display()))?; + println!( + "info: successfully installed wasm-pack to `{}`", + destination.display() + ); + + // ... and that's it! + + Ok(()) +} + +fn confirm_can_overwrite(dst: &Path) -> Result<(), Error> { + // If the `-f` argument was passed, we can always overwrite everything. + if env::args().any(|arg| arg == "-f") { + return Ok(()); + } + + // If we're not attached to a TTY then we can't get user input, so there's + // nothing to do except inform the user about the `-f` flag. + if !atty::is(atty::Stream::Stdin) { + bail!( + "existing wasm-pack installation found at `{}`, pass `-f` to \ + force installation over this file, otherwise aborting \ + installation now", + dst.display() + ); + } + + // It looks like we're at an interactive prompt, so ask the user if they'd + // like to overwrite the previous installation. + eprintln!( + "info: existing wasm-pack installation found at `{}`", + dst.display() + ); + eprint!("info: would you like to overwrite this file? [y/N]: "); + let mut line = String::new(); + io::stdin() + .read_line(&mut line) + .with_context(|_| "failed to read stdin")?; + + if line.starts_with("y") || line.starts_with("Y") { + return Ok(()); + } + + bail!("aborting installation"); +} diff --git a/src/main.rs b/src/main.rs index 39b56a35..c9295396 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,31 +1,43 @@ +extern crate atty; +#[macro_use] extern crate failure; #[macro_use] extern crate human_panic; extern crate structopt; extern crate wasm_pack; +extern crate which; -use failure::Fail; use std::env; use structopt::StructOpt; -use wasm_pack::{command::run_wasm_pack, error::Error, logger, Cli}; +use wasm_pack::{command::run_wasm_pack, logger, Cli}; + +mod installer; fn main() { setup_panic!(); if let Err(e) = run() { eprintln!("{}", e); - for cause in Fail::iter_causes(&e) { + for cause in e.iter_causes() { eprintln!("Caused by: {}", cause); } ::std::process::exit(1); } } -fn run() -> Result<(), Error> { +fn run() -> Result<(), failure::Error> { // Deprecate `init` if let Some("init") = env::args().nth(1).as_ref().map(|arg| arg.as_str()) { println!("wasm-pack init is deprecated, consider using wasm-pack build"); } + if let Ok(me) = env::current_exe() { + // If we're actually running as the installer then execute our + // self-installation, otherwise just continue as usual. + if me.file_stem().and_then(|s| s.to_str()) == Some("wasm-pack-init") { + installer::install(); + } + } + let args = Cli::from_args(); let log = logger::new(&args.cmd, args.verbosity)?; run_wasm_pack(args.cmd, &log)?;