Skip to content

Commit

Permalink
improve installer (#512)
Browse files Browse the repository at this point in the history
- remove uninstall command
- add --reload to deno fetch - to ensure subsequent installation 
  upgrades script and deps
- fix executable shebang
- fix prompt for subsequent installation
- support custom installation dir via -d/--dir flag
  • Loading branch information
bartlomieju authored and ry committed Jun 20, 2019
1 parent 4317af1 commit b13441f
Show file tree
Hide file tree
Showing 5 changed files with 388 additions and 129 deletions.
47 changes: 33 additions & 14 deletions installer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,50 +4,65 @@ Install remote or local script as executables.

## Installation

`installer` can be install using iteself:
`installer` can be install using itself:

```sh
deno -A https://deno.land/std/installer/mod.ts deno_installer https://deno.land/std/installer/mod.ts -A
```

Installer uses `~/.deno/bin` to store installed scripts so make sure it's in `$PATH`

```
echo 'export PATH="$HOME/.deno/bin:$PATH"' >> ~/.bashrc # change this to your shell
```

## Usage

Install script

```sh
# remote script
$ deno_installer file_server https://deno.land/std/http/file_server.ts --allow-net --allow-read
> Downloading: https://deno.land/std/http/file_server.ts
> [1/1] Compiling https://deno.land/std/http/file_server.ts
>
> ✅ Successfully installed file_server.
> ~/.deno/bin/file_server

# local script
$ deno_installer file_server ./deno_std/http/file_server.ts --allow-net --allow-read
> Looking for: /dev/deno_std/http/file_server.ts
> [1/1] Compiling file:///dev/deno_std/http/file_server.ts
>
> ✅ Successfully installed file_server.
> ~/.deno/bin/file_server
```

Use installed script:
Run installed script:

```sh
$ file_server
HTTP server listening on http://0.0.0.0:4500/
```

Update installed script
## Custom installation directory

By default installer uses `~/.deno/bin` to store installed scripts so make sure it's in your `$PATH`.

```
echo 'export PATH="$HOME/.deno/bin:$PATH"' >> ~/.bashrc # change this to your shell
```

If you prefer to change installation directory use `-d` or `--dir` flag.

```
$ deno_installer --dir /usr/local/bin file_server ./deno_std/http/file_server.ts --allow-net --allow-read
> [1/1] Compiling file:///dev/deno_std/http/file_server.ts
>
> ✅ Successfully installed file_server.
> /usr/local/bin/file_server
```

## Update installed script

```sh
$ deno_installer file_server https://deno.land/std/http/file_server.ts --allow-net --allow-read
> ⚠️ file_server is already installed, do you want to overwrite it? [yN]
> y
>
> Downloading: https://deno.land/std/http/file_server.ts
> [1/1] Compiling file:///dev/deno_std/http/file_server.ts
>
> ✅ Successfully installed file_server.
```
Expand All @@ -60,10 +75,14 @@ $ deno_installer --help
Install remote or local script as executables.

USAGE:
deno https://deno.land/std/installer/mod.ts EXE_NAME SCRIPT_URL [FLAGS...]
deno -A https://deno.land/std/installer/mod.ts [OPTIONS] EXE_NAME SCRIPT_URL [FLAGS...]

ARGS:
EXE_NAME Name for executable
SCRIPT_URL Local or remote URL of script to install
[FLAGS...] List of flags for script, both Deno permission and script specific flag can be used.
[FLAGS...] List of flags for script, both Deno permission and script specific
flag can be used.

OPTIONS:
-d, --dir <PATH> Installation directory path (defaults to ~/.deno/bin)
```
156 changes: 78 additions & 78 deletions installer/mod.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,34 @@
#!/usr/bin/env deno --allow-all
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
const {
args,
env,
readDirSync,
mkdirSync,
writeFile,
exit,
stdin,
chmod,
remove,
run
} = Deno;
const { env, stdin, args, exit, writeFile, chmod, run } = Deno;
import { parse } from "../flags/mod.ts";
import * as path from "../fs/path.ts";
import { exists } from "../fs/exists.ts";
import { ensureDir } from "../fs/ensure_dir.ts";

const encoder = new TextEncoder();
const decoder = new TextDecoder("utf-8");
const isWindows = Deno.platform.os === "win";
// Regular expression to test disk driver letter. eg "C:\\User\username\path\to"
const driverLetterReg = /^[c-z]:/i;
const isWindows = Deno.platform.os === "win";

function showHelp(): void {
console.log(`deno installer
Install remote or local script as executables.
USAGE:
deno -A https://deno.land/std/installer/mod.ts [OPTIONS] EXE_NAME SCRIPT_URL [FLAGS...]
ARGS:
EXE_NAME Name for executable
SCRIPT_URL Local or remote URL of script to install
[FLAGS...] List of flags for script, both Deno permission and script specific
flag can be used.
OPTIONS:
-d, --dir <PATH> Installation directory path (defaults to ~/.deno/bin)
`);
}

enum Permission {
Read,
Expand Down Expand Up @@ -67,6 +76,20 @@ function getFlagFromPermission(perm: Permission): string {
return "";
}

function getInstallerDir(): string {
// In Windows's Powershell $HOME environmental variable maybe null
// if so use $HOMEPATH instead.
let { HOME, HOMEPATH } = env();

const HOME_PATH = HOME || HOMEPATH;

if (!HOME_PATH) {
throw new Error("$HOME is not defined.");
}

return path.join(HOME_PATH, ".deno", "bin");
}

async function readCharacter(): Promise<string> {
const byteArray = new Uint8Array(1024);
await stdin.read(byteArray);
Expand All @@ -81,14 +104,6 @@ async function yesNoPrompt(message: string): Promise<boolean> {
return input === "y" || input === "Y";
}

function createDirIfNotExists(path: string): void {
try {
readDirSync(path);
} catch (e) {
mkdirSync(path, true);
}
}

function checkIfExistsInPath(filePath: string): boolean {
// In Windows's Powershell $PATH not exist, so use $Path instead.
// $HOMEDRIVE is only used on Windows.
Expand Down Expand Up @@ -120,33 +135,16 @@ function checkIfExistsInPath(filePath: string): boolean {
return false;
}

function getInstallerDir(): string {
// In Windows's Powershell $HOME environmental variable maybe null
// if so use $HOMEPATH instead.
let { HOME, HOMEPATH } = env();

const HOME_PATH = HOME || HOMEPATH;

if (!HOME_PATH) {
throw new Error("$HOME is not defined.");
}

return path.join(HOME_PATH, ".deno", "bin");
export function isRemoteUrl(url: string): boolean {
return /^https?:\/\//.test(url);
}

function showHelp(): void {
console.log(`deno installer
Install remote or local script as executables.
USAGE:
deno https://deno.land/std/installer/mod.ts EXE_NAME SCRIPT_URL [FLAGS...]
ARGS:
EXE_NAME Name for executable
SCRIPT_URL Local or remote URL of script to install
[FLAGS...] List of flags for script, both Deno permission and script specific
flag can be used.
`);
function validateModuleName(moduleName: string): boolean {
if (/^[a-z][\w-]*$/i.test(moduleName)) {
return true;
} else {
throw new Error("Invalid module name: " + moduleName);
}
}

async function generateExecutable(
Expand Down Expand Up @@ -176,7 +174,7 @@ async function generateExecutable(
}

// generate Shell script
const template = `#/bin/sh
const template = `#!/bin/sh
# ${templateHeader}
basedir=$(dirname "$(echo "$0" | sed -e 's,\\\\,/,g')")
Expand All @@ -200,25 +198,36 @@ exit $ret
export async function install(
moduleName: string,
moduleUrl: string,
flags: string[]
flags: string[],
installationDir?: string
): Promise<void> {
const installerDir = getInstallerDir();
createDirIfNotExists(installerDir);
if (!installationDir) {
installationDir = getInstallerDir();
}
await ensureDir(installationDir);

// if install local module
if (!isRemoteUrl(moduleUrl)) {
moduleUrl = path.resolve(moduleUrl);
}

const filePath = path.join(installerDir, moduleName);
validateModuleName(moduleName);
const filePath = path.join(installationDir, moduleName);

if (await exists(filePath)) {
const msg =
"⚠️ ${moduleName} is already installed, " +
"do you want to overwrite it?";
"⚠️ " +
moduleName +
" is already installed" +
", do you want to overwrite it?";
if (!(await yesNoPrompt(msg))) {
return;
}
}

// ensure script that is being installed exists
const ps = run({
args: ["deno", "fetch", moduleUrl],
args: ["deno", "fetch", "--reload", moduleUrl],
stdout: "inherit",
stderr: "inherit"
});
Expand Down Expand Up @@ -254,44 +263,35 @@ export async function install(
console.log(`✅ Successfully installed ${moduleName}`);
console.log(filePath);

if (!checkIfExistsInPath(installerDir)) {
console.log("\nℹ️ Add ~/.deno/bin to PATH");
if (!checkIfExistsInPath(installationDir)) {
console.log(`\nℹ️ Add ${installationDir} to PATH`);
console.log(
" echo 'export PATH=\"$HOME/.deno/bin:$PATH\"' >> ~/.bashrc # change" +
" echo 'export PATH=\"" +
installationDir +
":$PATH\"' >> ~/.bashrc # change" +
" this to your shell"
);
}
}

export async function uninstall(moduleName: string): Promise<void> {
const installerDir = getInstallerDir();
const filePath = path.join(installerDir, moduleName);

if (!(await exists(filePath))) {
throw new Error(`ℹ️ ${moduleName} not found`);
}

await remove(filePath);
if (isWindows) {
await remove(filePath + ".cmd");
}
console.log(`ℹ️ Uninstalled ${moduleName}`);
}

async function main(): Promise<void> {
if (args.length < 3) {
const parsedArgs = parse(args.slice(1), { stopEarly: true });

if (parsedArgs.h || parsedArgs.help) {
return showHelp();
}

if (["-h", "--help"].includes(args[1])) {
if (parsedArgs._.length < 2) {
return showHelp();
}

const moduleName = args[1];
const moduleUrl = args[2];
const flags = args.slice(3);
const moduleName = parsedArgs._[0];
const moduleUrl = parsedArgs._[1];
const flags = parsedArgs._.slice(2);
const installationDir = parsedArgs.d || parsedArgs.dir;

try {
await install(moduleName, moduleUrl, flags);
await install(moduleName, moduleUrl, flags, installationDir);
} catch (e) {
console.log(e);
exit(1);
Expand Down
Loading

0 comments on commit b13441f

Please sign in to comment.