From c689f8ef82766c54c22217c6d4145102370e5c85 Mon Sep 17 00:00:00 2001 From: syumai Date: Mon, 10 Jun 2019 01:52:11 +0900 Subject: [PATCH 01/28] Add installer --- installer/README.md | 47 +++++++++ installer/deno_install.ts | 3 + installer/mod.ts | 195 ++++++++++++++++++++++++++++++++++++++ installer/shebang.ts | 29 ++++++ 4 files changed, 274 insertions(+) create mode 100644 installer/README.md create mode 100644 installer/deno_install.ts create mode 100644 installer/mod.ts create mode 100644 installer/shebang.ts diff --git a/installer/README.md b/installer/README.md new file mode 100644 index 000000000000..2d8e846f7bf9 --- /dev/null +++ b/installer/README.md @@ -0,0 +1,47 @@ +# deno_install + +- This command installs executable deno script. + +## Features + +- Install executable script into ~/.deno/bin + +## Usage + +```sh +deno_install https://deno.land/std/http/file_server.ts +# now you can use installed command! +file_server +``` + +## Requirements + +- wget #TODO: Remove + +## Installing + +### 1. Install deno_install + +deno_install can be installed by using itself. + +```sh +deno -A https://deno.land/std/install/deno_install.ts https://deno.land/std/install/deno_install.ts +``` + +### 2. Add `~/.deno/bin` to PATH + +``` +echo 'export PATH="$HOME/.deno/bin:$PATH"' >> ~/.bashrc # change this to your shell +``` + +## Create Executable Script + +- Add shebang to top of your deno script. + - This defines what permissions are needed. + +```sh +#!/usr/bin/env deno --allow-read --allow-write --allow-env --allow-run +``` + +- Host script on the web. +- Install script using deno_install. diff --git a/installer/deno_install.ts b/installer/deno_install.ts new file mode 100644 index 000000000000..109fbb9f4b54 --- /dev/null +++ b/installer/deno_install.ts @@ -0,0 +1,3 @@ +#!/usr/bin/env deno --allow-all +// This file was added to install `installer/mod.ts` with the name `deno_install`. +import './mod.ts'; \ No newline at end of file diff --git a/installer/mod.ts b/installer/mod.ts new file mode 100644 index 000000000000..40ef9bf44756 --- /dev/null +++ b/installer/mod.ts @@ -0,0 +1,195 @@ +#!/usr/bin/env deno --allow-all + +const { + args, + env, + readDirSync, + mkdirSync, + writeFileSync, + exit, + stdin, + run, +} = Deno; +import * as path from '../fs/path.ts'; +import { parse } from './shebang.ts'; + +enum Permission { + Unknown, + Read, + Write, + Net, + Env, + Run, + All, +} + +function getPermissionFromFlag(flag: string): Permission { + switch (flag) { + case '--allow-read': + return Permission.Read; + case '--allow-write': + return Permission.Write; + case '--allow-net': + return Permission.Net; + case '--allow-env': + return Permission.Env; + case '--allow-run': + return Permission.Run; + case '--allow-all': + return Permission.All; + case '-A': + return Permission.All; + } + return Permission.Unknown; +} + +function getFlagFromPermission(perm: Permission): string { + switch (perm) { + case Permission.Read: + return '--allow-read'; + case Permission.Write: + return '--allow-write'; + case Permission.Net: + return '--allow-net'; + case Permission.Env: + return '--allow-env'; + case Permission.Run: + return '--allow-run'; + case Permission.All: + return '--allow-all'; + } + return ''; +} + +async function readCharacter(): Promise { + const decoder = new TextDecoder('utf-8'); + const byteArray = new Uint8Array(1024); + await stdin.read(byteArray); + const line = decoder.decode(byteArray); + return line[0]; +} + +async function grantPermission( + perm: Permission, + moduleName: string = 'Deno' +): Promise { + let msg = `${moduleName} requests `; + switch (perm) { + case Permission.Read: + msg += 'read access to file system. '; + break; + case Permission.Write: + msg += 'write access to file system. '; + break; + case Permission.Net: + msg += 'network access. '; + break; + case Permission.Env: + msg += 'access to environment variable. '; + break; + case Permission.Run: + msg += 'access to run a subprocess. '; + break; + case Permission.All: + msg += 'all available access. '; + break; + default: + return false; + } + msg += 'Grant permanently? [yN]'; + console.log(msg); + + const input = await readCharacter(); + if (input !== 'y' && input !== 'Y') { + return false; + } + return true; +} + +function createDirIfNotExists(path: string) { + try { + readDirSync(path); + } catch (e) { + mkdirSync(path); + } +} + +function getInstallerHome(): string { + const { DENO_DIR, HOME } = env(); + if (!HOME && !DENO_DIR) { + throw new Error('$DENO_DIR and $HOME are not defined.'); + } + if (DENO_DIR) { + return path.join(DENO_DIR, 'bin'); + } + return path.join(HOME, '.deno', 'bin'); +} + +async function main() { + const encoder = new TextEncoder(); + const decoder = new TextDecoder('utf-8'); + + const INSTALLER_HOME = getInstallerHome(); + + const modulePath: string = args[args.length - 1]; + if (!modulePath.startsWith('http')) { + throw new Error('module path is not correct.'); + } + const moduleName = path.basename(modulePath, '.ts'); + + const wget = run({ + args: ['wget', '--quiet', '-O', '-', modulePath], + stdout: 'piped', + }); + const moduleText = decoder.decode(await wget.output()); + const status = await wget.status(); + wget.close(); + if (status.code !== 0) { + throw new Error(`Failed to get remote script: ${modulePath}`); + } + console.log('Completed loading remote script.'); + + createDirIfNotExists(INSTALLER_HOME); + const FILE_PATH = path.join(INSTALLER_HOME, moduleName); + + const shebang = parse(moduleText.split('\n')[0]); + + const grantedPermissions: Array = []; + for (const flag of shebang.args) { + const permission = getPermissionFromFlag(flag); + if (permission === Permission.Unknown) { + continue; + } + if (!(await grantPermission(permission, moduleName))) { + continue; + } + grantedPermissions.push(permission); + } + const commands = [ + 'deno', + ...grantedPermissions.map(getFlagFromPermission), + modulePath, + '$@', + ]; + + writeFileSync(FILE_PATH, encoder.encode('#/bin/sh\n')); + writeFileSync(FILE_PATH, encoder.encode(commands.join(' '))); + + const makeExecutable = run({ args: ['chmod', '+x', FILE_PATH] }); + await makeExecutable.status(); + makeExecutable.close(); + + console.log(`Successfully installed ${moduleName}.`); +} + +try { + main(); +} catch (e) { + const err = e as Error; + if (err.message) { + console.log(err.message); + exit(1); + } + console.log(e); + exit(1); +} diff --git a/installer/shebang.ts b/installer/shebang.ts new file mode 100644 index 000000000000..95c0410d5665 --- /dev/null +++ b/installer/shebang.ts @@ -0,0 +1,29 @@ +export interface Shebang { + path: string; + args: Array; +} + +class ShebangImpl implements Shebang { + public readonly path: string; + public readonly args: Array; + constructor(shebang: string) { + const line = shebang.split('\n')[0]; + const parts = line.split(' ').map(s => s.trim()); + const pathBase = parts.shift(); + if (pathBase.startsWith('#!')) { + this.path = pathBase.slice(2); + this.args = [...parts]; + } else { + this.path = ''; + this.args = []; + } + } + + toString(): string { + return [`#!${this.path}`, ...this.args].join(' '); + } +} + +export function parse(shebang: string): Shebang { + return new ShebangImpl(shebang); +} From 38aea3d19a02c6b1ecbc6223bcf25a3493c8c6fd Mon Sep 17 00:00:00 2001 From: syumai Date: Mon, 10 Jun 2019 01:59:23 +0900 Subject: [PATCH 02/28] Update README of deno_install --- installer/README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/installer/README.md b/installer/README.md index 2d8e846f7bf9..75ec06adb4ec 100644 --- a/installer/README.md +++ b/installer/README.md @@ -9,9 +9,11 @@ ## Usage ```sh -deno_install https://deno.land/std/http/file_server.ts -# now you can use installed command! -file_server +$ deno_install https://deno.land/std/http/file_server.ts +> file_server requests network access. Grant permanently? [yN] # Grant the permissions to use command. +> y +> Successfully installed file_server. +$ file_server # now you can use installed command! ``` ## Requirements From 8f78c9d945b278da2c64c1aa23fc341b6b418343 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Thu, 13 Jun 2019 17:51:44 +0200 Subject: [PATCH 03/28] replace wget with fetch --- installer/README.md | 4 -- installer/deno_install.ts | 2 +- installer/mod.ts | 122 +++++++++++++++++++++----------------- installer/shebang.ts | 10 ++-- 4 files changed, 75 insertions(+), 63 deletions(-) diff --git a/installer/README.md b/installer/README.md index 75ec06adb4ec..c7662a14e5ab 100644 --- a/installer/README.md +++ b/installer/README.md @@ -16,10 +16,6 @@ $ deno_install https://deno.land/std/http/file_server.ts $ file_server # now you can use installed command! ``` -## Requirements - -- wget #TODO: Remove - ## Installing ### 1. Install deno_install diff --git a/installer/deno_install.ts b/installer/deno_install.ts index 109fbb9f4b54..175f6dcb8f62 100644 --- a/installer/deno_install.ts +++ b/installer/deno_install.ts @@ -1,3 +1,3 @@ #!/usr/bin/env deno --allow-all // This file was added to install `installer/mod.ts` with the name `deno_install`. -import './mod.ts'; \ No newline at end of file +import "./mod.ts"; diff --git a/installer/mod.ts b/installer/mod.ts index 40ef9bf44756..0e62f3e6c887 100644 --- a/installer/mod.ts +++ b/installer/mod.ts @@ -8,10 +8,13 @@ const { writeFileSync, exit, stdin, - run, + run } = Deno; -import * as path from '../fs/path.ts'; -import { parse } from './shebang.ts'; +import * as path from "../fs/path.ts"; +import { parse } from "./shebang.ts"; + +const encoder = new TextEncoder(); +const decoder = new TextDecoder("utf-8"); enum Permission { Unknown, @@ -20,24 +23,24 @@ enum Permission { Net, Env, Run, - All, + All } function getPermissionFromFlag(flag: string): Permission { switch (flag) { - case '--allow-read': + case "--allow-read": return Permission.Read; - case '--allow-write': + case "--allow-write": return Permission.Write; - case '--allow-net': + case "--allow-net": return Permission.Net; - case '--allow-env': + case "--allow-env": return Permission.Env; - case '--allow-run': + case "--allow-run": return Permission.Run; - case '--allow-all': + case "--allow-all": return Permission.All; - case '-A': + case "-A": return Permission.All; } return Permission.Unknown; @@ -46,23 +49,23 @@ function getPermissionFromFlag(flag: string): Permission { function getFlagFromPermission(perm: Permission): string { switch (perm) { case Permission.Read: - return '--allow-read'; + return "--allow-read"; case Permission.Write: - return '--allow-write'; + return "--allow-write"; case Permission.Net: - return '--allow-net'; + return "--allow-net"; case Permission.Env: - return '--allow-env'; + return "--allow-env"; case Permission.Run: - return '--allow-run'; + return "--allow-run"; case Permission.All: - return '--allow-all'; + return "--allow-all"; } - return ''; + return ""; } async function readCharacter(): Promise { - const decoder = new TextDecoder('utf-8'); + const decoder = new TextDecoder("utf-8"); const byteArray = new Uint8Array(1024); await stdin.read(byteArray); const line = decoder.decode(byteArray); @@ -71,36 +74,36 @@ async function readCharacter(): Promise { async function grantPermission( perm: Permission, - moduleName: string = 'Deno' + moduleName: string = "Deno" ): Promise { let msg = `${moduleName} requests `; switch (perm) { case Permission.Read: - msg += 'read access to file system. '; + msg += "read access to file system. "; break; case Permission.Write: - msg += 'write access to file system. '; + msg += "write access to file system. "; break; case Permission.Net: - msg += 'network access. '; + msg += "network access. "; break; case Permission.Env: - msg += 'access to environment variable. '; + msg += "access to environment variable. "; break; case Permission.Run: - msg += 'access to run a subprocess. '; + msg += "access to run a subprocess. "; break; case Permission.All: - msg += 'all available access. '; + msg += "all available access. "; break; default: return false; } - msg += 'Grant permanently? [yN]'; + msg += "Grant permanently? [yN]"; console.log(msg); const input = await readCharacter(); - if (input !== 'y' && input !== 'Y') { + if (input !== "y" && input !== "Y") { return false; } return true; @@ -117,42 +120,55 @@ function createDirIfNotExists(path: string) { function getInstallerHome(): string { const { DENO_DIR, HOME } = env(); if (!HOME && !DENO_DIR) { - throw new Error('$DENO_DIR and $HOME are not defined.'); + throw new Error("$DENO_DIR and $HOME are not defined."); } if (DENO_DIR) { - return path.join(DENO_DIR, 'bin'); + return path.join(DENO_DIR, "bin"); } - return path.join(HOME, '.deno', 'bin'); + return path.join(HOME, ".deno", "bin"); } -async function main() { - const encoder = new TextEncoder(); - const decoder = new TextDecoder('utf-8'); +// TODO: `Response` is not exposed in global +// eslint-disable-next-line @typescript-eslint/no-explicit-any +async function fetchWithRedirects( + url: string, + redirectLimit: number = 10 +): Promise { + const response = await fetch(url); + + if (response.status === 301 || response.status === 302) { + if (redirectLimit > 0) { + const redirectUrl = response.headers.get("location"); + return await fetchWithRedirects(redirectUrl, redirectLimit - 1); + } + } + return response; +} + +async function main() { const INSTALLER_HOME = getInstallerHome(); const modulePath: string = args[args.length - 1]; - if (!modulePath.startsWith('http')) { - throw new Error('module path is not correct.'); + if (!modulePath.startsWith("http")) { + throw new Error("module path is not correct."); } - const moduleName = path.basename(modulePath, '.ts'); - - const wget = run({ - args: ['wget', '--quiet', '-O', '-', modulePath], - stdout: 'piped', - }); - const moduleText = decoder.decode(await wget.output()); - const status = await wget.status(); - wget.close(); - if (status.code !== 0) { + const moduleName = path.basename(modulePath, ".ts"); + + console.log(`Downloading: ${modulePath}`); + const response = await fetchWithRedirects(modulePath); + + if (response.status !== 200) { throw new Error(`Failed to get remote script: ${modulePath}`); } - console.log('Completed loading remote script.'); + const body = await Deno.readAll(response.body); + const moduleText = decoder.decode(body); + console.log("Download complete."); createDirIfNotExists(INSTALLER_HOME); const FILE_PATH = path.join(INSTALLER_HOME, moduleName); - const shebang = parse(moduleText.split('\n')[0]); + const shebang = parse(moduleText.split("\n")[0]); const grantedPermissions: Array = []; for (const flag of shebang.args) { @@ -166,16 +182,16 @@ async function main() { grantedPermissions.push(permission); } const commands = [ - 'deno', + "deno", ...grantedPermissions.map(getFlagFromPermission), modulePath, - '$@', + "$@" ]; - writeFileSync(FILE_PATH, encoder.encode('#/bin/sh\n')); - writeFileSync(FILE_PATH, encoder.encode(commands.join(' '))); + writeFileSync(FILE_PATH, encoder.encode("#/bin/sh\n")); + writeFileSync(FILE_PATH, encoder.encode(commands.join(" "))); - const makeExecutable = run({ args: ['chmod', '+x', FILE_PATH] }); + const makeExecutable = run({ args: ["chmod", "+x", FILE_PATH] }); await makeExecutable.status(); makeExecutable.close(); diff --git a/installer/shebang.ts b/installer/shebang.ts index 95c0410d5665..d9ebd672c75c 100644 --- a/installer/shebang.ts +++ b/installer/shebang.ts @@ -7,20 +7,20 @@ class ShebangImpl implements Shebang { public readonly path: string; public readonly args: Array; constructor(shebang: string) { - const line = shebang.split('\n')[0]; - const parts = line.split(' ').map(s => s.trim()); + const line = shebang.split("\n")[0]; + const parts = line.split(" ").map(s => s.trim()); const pathBase = parts.shift(); - if (pathBase.startsWith('#!')) { + if (pathBase.startsWith("#!")) { this.path = pathBase.slice(2); this.args = [...parts]; } else { - this.path = ''; + this.path = ""; this.args = []; } } toString(): string { - return [`#!${this.path}`, ...this.args].join(' '); + return [`#!${this.path}`, ...this.args].join(" "); } } From c865227bde120fc07e5954dfa64f1a4e0cff0af1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Thu, 13 Jun 2019 18:29:35 +0200 Subject: [PATCH 04/28] more prompts, handle situation without shebang, prompt on overwrite --- installer/mod.ts | 94 ++++++++++++++++++++++++++++---------------- installer/shebang.ts | 13 +++--- 2 files changed, 68 insertions(+), 39 deletions(-) diff --git a/installer/mod.ts b/installer/mod.ts index 0e62f3e6c887..336056761cab 100644 --- a/installer/mod.ts +++ b/installer/mod.ts @@ -5,13 +5,14 @@ const { env, readDirSync, mkdirSync, - writeFileSync, + writeFile, exit, stdin, + stat, run } = Deno; import * as path from "../fs/path.ts"; -import { parse } from "./shebang.ts"; +import { parse as parseShebang } from "./shebang.ts"; const encoder = new TextEncoder(); const decoder = new TextDecoder("utf-8"); @@ -72,6 +73,12 @@ async function readCharacter(): Promise { return line[0]; } +async function yesNoPrompt(): Promise { + console.log("[yN]"); + const input = await readCharacter(); + return input === "y" || input === "Y"; +} + async function grantPermission( perm: Permission, moduleName: string = "Deno" @@ -99,14 +106,9 @@ async function grantPermission( default: return false; } - msg += "Grant permanently? [yN]"; + msg += "Grant permanently?"; console.log(msg); - - const input = await readCharacter(); - if (input !== "y" && input !== "Y") { - return false; - } - return true; + return await yesNoPrompt(); } function createDirIfNotExists(path: string) { @@ -117,14 +119,13 @@ function createDirIfNotExists(path: string) { } } -function getInstallerHome(): string { - const { DENO_DIR, HOME } = env(); - if (!HOME && !DENO_DIR) { - throw new Error("$DENO_DIR and $HOME are not defined."); - } - if (DENO_DIR) { - return path.join(DENO_DIR, "bin"); +function getInstallerDir(): string { + const { HOME } = env(); + + if (!HOME) { + throw new Error("$HOME is not defined."); } + return path.join(HOME, ".deno", "bin"); } @@ -147,13 +148,31 @@ async function fetchWithRedirects( } async function main() { - const INSTALLER_HOME = getInstallerHome(); + const installerDir = getInstallerDir(); + createDirIfNotExists(installerDir); - const modulePath: string = args[args.length - 1]; + const modulePath: string = args[1]; if (!modulePath.startsWith("http")) { - throw new Error("module path is not correct."); + throw new Error("Only remote modules are supported."); } const moduleName = path.basename(modulePath, ".ts"); + const FILE_PATH = path.join(installerDir, moduleName); + + let fileInfo; + try { + fileInfo = await stat(FILE_PATH); + } catch (e) { + // pass + } + + if (fileInfo) { + console.log( + `${moduleName} is already installed, do you want to overwrite it?` + ); + if (!(await yesNoPrompt())) { + return; + } + } console.log(`Downloading: ${modulePath}`); const response = await fetchWithRedirects(modulePath); @@ -165,22 +184,31 @@ async function main() { const moduleText = decoder.decode(body); console.log("Download complete."); - createDirIfNotExists(INSTALLER_HOME); - const FILE_PATH = path.join(INSTALLER_HOME, moduleName); - - const shebang = parse(moduleText.split("\n")[0]); - const grantedPermissions: Array = []; - for (const flag of shebang.args) { - const permission = getPermissionFromFlag(flag); - if (permission === Permission.Unknown) { - continue; + + try { + const line = moduleText.split("\n")[0]; + const shebang = parseShebang(line); + + for (const flag of shebang.args) { + const permission = getPermissionFromFlag(flag); + if (permission === Permission.Unknown) { + continue; + } + if (await grantPermission(permission, moduleName)) { + grantedPermissions.push(permission); + } } - if (!(await grantPermission(permission, moduleName))) { - continue; + } catch (e) { + for (const flag of args.slice(2)) { + const permission = getPermissionFromFlag(flag); + if (permission === Permission.Unknown) { + continue; + } + grantedPermissions.push(permission); } - grantedPermissions.push(permission); } + const commands = [ "deno", ...grantedPermissions.map(getFlagFromPermission), @@ -188,8 +216,8 @@ async function main() { "$@" ]; - writeFileSync(FILE_PATH, encoder.encode("#/bin/sh\n")); - writeFileSync(FILE_PATH, encoder.encode(commands.join(" "))); + const template = `#/bin/sh\n${commands.join(" ")}`; + writeFile(FILE_PATH, encoder.encode(template)); const makeExecutable = run({ args: ["chmod", "+x", FILE_PATH] }); await makeExecutable.status(); diff --git a/installer/shebang.ts b/installer/shebang.ts index d9ebd672c75c..60568b12ef2e 100644 --- a/installer/shebang.ts +++ b/installer/shebang.ts @@ -6,17 +6,18 @@ export interface Shebang { class ShebangImpl implements Shebang { public readonly path: string; public readonly args: Array; + constructor(shebang: string) { const line = shebang.split("\n")[0]; const parts = line.split(" ").map(s => s.trim()); const pathBase = parts.shift(); - if (pathBase.startsWith("#!")) { - this.path = pathBase.slice(2); - this.args = [...parts]; - } else { - this.path = ""; - this.args = []; + + if (!pathBase.startsWith("#!")) { + throw new Error("Not a shebang."); } + + this.path = pathBase.slice(2); + this.args = [...parts]; } toString(): string { From b64f0a6e5fab8d8cbf00f549ff20150d41d35e61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Thu, 13 Jun 2019 19:19:44 +0200 Subject: [PATCH 05/28] better prompt --- installer/mod.ts | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/installer/mod.ts b/installer/mod.ts index 336056761cab..d11c2a835433 100644 --- a/installer/mod.ts +++ b/installer/mod.ts @@ -66,24 +66,24 @@ function getFlagFromPermission(perm: Permission): string { } async function readCharacter(): Promise { - const decoder = new TextDecoder("utf-8"); const byteArray = new Uint8Array(1024); await stdin.read(byteArray); const line = decoder.decode(byteArray); return line[0]; } -async function yesNoPrompt(): Promise { - console.log("[yN]"); +async function yesNoPrompt(message: string): Promise { + console.log(`${message} [yN]`); const input = await readCharacter(); return input === "y" || input === "Y"; } async function grantPermission( perm: Permission, - moduleName: string = "Deno" + moduleName: string, ): Promise { - let msg = `${moduleName} requests `; + // TODO: rewrite prompts + let msg = `⚠️ ${moduleName} requests `; switch (perm) { case Permission.Read: msg += "read access to file system. "; @@ -107,8 +107,7 @@ async function grantPermission( return false; } msg += "Grant permanently?"; - console.log(msg); - return await yesNoPrompt(); + return await yesNoPrompt(msg); } function createDirIfNotExists(path: string) { @@ -148,13 +147,21 @@ async function fetchWithRedirects( } async function main() { - const installerDir = getInstallerDir(); + let installerDir; + + try { + installerDir = getInstallerDir(); + } catch (e) { + fail(e.message); + } + createDirIfNotExists(installerDir); const modulePath: string = args[1]; if (!modulePath.startsWith("http")) { throw new Error("Only remote modules are supported."); } + // TODO: handle JS and extensionless files as well const moduleName = path.basename(modulePath, ".ts"); const FILE_PATH = path.join(installerDir, moduleName); @@ -166,10 +173,8 @@ async function main() { } if (fileInfo) { - console.log( - `${moduleName} is already installed, do you want to overwrite it?` - ); - if (!(await yesNoPrompt())) { + const msg = `⚠️ ${moduleName} is already installed, do you want to overwrite it?`; + if (!await yesNoPrompt(msg)) { return; } } @@ -178,14 +183,17 @@ async function main() { const response = await fetchWithRedirects(modulePath); if (response.status !== 200) { - throw new Error(`Failed to get remote script: ${modulePath}`); + // TODO(bartlomieju) show more debug information like status and maybe body + throw new Error(`Failed to get remote script ${modulePath}.`); } + const body = await Deno.readAll(response.body); const moduleText = decoder.decode(body); console.log("Download complete."); const grantedPermissions: Array = []; + // TODO(bartlomieju) this whole shebang bit should be optional or at least require confirmation try { const line = moduleText.split("\n")[0]; const shebang = parseShebang(line); @@ -226,14 +234,15 @@ async function main() { console.log(`Successfully installed ${moduleName}.`); } +// TODO: refactor try { main(); } catch (e) { const err = e as Error; if (err.message) { console.log(err.message); - exit(1); + } else { + console.log(e); } - console.log(e); exit(1); } From a8d0f634937da40e1e2388394b589d97ede82995 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Thu, 13 Jun 2019 20:08:42 +0200 Subject: [PATCH 06/28] even better prompt --- installer/mod.ts | 98 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 65 insertions(+), 33 deletions(-) diff --git a/installer/mod.ts b/installer/mod.ts index d11c2a835433..7a1ae66be3bf 100644 --- a/installer/mod.ts +++ b/installer/mod.ts @@ -7,6 +7,7 @@ const { mkdirSync, writeFile, exit, + readAll, stdin, stat, run @@ -75,12 +76,13 @@ async function readCharacter(): Promise { async function yesNoPrompt(message: string): Promise { console.log(`${message} [yN]`); const input = await readCharacter(); + console.log(); return input === "y" || input === "Y"; } async function grantPermission( perm: Permission, - moduleName: string, + moduleName: string ): Promise { // TODO: rewrite prompts let msg = `⚠️ ${moduleName} requests `; @@ -128,12 +130,14 @@ function getInstallerDir(): string { return path.join(HOME, ".deno", "bin"); } -// TODO: `Response` is not exposed in global -// eslint-disable-next-line @typescript-eslint/no-explicit-any +// TODO: fetch doesn't handle redirects yet - once it does this function +// can be removed async function fetchWithRedirects( url: string, redirectLimit: number = 10 + // eslint-disable-next-line @typescript-eslint/no-explicit-any ): Promise { + // TODO: `Response` is not exposed in global so 'any' const response = await fetch(url); if (response.status === 301 || response.status === 302) { @@ -146,23 +150,35 @@ async function fetchWithRedirects( return response; } -async function main() { - let installerDir; +function moduleNameFromPath(modulePath: string): string { + let moduleName = path.basename(modulePath, ".ts"); + moduleName = path.basename(moduleName, ".js"); + return moduleName; +} - try { - installerDir = getInstallerDir(); - } catch (e) { - fail(e.message); +async function fetchModule(url: string): Promise { + const response = await fetchWithRedirects(url); + + if (response.status !== 200) { + // TODO: show more debug information like status and maybe body + throw new Error(`Failed to get remote script ${url}.`); } + const body = await readAll(response.body); + return decoder.decode(body); +} + +async function main() { + let installerDir = getInstallerDir(); createDirIfNotExists(installerDir); - const modulePath: string = args[1]; - if (!modulePath.startsWith("http")) { + const moduleUrl: string = args[1]; + // TODO: handle local modules as well + if (!moduleUrl.startsWith("http")) { throw new Error("Only remote modules are supported."); } - // TODO: handle JS and extensionless files as well - const moduleName = path.basename(modulePath, ".ts"); + + const moduleName = moduleNameFromPath(moduleUrl); const FILE_PATH = path.join(installerDir, moduleName); let fileInfo; @@ -174,40 +190,48 @@ async function main() { if (fileInfo) { const msg = `⚠️ ${moduleName} is already installed, do you want to overwrite it?`; - if (!await yesNoPrompt(msg)) { + if (!(await yesNoPrompt(msg))) { return; } } - console.log(`Downloading: ${modulePath}`); - const response = await fetchWithRedirects(modulePath); + console.log(`Downloading: ${moduleUrl}\n`); + const moduleText = await fetchModule(moduleUrl); - if (response.status !== 200) { - // TODO(bartlomieju) show more debug information like status and maybe body - throw new Error(`Failed to get remote script ${modulePath}.`); - } + const grantedPermissions: Array = []; - const body = await Deno.readAll(response.body); - const moduleText = decoder.decode(body); - console.log("Download complete."); + let shebang: { args: Array } | undefined; - const grantedPermissions: Array = []; + const line = moduleText.split("\n")[0]; - // TODO(bartlomieju) this whole shebang bit should be optional or at least require confirmation try { - const line = moduleText.split("\n")[0]; - const shebang = parseShebang(line); + shebang = parseShebang(line); + } catch (e) {} + + if (shebang) { + const requestedPermissions: Array = []; + console.log("ℹ️ Detected shebang:\n"); + console.log(` ${line}\n`); + console.log(" Requested permissions:\n"); for (const flag of shebang.args) { const permission = getPermissionFromFlag(flag); if (permission === Permission.Unknown) { continue; } - if (await grantPermission(permission, moduleName)) { - grantedPermissions.push(permission); - } + + console.log("\t" + flag); + requestedPermissions.push(permission); } - } catch (e) { + + console.log(); + + if (yesNoPrompt("⚠️ Grant?")) { + requestedPermissions.forEach((perm: Permission) => { + grantedPermissions.push(perm); + }); + } + } else { for (const flag of args.slice(2)) { const permission = getPermissionFromFlag(flag); if (permission === Permission.Unknown) { @@ -220,10 +244,11 @@ async function main() { const commands = [ "deno", ...grantedPermissions.map(getFlagFromPermission), - modulePath, + moduleUrl, "$@" ]; + // TODO: add windows Version const template = `#/bin/sh\n${commands.join(" ")}`; writeFile(FILE_PATH, encoder.encode(template)); @@ -231,7 +256,14 @@ async function main() { await makeExecutable.status(); makeExecutable.close(); - console.log(`Successfully installed ${moduleName}.`); + // TODO: display granted permissions + console.log(`✅ Successfully installed ${moduleName}.\n`); + // TODO: display this prompt only if `installerDir` not in PATH + // TODO: add Windows version + console.log("ℹ️ Add ~/.deno/bin to PATH"); + console.log( + " echo 'export PATH=\"$HOME/.deno/bin:$PATH\"' >> ~/.bashrc # change this to your shell" + ); } // TODO: refactor From 8435486e9b88813e3447e2b8cdfdde2a44c2753e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Thu, 13 Jun 2019 20:26:25 +0200 Subject: [PATCH 07/28] lint & fmt --- installer/mod.ts | 51 +++++++++----------------------------------- installer/shebang.ts | 6 +++--- 2 files changed, 13 insertions(+), 44 deletions(-) diff --git a/installer/mod.ts b/installer/mod.ts index 7a1ae66be3bf..15d2e2a0eb52 100644 --- a/installer/mod.ts +++ b/installer/mod.ts @@ -80,39 +80,7 @@ async function yesNoPrompt(message: string): Promise { return input === "y" || input === "Y"; } -async function grantPermission( - perm: Permission, - moduleName: string -): Promise { - // TODO: rewrite prompts - let msg = `⚠️ ${moduleName} requests `; - switch (perm) { - case Permission.Read: - msg += "read access to file system. "; - break; - case Permission.Write: - msg += "write access to file system. "; - break; - case Permission.Net: - msg += "network access. "; - break; - case Permission.Env: - msg += "access to environment variable. "; - break; - case Permission.Run: - msg += "access to run a subprocess. "; - break; - case Permission.All: - msg += "all available access. "; - break; - default: - return false; - } - msg += "Grant permanently?"; - return await yesNoPrompt(msg); -} - -function createDirIfNotExists(path: string) { +function createDirIfNotExists(path: string): void { try { readDirSync(path); } catch (e) { @@ -168,7 +136,7 @@ async function fetchModule(url: string): Promise { return decoder.decode(body); } -async function main() { +async function main(): void { let installerDir = getInstallerDir(); createDirIfNotExists(installerDir); @@ -198,9 +166,9 @@ async function main() { console.log(`Downloading: ${moduleUrl}\n`); const moduleText = await fetchModule(moduleUrl); - const grantedPermissions: Array = []; + const grantedPermissions: Permission[] = []; - let shebang: { args: Array } | undefined; + let shebang: { args: string[] } | undefined; const line = moduleText.split("\n")[0]; @@ -209,7 +177,7 @@ async function main() { } catch (e) {} if (shebang) { - const requestedPermissions: Array = []; + const requestedPermissions: Permission[] = []; console.log("ℹ️ Detected shebang:\n"); console.log(` ${line}\n`); console.log(" Requested permissions:\n"); @@ -227,9 +195,11 @@ async function main() { console.log(); if (yesNoPrompt("⚠️ Grant?")) { - requestedPermissions.forEach((perm: Permission) => { - grantedPermissions.push(perm); - }); + requestedPermissions.forEach( + (perm: Permission): void => { + grantedPermissions.push(perm); + } + ); } } else { for (const flag of args.slice(2)) { @@ -256,7 +226,6 @@ async function main() { await makeExecutable.status(); makeExecutable.close(); - // TODO: display granted permissions console.log(`✅ Successfully installed ${moduleName}.\n`); // TODO: display this prompt only if `installerDir` not in PATH // TODO: add Windows version diff --git a/installer/shebang.ts b/installer/shebang.ts index 60568b12ef2e..3021d875b41b 100644 --- a/installer/shebang.ts +++ b/installer/shebang.ts @@ -1,15 +1,15 @@ export interface Shebang { path: string; - args: Array; + args: string[]; } class ShebangImpl implements Shebang { public readonly path: string; - public readonly args: Array; + public readonly args: string[]; constructor(shebang: string) { const line = shebang.split("\n")[0]; - const parts = line.split(" ").map(s => s.trim()); + const parts = line.split(" ").map((s: string): string => s.trim()); const pathBase = parts.shift(); if (!pathBase.startsWith("#!")) { From fd4d530a5e44e29063a39546131021eb7737286c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Thu, 13 Jun 2019 22:02:19 +0200 Subject: [PATCH 08/28] remove shebang parsing --- installer/mod.ts | 52 ++++++++------------------------------------ installer/shebang.ts | 30 ------------------------- 2 files changed, 9 insertions(+), 73 deletions(-) delete mode 100644 installer/shebang.ts diff --git a/installer/mod.ts b/installer/mod.ts index 15d2e2a0eb52..180d8ebbb3ff 100644 --- a/installer/mod.ts +++ b/installer/mod.ts @@ -13,7 +13,6 @@ const { run } = Deno; import * as path from "../fs/path.ts"; -import { parse as parseShebang } from "./shebang.ts"; const encoder = new TextEncoder(); const decoder = new TextDecoder("utf-8"); @@ -136,7 +135,7 @@ async function fetchModule(url: string): Promise { return decoder.decode(body); } -async function main(): void { +async function main(): Promise { let installerDir = getInstallerDir(); createDirIfNotExists(installerDir); @@ -164,51 +163,18 @@ async function main(): void { } console.log(`Downloading: ${moduleUrl}\n`); - const moduleText = await fetchModule(moduleUrl); + // fetch module - this is done only to ensure that it actually exists + // we don't want to create programs that are not resolvable + await fetchModule(moduleUrl); const grantedPermissions: Permission[] = []; - let shebang: { args: string[] } | undefined; - - const line = moduleText.split("\n")[0]; - - try { - shebang = parseShebang(line); - } catch (e) {} - - if (shebang) { - const requestedPermissions: Permission[] = []; - console.log("ℹ️ Detected shebang:\n"); - console.log(` ${line}\n`); - console.log(" Requested permissions:\n"); - - for (const flag of shebang.args) { - const permission = getPermissionFromFlag(flag); - if (permission === Permission.Unknown) { - continue; - } - - console.log("\t" + flag); - requestedPermissions.push(permission); - } - - console.log(); - - if (yesNoPrompt("⚠️ Grant?")) { - requestedPermissions.forEach( - (perm: Permission): void => { - grantedPermissions.push(perm); - } - ); - } - } else { - for (const flag of args.slice(2)) { - const permission = getPermissionFromFlag(flag); - if (permission === Permission.Unknown) { - continue; - } - grantedPermissions.push(permission); + for (const flag of args.slice(2)) { + const permission = getPermissionFromFlag(flag); + if (permission === Permission.Unknown) { + continue; } + grantedPermissions.push(permission); } const commands = [ diff --git a/installer/shebang.ts b/installer/shebang.ts deleted file mode 100644 index 3021d875b41b..000000000000 --- a/installer/shebang.ts +++ /dev/null @@ -1,30 +0,0 @@ -export interface Shebang { - path: string; - args: string[]; -} - -class ShebangImpl implements Shebang { - public readonly path: string; - public readonly args: string[]; - - constructor(shebang: string) { - const line = shebang.split("\n")[0]; - const parts = line.split(" ").map((s: string): string => s.trim()); - const pathBase = parts.shift(); - - if (!pathBase.startsWith("#!")) { - throw new Error("Not a shebang."); - } - - this.path = pathBase.slice(2); - this.args = [...parts]; - } - - toString(): string { - return [`#!${this.path}`, ...this.args].join(" "); - } -} - -export function parse(shebang: string): Shebang { - return new ShebangImpl(shebang); -} From 4ff7fec6e42e50bfa8554b2245c8a508bfe0b319 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Thu, 13 Jun 2019 22:19:02 +0200 Subject: [PATCH 09/28] add help prompt --- installer/mod.ts | 52 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/installer/mod.ts b/installer/mod.ts index 180d8ebbb3ff..a076805db3fb 100644 --- a/installer/mod.ts +++ b/installer/mod.ts @@ -135,11 +135,20 @@ async function fetchModule(url: string): Promise { return decoder.decode(body); } -async function main(): Promise { +function showHelp(): void { + console.log(`USAGE: +deno https://deno.land/std/installer/mod.ts SCRIPT [FLAGS...] + +ARGS: + SCRIPT URL of script to install + [FLAGS...] List of flags for script + `); +} + +async function install(moduleUrl: string, flags: string[]): Promise { let installerDir = getInstallerDir(); createDirIfNotExists(installerDir); - const moduleUrl: string = args[1]; // TODO: handle local modules as well if (!moduleUrl.startsWith("http")) { throw new Error("Only remote modules are supported."); @@ -169,7 +178,7 @@ async function main(): Promise { const grantedPermissions: Permission[] = []; - for (const flag of args.slice(2)) { + for (const flag of flags) { const permission = getPermissionFromFlag(flag); if (permission === Permission.Unknown) { continue; @@ -192,7 +201,7 @@ async function main(): Promise { await makeExecutable.status(); makeExecutable.close(); - console.log(`✅ Successfully installed ${moduleName}.\n`); + console.log(`✅ Successfully installed ${moduleName}.\n`); // TODO: display this prompt only if `installerDir` not in PATH // TODO: add Windows version console.log("ℹ️ Add ~/.deno/bin to PATH"); @@ -201,15 +210,30 @@ async function main(): Promise { ); } -// TODO: refactor -try { - main(); -} catch (e) { - const err = e as Error; - if (err.message) { - console.log(err.message); - } else { - console.log(e); +async function main(): Promise { + if (args.length < 2) { + return showHelp(); + } + + const moduleUrl = args[1]; + const flags = args.slice(2); + + if (moduleUrl == "-h" || "--help") { + return showHelp(); + } + + // TODO: refactor + try { + await install(moduleUrl, flags); + } catch (e) { + const err = e as Error; + if (err.message) { + console.log(err.message); + } else { + console.log(e); + } + exit(1); } - exit(1); } + +main(); From ffd3af8236bfcc60f58cc6e460d488bd9673aab1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 14 Jun 2019 01:37:45 +0200 Subject: [PATCH 10/28] fix arg parsing --- installer/mod.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/installer/mod.ts b/installer/mod.ts index a076805db3fb..7214729956b3 100644 --- a/installer/mod.ts +++ b/installer/mod.ts @@ -7,7 +7,6 @@ const { mkdirSync, writeFile, exit, - readAll, stdin, stat, run @@ -103,7 +102,7 @@ async function fetchWithRedirects( url: string, redirectLimit: number = 10 // eslint-disable-next-line @typescript-eslint/no-explicit-any -): Promise { +): Promise { // TODO: `Response` is not exposed in global so 'any' const response = await fetch(url); @@ -123,7 +122,8 @@ function moduleNameFromPath(modulePath: string): string { return moduleName; } -async function fetchModule(url: string): Promise { +// eslint-disable-next-line @typescript-eslint/no-explicit-any +async function fetchModule(url: string): Promise { const response = await fetchWithRedirects(url); if (response.status !== 200) { @@ -218,7 +218,7 @@ async function main(): Promise { const moduleUrl = args[1]; const flags = args.slice(2); - if (moduleUrl == "-h" || "--help") { + if (["-h", "--help"].includes(moduleUrl)) { return showHelp(); } From 03311098fc20574bead6c17f5a3f7cf8e59fd1b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 14 Jun 2019 01:49:33 +0200 Subject: [PATCH 11/28] add uninstall command --- installer/mod.ts | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/installer/mod.ts b/installer/mod.ts index 7214729956b3..87e0c6d9b802 100644 --- a/installer/mod.ts +++ b/installer/mod.ts @@ -9,7 +9,9 @@ const { exit, stdin, stat, - run + readAll, + run, + remove } = Deno; import * as path from "../fs/path.ts"; @@ -102,7 +104,7 @@ async function fetchWithRedirects( url: string, redirectLimit: number = 10 // eslint-disable-next-line @typescript-eslint/no-explicit-any -): Promise { +): Promise { // TODO: `Response` is not exposed in global so 'any' const response = await fetch(url); @@ -146,7 +148,7 @@ ARGS: } async function install(moduleUrl: string, flags: string[]): Promise { - let installerDir = getInstallerDir(); + const installerDir = getInstallerDir(); createDirIfNotExists(installerDir); // TODO: handle local modules as well @@ -210,6 +212,23 @@ async function install(moduleUrl: string, flags: string[]): Promise { ); } +async function uninstall(moduleName: string): Promise { + const installerDir = getInstallerDir(); + const FILE_PATH = path.join(installerDir, moduleName); + + try { + await stat(FILE_PATH); + } catch (e) { + if (e instanceof Deno.DenoError && e.kind === Deno.ErrorKind.NotFound) { + console.error(`ℹ️ ${moduleName} not found`); + exit(1); + } + } + + await remove(FILE_PATH); + console.log(`ℹ️ Uninstalled ${moduleName}`); +} + async function main(): Promise { if (args.length < 2) { return showHelp(); @@ -218,6 +237,10 @@ async function main(): Promise { const moduleUrl = args[1]; const flags = args.slice(2); + if (moduleUrl === "uninstall") { + return await uninstall(args[2]); + } + if (["-h", "--help"].includes(moduleUrl)) { return showHelp(); } From aabd1911a9ff5b8df3d8fdd1b9be8f4517e66f1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 14 Jun 2019 01:57:16 +0200 Subject: [PATCH 12/28] don't show PATH prompt if dir in path --- installer/mod.ts | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/installer/mod.ts b/installer/mod.ts index 87e0c6d9b802..0a3439b16853 100644 --- a/installer/mod.ts +++ b/installer/mod.ts @@ -88,6 +88,14 @@ function createDirIfNotExists(path: string): void { } } +function checkIfExistsInPath(path: string) { + const { PATH } = env(); + + const paths = (PATH as string).split(":"); + + return paths.includes(path); +} + function getInstallerDir(): string { const { HOME } = env(); @@ -203,13 +211,14 @@ async function install(moduleUrl: string, flags: string[]): Promise { await makeExecutable.status(); makeExecutable.close(); - console.log(`✅ Successfully installed ${moduleName}.\n`); - // TODO: display this prompt only if `installerDir` not in PATH + console.log(`✅ Successfully installed ${moduleName}.`); // TODO: add Windows version - console.log("ℹ️ Add ~/.deno/bin to PATH"); - console.log( - " echo 'export PATH=\"$HOME/.deno/bin:$PATH\"' >> ~/.bashrc # change this to your shell" - ); + if (!checkIfExistsInPath(installerDir)) { + console.log("\nℹ️ Add ~/.deno/bin to PATH"); + console.log( + " echo 'export PATH=\"$HOME/.deno/bin:$PATH\"' >> ~/.bashrc # change this to your shell" + ); + } } async function uninstall(moduleName: string): Promise { From 86a6e4e768e5f5770d44b47cfc27519c65fcc8f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 14 Jun 2019 02:05:31 +0200 Subject: [PATCH 13/28] install local scripts --- installer/mod.ts | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/installer/mod.ts b/installer/mod.ts index 0a3439b16853..366ada6ee9bc 100644 --- a/installer/mod.ts +++ b/installer/mod.ts @@ -160,9 +160,9 @@ async function install(moduleUrl: string, flags: string[]): Promise { createDirIfNotExists(installerDir); // TODO: handle local modules as well - if (!moduleUrl.startsWith("http")) { - throw new Error("Only remote modules are supported."); - } + // if (!moduleUrl.startsWith("http")) { + // throw new Error("Only remote modules are supported."); + // } const moduleName = moduleNameFromPath(moduleUrl); const FILE_PATH = path.join(installerDir, moduleName); @@ -181,10 +181,17 @@ async function install(moduleUrl: string, flags: string[]): Promise { } } - console.log(`Downloading: ${moduleUrl}\n`); - // fetch module - this is done only to ensure that it actually exists - // we don't want to create programs that are not resolvable - await fetchModule(moduleUrl); + // ensure script that is being installed exists + if (moduleUrl.startsWith("http")) { + // remote module + console.log(`Downloading: ${moduleUrl}\n`); + await fetchModule(moduleUrl); + } else { + // assume that it's local file + moduleUrl = path.resolve(moduleUrl); + console.log(`Looking for: ${moduleUrl}\n`); + await stat(moduleUrl); + } const grantedPermissions: Permission[] = []; From 86bd5108396f462af92df187baade072f18f4f96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 14 Jun 2019 02:06:05 +0200 Subject: [PATCH 14/28] lint --- installer/mod.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/installer/mod.ts b/installer/mod.ts index 366ada6ee9bc..16ac56a3642f 100644 --- a/installer/mod.ts +++ b/installer/mod.ts @@ -88,7 +88,7 @@ function createDirIfNotExists(path: string): void { } } -function checkIfExistsInPath(path: string) { +function checkIfExistsInPath(path: string): boolean { const { PATH } = env(); const paths = (PATH as string).split(":"); From 1dda37e1470b165f8ca94f220f0f6991d15e74fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 14 Jun 2019 02:28:40 +0200 Subject: [PATCH 15/28] add simple test case --- installer/mod.ts | 13 ++++++--- installer/test.ts | 70 +++++++++++++++++++++++++++++++++++++++++++++++ test.ts | 1 + 3 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 installer/test.ts diff --git a/installer/mod.ts b/installer/mod.ts index 16ac56a3642f..40e65cf6eb83 100644 --- a/installer/mod.ts +++ b/installer/mod.ts @@ -84,7 +84,7 @@ function createDirIfNotExists(path: string): void { try { readDirSync(path); } catch (e) { - mkdirSync(path); + mkdirSync(path, true); } } @@ -155,7 +155,10 @@ ARGS: `); } -async function install(moduleUrl: string, flags: string[]): Promise { +export async function install( + moduleUrl: string, + flags: string[] +): Promise { const installerDir = getInstallerDir(); createDirIfNotExists(installerDir); @@ -228,7 +231,7 @@ async function install(moduleUrl: string, flags: string[]): Promise { } } -async function uninstall(moduleName: string): Promise { +export async function uninstall(moduleName: string): Promise { const installerDir = getInstallerDir(); const FILE_PATH = path.join(installerDir, moduleName); @@ -275,4 +278,6 @@ async function main(): Promise { } } -main(); +if (import.meta.main) { + main(); +} diff --git a/installer/test.ts b/installer/test.ts new file mode 100644 index 000000000000..04ce88e7b609 --- /dev/null +++ b/installer/test.ts @@ -0,0 +1,70 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +const { readFile, run, stat, makeTempDir, remove, env } = Deno; + +import { test, runIfMain } from "../testing/mod.ts"; +import { assert, assertEquals } from "../testing/asserts.ts"; +import { BufReader, EOF } from "../io/bufio.ts"; +import { TextProtoReader } from "../textproto/mod.ts"; +import { install, uninstall } from "./mod.ts"; +import * as path from "../fs/path.ts"; + +let fileServer: Deno.Process; + +// copied from `http/file_server_test.ts` +async function startFileServer(): Promise { + fileServer = run({ + args: [ + "deno", + "run", + "--allow-read", + "--allow-net", + "http/file_server.ts", + ".", + "--cors" + ], + stdout: "piped" + }); + // Once fileServer is ready it will write to its stdout. + const r = new TextProtoReader(new BufReader(fileServer.stdout!)); + const s = await r.readLine(); + assert(s !== EOF && s.includes("server listening")); +} + +function killFileServer(): void { + fileServer.close(); + fileServer.stdout!.close(); +} + +// TODO: create helper function with all setup +test(async function installerInstall() { + await startFileServer(); + const tempDir = await makeTempDir(); + const envVars = env(); + const originalHomeDir = envVars["HOME"]; + envVars["HOME"] = tempDir; + + try { + await install("http://localhost:4500/http/file_server.ts", [ + "--allow-net", + "--allow-read" + ]); + + const filePath = path.resolve(tempDir, ".deno/bin/file_server"); + const fileInfo = await stat(filePath); + assert(fileInfo.isFile()); + // TODO: verify that it's executable file + + const fileBytes = await readFile(filePath); + const fileContents = new TextDecoder().decode(fileBytes); + assertEquals( + fileContents, + "#/bin/sh\ndeno --allow-net --allow-read http://localhost:4500/http/file_server.ts $@" + ); + } finally { + killFileServer(); + await remove(tempDir, { recursive: true }); + envVars["HOME"] = originalHomeDir; + } +}); + +runIfMain(import.meta); diff --git a/test.ts b/test.ts index 864f1b511298..c1381cb27609 100755 --- a/test.ts +++ b/test.ts @@ -11,6 +11,7 @@ import "./flags/test.ts"; import "./fs/test.ts"; import "./http/test.ts"; import "./io/test.ts"; +import "./installer/test.ts"; import "./log/test.ts"; import "./media_types/test.ts"; import "./mime/test.ts"; From 0576c4cbbdb317c18f3de6f95ef43720a9a455e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 14 Jun 2019 02:35:16 +0200 Subject: [PATCH 16/28] lint --- installer/mod.ts | 2 +- installer/test.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/installer/mod.ts b/installer/mod.ts index 40e65cf6eb83..525a6e731407 100644 --- a/installer/mod.ts +++ b/installer/mod.ts @@ -118,7 +118,7 @@ async function fetchWithRedirects( if (response.status === 301 || response.status === 302) { if (redirectLimit > 0) { - const redirectUrl = response.headers.get("location"); + const redirectUrl = response.headers.get("location")!; return await fetchWithRedirects(redirectUrl, redirectLimit - 1); } } diff --git a/installer/test.ts b/installer/test.ts index 04ce88e7b609..f4d847777971 100644 --- a/installer/test.ts +++ b/installer/test.ts @@ -5,7 +5,7 @@ import { test, runIfMain } from "../testing/mod.ts"; import { assert, assertEquals } from "../testing/asserts.ts"; import { BufReader, EOF } from "../io/bufio.ts"; import { TextProtoReader } from "../textproto/mod.ts"; -import { install, uninstall } from "./mod.ts"; +import { install } from "./mod.ts"; import * as path from "../fs/path.ts"; let fileServer: Deno.Process; @@ -36,7 +36,7 @@ function killFileServer(): void { } // TODO: create helper function with all setup -test(async function installerInstall() { +test(async function installerInstall(): Promise { await startFileServer(); const tempDir = await makeTempDir(); const envVars = env(); From 75ab128c9cade5b88110d77736c9e198741dd12d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 14 Jun 2019 12:18:11 +0200 Subject: [PATCH 17/28] reset CI From cbd05ea77b7519e2c4bbd305bac911855773e07d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 14 Jun 2019 12:24:40 +0200 Subject: [PATCH 18/28] add env permission --- .ci/template.common.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/template.common.yml b/.ci/template.common.yml index 2d750c27b25b..76c98c639ab2 100644 --- a/.ci/template.common.yml +++ b/.ci/template.common.yml @@ -3,4 +3,4 @@ parameters: steps: - bash: deno${{ parameters.exe_suffix }} run --allow-run --allow-write --allow-read format.ts --check - - bash: deno${{ parameters.exe_suffix }} run --allow-run --allow-net --allow-write --allow-read --config=tsconfig.test.json test.ts \ No newline at end of file + - bash: deno${{ parameters.exe_suffix }} run --allow-run --allow-net --allow-write --allow-read --allow-env --config=tsconfig.test.json test.ts \ No newline at end of file From 29d891ed15e4070b19bb4c3df4e2d49e4d184b54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 14 Jun 2019 12:35:08 +0200 Subject: [PATCH 19/28] add debug statement --- installer/mod.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/installer/mod.ts b/installer/mod.ts index 525a6e731407..a6df190978ea 100644 --- a/installer/mod.ts +++ b/installer/mod.ts @@ -217,10 +217,22 @@ export async function install( const template = `#/bin/sh\n${commands.join(" ")}`; writeFile(FILE_PATH, encoder.encode(template)); + // TODO: remove just for test + try { + const fileInfo = await stat(FILE_PATH); + console.log(fileInfo, fileInfo.isFile()); + } catch (e) { + console.log("stat failed"); + } + const makeExecutable = run({ args: ["chmod", "+x", FILE_PATH] }); - await makeExecutable.status(); + const { code } = await makeExecutable.status(); makeExecutable.close(); + if (code !== 0) { + throw new Error("Failed to make file executable"); + } + console.log(`✅ Successfully installed ${moduleName}.`); // TODO: add Windows version if (!checkIfExistsInPath(installerDir)) { From 25b9ae51b908d45fd86738a12cd51b4996fe69ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 14 Jun 2019 12:51:51 +0200 Subject: [PATCH 20/28] remove debug statement --- installer/mod.ts | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/installer/mod.ts b/installer/mod.ts index a6df190978ea..c74d565a6807 100644 --- a/installer/mod.ts +++ b/installer/mod.ts @@ -162,11 +162,6 @@ export async function install( const installerDir = getInstallerDir(); createDirIfNotExists(installerDir); - // TODO: handle local modules as well - // if (!moduleUrl.startsWith("http")) { - // throw new Error("Only remote modules are supported."); - // } - const moduleName = moduleNameFromPath(moduleUrl); const FILE_PATH = path.join(installerDir, moduleName); @@ -217,14 +212,6 @@ export async function install( const template = `#/bin/sh\n${commands.join(" ")}`; writeFile(FILE_PATH, encoder.encode(template)); - // TODO: remove just for test - try { - const fileInfo = await stat(FILE_PATH); - console.log(fileInfo, fileInfo.isFile()); - } catch (e) { - console.log("stat failed"); - } - const makeExecutable = run({ args: ["chmod", "+x", FILE_PATH] }); const { code } = await makeExecutable.status(); makeExecutable.close(); From 84143ca7b65d7b30179c2d56082df47b3cd8499d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 14 Jun 2019 12:55:27 +0200 Subject: [PATCH 21/28] Add missing await --- installer/mod.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/installer/mod.ts b/installer/mod.ts index c74d565a6807..c8f56188cddd 100644 --- a/installer/mod.ts +++ b/installer/mod.ts @@ -210,7 +210,7 @@ export async function install( // TODO: add windows Version const template = `#/bin/sh\n${commands.join(" ")}`; - writeFile(FILE_PATH, encoder.encode(template)); + await writeFile(FILE_PATH, encoder.encode(template)); const makeExecutable = run({ args: ["chmod", "+x", FILE_PATH] }); const { code } = await makeExecutable.status(); From b7a703b21ca7bb9f28fb0c1d4fd2095a00727419 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 14 Jun 2019 13:17:39 +0200 Subject: [PATCH 22/28] properly parse script flags --- installer/mod.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/installer/mod.ts b/installer/mod.ts index c8f56188cddd..df693322e516 100644 --- a/installer/mod.ts +++ b/installer/mod.ts @@ -19,7 +19,6 @@ const encoder = new TextEncoder(); const decoder = new TextDecoder("utf-8"); enum Permission { - Unknown, Read, Write, Net, @@ -28,7 +27,7 @@ enum Permission { All } -function getPermissionFromFlag(flag: string): Permission { +function getPermissionFromFlag(flag: string): Permission | undefined { switch (flag) { case "--allow-read": return Permission.Read; @@ -45,7 +44,6 @@ function getPermissionFromFlag(flag: string): Permission { case "-A": return Permission.All; } - return Permission.Unknown; } function getFlagFromPermission(perm: Permission): string { @@ -192,19 +190,22 @@ export async function install( } const grantedPermissions: Permission[] = []; + const scriptArgs: string[] = []; for (const flag of flags) { const permission = getPermissionFromFlag(flag); - if (permission === Permission.Unknown) { + if (!permission) { + scriptArgs.push(flag); continue; } - grantedPermissions.push(permission); + grantedPermissions.push(permission!); } const commands = [ "deno", ...grantedPermissions.map(getFlagFromPermission), moduleUrl, + ...scriptArgs, "$@" ]; From 77c37db1ef0f3ee64b95f1e6895082ffbf1bd102 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 14 Jun 2019 13:33:11 +0200 Subject: [PATCH 23/28] add more tests for installer --- installer/mod.ts | 18 +++++--- installer/test.ts | 113 +++++++++++++++++++++++++++++++++------------- 2 files changed, 92 insertions(+), 39 deletions(-) diff --git a/installer/mod.ts b/installer/mod.ts index df693322e516..61fc106bdc7b 100644 --- a/installer/mod.ts +++ b/installer/mod.ts @@ -149,7 +149,7 @@ deno https://deno.land/std/installer/mod.ts SCRIPT [FLAGS...] ARGS: SCRIPT URL of script to install - [FLAGS...] List of flags for script + [FLAGS...] List of flags for script, both Deno permission and script specific flag can be used. `); } @@ -194,11 +194,11 @@ export async function install( for (const flag of flags) { const permission = getPermissionFromFlag(flag); - if (!permission) { + if (permission === undefined) { scriptArgs.push(flag); - continue; + } else { + grantedPermissions.push(permission); } - grantedPermissions.push(permission!); } const commands = [ @@ -239,8 +239,7 @@ export async function uninstall(moduleName: string): Promise { await stat(FILE_PATH); } catch (e) { if (e instanceof Deno.DenoError && e.kind === Deno.ErrorKind.NotFound) { - console.error(`ℹ️ ${moduleName} not found`); - exit(1); + throw new Error(`ℹ️ ${moduleName} not found`); } } @@ -257,7 +256,12 @@ async function main(): Promise { const flags = args.slice(2); if (moduleUrl === "uninstall") { - return await uninstall(args[2]); + try { + return await uninstall(args[2]); + } catch (e) { + console.error(e); + exit(1); + } } if (["-h", "--help"].includes(moduleUrl)) { diff --git a/installer/test.ts b/installer/test.ts index f4d847777971..b0c036654964 100644 --- a/installer/test.ts +++ b/installer/test.ts @@ -1,11 +1,11 @@ // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. const { readFile, run, stat, makeTempDir, remove, env } = Deno; -import { test, runIfMain } from "../testing/mod.ts"; -import { assert, assertEquals } from "../testing/asserts.ts"; +import { test, runIfMain, TestFunction } from "../testing/mod.ts"; +import { assert, assertEquals, assertThrowsAsync } from "../testing/asserts.ts"; import { BufReader, EOF } from "../io/bufio.ts"; import { TextProtoReader } from "../textproto/mod.ts"; -import { install } from "./mod.ts"; +import { install, uninstall } from "./mod.ts"; import * as path from "../fs/path.ts"; let fileServer: Deno.Process; @@ -35,36 +35,85 @@ function killFileServer(): void { fileServer.stdout!.close(); } -// TODO: create helper function with all setup -test(async function installerInstall(): Promise { - await startFileServer(); - const tempDir = await makeTempDir(); - const envVars = env(); - const originalHomeDir = envVars["HOME"]; - envVars["HOME"] = tempDir; +function installerTest(t: TestFunction): void { + const fn = async (): Promise => { + await startFileServer(); + const tempDir = await makeTempDir(); + const envVars = env(); + const originalHomeDir = envVars["HOME"]; + envVars["HOME"] = tempDir; - try { - await install("http://localhost:4500/http/file_server.ts", [ - "--allow-net", - "--allow-read" - ]); - - const filePath = path.resolve(tempDir, ".deno/bin/file_server"); - const fileInfo = await stat(filePath); - assert(fileInfo.isFile()); - // TODO: verify that it's executable file - - const fileBytes = await readFile(filePath); - const fileContents = new TextDecoder().decode(fileBytes); - assertEquals( - fileContents, - "#/bin/sh\ndeno --allow-net --allow-read http://localhost:4500/http/file_server.ts $@" - ); - } finally { - killFileServer(); - await remove(tempDir, { recursive: true }); - envVars["HOME"] = originalHomeDir; - } + try { + await t(); + } finally { + killFileServer(); + await remove(tempDir, { recursive: true }); + envVars["HOME"] = originalHomeDir; + } + }; + + test(fn); +} + +installerTest(async function installBasic(): Promise { + await install("http://localhost:4500/http/file_server.ts", []); + + const { HOME } = env(); + const filePath = path.resolve(HOME, ".deno/bin/file_server"); + const fileInfo = await stat(filePath); + assert(fileInfo.isFile()); + + const fileBytes = await readFile(filePath); + const fileContents = new TextDecoder().decode(fileBytes); + assertEquals( + fileContents, + "#/bin/sh\ndeno http://localhost:4500/http/file_server.ts $@" + ); +}); + +installerTest(async function installWithFlags(): Promise { + await install("http://localhost:4500/http/file_server.ts", [ + "--allow-net", + "--allow-read", + "--foobar" + ]); + + const { HOME } = env(); + const filePath = path.resolve(HOME, ".deno/bin/file_server"); + + const fileBytes = await readFile(filePath); + const fileContents = new TextDecoder().decode(fileBytes); + assertEquals( + fileContents, + "#/bin/sh\ndeno --allow-net --allow-read http://localhost:4500/http/file_server.ts --foobar $@" + ); +}); + +installerTest(async function uninstallBasic(): Promise { + await install("http://localhost:4500/http/file_server.ts", []); + + const { HOME } = env(); + const filePath = path.resolve(HOME, ".deno/bin/file_server"); + + await uninstall("file_server"); + + await assertThrowsAsync( + async (): Promise => { + await stat(filePath); + }, + Deno.DenoError, + "No such file" + ); +}); + +installerTest(async function uninstallNonExistentModule(): Promise { + await assertThrowsAsync( + async (): Promise => { + await uninstall("file_server"); + }, + Error, + "file_server not found" + ); }); runIfMain(import.meta); From 81030d6fca20f0999d1cd5bbb5bad77d0bfc350c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 14 Jun 2019 13:46:51 +0200 Subject: [PATCH 24/28] fix windows test --- installer/test.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/installer/test.ts b/installer/test.ts index b0c036654964..7dda07191492 100644 --- a/installer/test.ts +++ b/installer/test.ts @@ -97,13 +97,16 @@ installerTest(async function uninstallBasic(): Promise { await uninstall("file_server"); - await assertThrowsAsync( - async (): Promise => { - await stat(filePath); - }, - Deno.DenoError, - "No such file" - ); + let thrown = false; + try { + await stat(filePath); + } catch (e) { + thrown = true; + assert(e instanceof Deno.DenoError); + assertEquals(e.kind, Deno.ErrorKind.NotFound); + } + + assert(thrown); }); installerTest(async function uninstallNonExistentModule(): Promise { From b77746c91a6e63463491831f13be3ffb10abbf5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 14 Jun 2019 14:04:52 +0200 Subject: [PATCH 25/28] update README --- installer/README.md | 75 +++++++++++++++++++++++++++++++-------------- 1 file changed, 52 insertions(+), 23 deletions(-) diff --git a/installer/README.md b/installer/README.md index c7662a14e5ab..3e1a69ebcc52 100644 --- a/installer/README.md +++ b/installer/README.md @@ -1,45 +1,74 @@ # deno_install -- This command installs executable deno script. +Installs remote or local script as executable. -## Features +```` +## Installation -- Install executable script into ~/.deno/bin +`installer` can be install using iteself: + +```sh +deno -A https://deno.land/std/install/deno_install.ts https://deno.land/std/install/deno_install.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 -$ deno_install https://deno.land/std/http/file_server.ts -> file_server requests network access. Grant permanently? [yN] # Grant the permissions to use command. -> y -> Successfully installed file_server. -$ file_server # now you can use installed command! +$ deno_install https://deno.land/std/http/file_server.ts --allow-net --allow-read +> Downloading: https://deno.land/std/http/file_server.ts +> +> ✅ Successfully installed file_server. + +# local script +$ deno_install ./deno_std/http/file_server.ts --allow-net --allow-read +> Looking for: /dev/deno_std/http/file_server.ts +> +> ✅ Successfully installed file_server. ``` -## Installing +Use installed script: -### 1. Install deno_install +```sh +$ file_server +HTTP server listening on http://0.0.0.0:4500/ +``` -deno_install can be installed by using itself. +Update installed script ```sh -deno -A https://deno.land/std/install/deno_install.ts https://deno.land/std/install/deno_install.ts +$ deno_install 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 +> +> ✅ Successfully installed file_server. ``` -### 2. Add `~/.deno/bin` to PATH +Uninstall script +```sh +$ deno_install uninstall file_server +> ℹ️ Uninstalled file_server ``` -echo 'export PATH="$HOME/.deno/bin:$PATH"' >> ~/.bashrc # change this to your shell -``` - -## Create Executable Script -- Add shebang to top of your deno script. - - This defines what permissions are needed. +Display help ```sh -#!/usr/bin/env deno --allow-read --allow-write --allow-env --allow-run -``` +$ deno_install --help +> USAGE: +deno https://deno.land/std/installer/mod.ts SCRIPT [FLAGS...] -- Host script on the web. -- Install script using deno_install. +ARGS: + SCRIPT URL of script to install + [FLAGS...] List of flags for script, both Deno permission and script specific flag can be used. + +``` From 6137f6ee2bf969f09d233d807c769c939b2f92bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 14 Jun 2019 14:19:14 +0200 Subject: [PATCH 26/28] explicitly require name for installed executable --- installer/README.md | 30 +++++++++++++---------------- installer/mod.ts | 47 +++++++++++++++------------------------------ installer/test.ts | 8 ++++---- 3 files changed, 32 insertions(+), 53 deletions(-) diff --git a/installer/README.md b/installer/README.md index 3e1a69ebcc52..db60dd892dd3 100644 --- a/installer/README.md +++ b/installer/README.md @@ -1,6 +1,6 @@ # deno_install -Installs remote or local script as executable. +Install remote or local script as executables. ```` ## Installation @@ -8,7 +8,7 @@ Installs remote or local script as executable. `installer` can be install using iteself: ```sh -deno -A https://deno.land/std/install/deno_install.ts https://deno.land/std/install/deno_install.ts -A +deno -A https://deno.land/std/install/deno_install.ts deno_install https://deno.land/std/install/deno_install.ts -A ```` Installer uses `~/.deno/bin` to store installed scripts so make sure it's in `$PATH` @@ -22,13 +22,13 @@ echo 'export PATH="$HOME/.deno/bin:$PATH"' >> ~/.bashrc # change this to your sh Install script ```sh -$ deno_install https://deno.land/std/http/file_server.ts --allow-net --allow-read +$ deno_install file_server https://deno.land/std/http/file_server.ts --allow-net --allow-read > Downloading: https://deno.land/std/http/file_server.ts > > ✅ Successfully installed file_server. # local script -$ deno_install ./deno_std/http/file_server.ts --allow-net --allow-read +$ deno_install file_server ./deno_std/http/file_server.ts --allow-net --allow-read > Looking for: /dev/deno_std/http/file_server.ts > > ✅ Successfully installed file_server. @@ -44,7 +44,7 @@ HTTP server listening on http://0.0.0.0:4500/ Update installed script ```sh -$ deno_install https://deno.land/std/http/file_server.ts --allow-net --allow-read +$ deno_install 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 > @@ -53,22 +53,18 @@ $ deno_install https://deno.land/std/http/file_server.ts --allow-net --allow-rea > ✅ Successfully installed file_server. ``` -Uninstall script - -```sh -$ deno_install uninstall file_server -> ℹ️ Uninstalled file_server -``` - -Display help +Show help ```sh $ deno_install --help -> USAGE: -deno https://deno.land/std/installer/mod.ts SCRIPT [FLAGS...] +> deno installer + Install remote or local script as executables. + +USAGE: + deno https://deno.land/std/installer/mod.ts EXE_NAME SCRIPT_URL [FLAGS...] ARGS: - SCRIPT URL of script to install + 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. - ``` diff --git a/installer/mod.ts b/installer/mod.ts index 61fc106bdc7b..aff01f361644 100644 --- a/installer/mod.ts +++ b/installer/mod.ts @@ -124,12 +124,6 @@ async function fetchWithRedirects( return response; } -function moduleNameFromPath(modulePath: string): string { - let moduleName = path.basename(modulePath, ".ts"); - moduleName = path.basename(moduleName, ".js"); - return moduleName; -} - // eslint-disable-next-line @typescript-eslint/no-explicit-any async function fetchModule(url: string): Promise { const response = await fetchWithRedirects(url); @@ -144,23 +138,27 @@ async function fetchModule(url: string): Promise { } function showHelp(): void { - console.log(`USAGE: -deno https://deno.land/std/installer/mod.ts SCRIPT [FLAGS...] + 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: - SCRIPT URL of script to install + 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. `); } export async function install( + moduleName: string, moduleUrl: string, flags: string[] ): Promise { const installerDir = getInstallerDir(); createDirIfNotExists(installerDir); - const moduleName = moduleNameFromPath(moduleUrl); const FILE_PATH = path.join(installerDir, moduleName); let fileInfo; @@ -248,36 +246,21 @@ export async function uninstall(moduleName: string): Promise { } async function main(): Promise { - if (args.length < 2) { + if (args.length < 3) { return showHelp(); } - const moduleUrl = args[1]; - const flags = args.slice(2); - - if (moduleUrl === "uninstall") { - try { - return await uninstall(args[2]); - } catch (e) { - console.error(e); - exit(1); - } - } - - if (["-h", "--help"].includes(moduleUrl)) { + if (["-h", "--help"].includes(args[1])) { return showHelp(); } - // TODO: refactor + const moduleName = args[1]; + const moduleUrl = args[2]; + const flags = args.slice(3); try { - await install(moduleUrl, flags); + await install(moduleName, moduleUrl, flags); } catch (e) { - const err = e as Error; - if (err.message) { - console.log(err.message); - } else { - console.log(e); - } + console.log(e); exit(1); } } diff --git a/installer/test.ts b/installer/test.ts index 7dda07191492..1b1aa4200be2 100644 --- a/installer/test.ts +++ b/installer/test.ts @@ -56,10 +56,10 @@ function installerTest(t: TestFunction): void { } installerTest(async function installBasic(): Promise { - await install("http://localhost:4500/http/file_server.ts", []); + await install("file_srv", "http://localhost:4500/http/file_server.ts", []); const { HOME } = env(); - const filePath = path.resolve(HOME, ".deno/bin/file_server"); + const filePath = path.resolve(HOME, ".deno/bin/file_srv"); const fileInfo = await stat(filePath); assert(fileInfo.isFile()); @@ -72,7 +72,7 @@ installerTest(async function installBasic(): Promise { }); installerTest(async function installWithFlags(): Promise { - await install("http://localhost:4500/http/file_server.ts", [ + await install("file_server", "http://localhost:4500/http/file_server.ts", [ "--allow-net", "--allow-read", "--foobar" @@ -90,7 +90,7 @@ installerTest(async function installWithFlags(): Promise { }); installerTest(async function uninstallBasic(): Promise { - await install("http://localhost:4500/http/file_server.ts", []); + await install("file_server", "http://localhost:4500/http/file_server.ts", []); const { HOME } = env(); const filePath = path.resolve(HOME, ".deno/bin/file_server"); From 637a6f386baeb3373d12c29780310109ed84f3c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 14 Jun 2019 17:10:03 +0200 Subject: [PATCH 27/28] s/deno_install/deno_installer/ --- installer/README.md | 12 ++++++------ installer/{deno_install.ts => deno_installer.ts} | 0 2 files changed, 6 insertions(+), 6 deletions(-) rename installer/{deno_install.ts => deno_installer.ts} (100%) diff --git a/installer/README.md b/installer/README.md index db60dd892dd3..ed97f039770c 100644 --- a/installer/README.md +++ b/installer/README.md @@ -1,4 +1,4 @@ -# deno_install +# deno_installer Install remote or local script as executables. @@ -8,7 +8,7 @@ Install remote or local script as executables. `installer` can be install using iteself: ```sh -deno -A https://deno.land/std/install/deno_install.ts deno_install https://deno.land/std/install/deno_install.ts -A +deno -A https://deno.land/std/installer/deno_installer.ts deno_installer https://deno.land/std/installer/deno_installer.ts -A ```` Installer uses `~/.deno/bin` to store installed scripts so make sure it's in `$PATH` @@ -22,13 +22,13 @@ echo 'export PATH="$HOME/.deno/bin:$PATH"' >> ~/.bashrc # change this to your sh Install script ```sh -$ deno_install file_server https://deno.land/std/http/file_server.ts --allow-net --allow-read +$ 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 > > ✅ Successfully installed file_server. # local script -$ deno_install file_server ./deno_std/http/file_server.ts --allow-net --allow-read +$ deno_installer file_server ./deno_std/http/file_server.ts --allow-net --allow-read > Looking for: /dev/deno_std/http/file_server.ts > > ✅ Successfully installed file_server. @@ -44,7 +44,7 @@ HTTP server listening on http://0.0.0.0:4500/ Update installed script ```sh -$ deno_install file_server https://deno.land/std/http/file_server.ts --allow-net --allow-read +$ 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 > @@ -56,7 +56,7 @@ $ deno_install file_server https://deno.land/std/http/file_server.ts --allow-net Show help ```sh -$ deno_install --help +$ deno_installer --help > deno installer Install remote or local script as executables. diff --git a/installer/deno_install.ts b/installer/deno_installer.ts similarity index 100% rename from installer/deno_install.ts rename to installer/deno_installer.ts From 09ac618a8373b71bf19c150f83031e0dbc20dec5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 14 Jun 2019 17:15:14 +0200 Subject: [PATCH 28/28] remove installer/deno_installer.ts --- installer/README.md | 2 +- installer/deno_installer.ts | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 installer/deno_installer.ts diff --git a/installer/README.md b/installer/README.md index ed97f039770c..309364fbcf5f 100644 --- a/installer/README.md +++ b/installer/README.md @@ -8,7 +8,7 @@ Install remote or local script as executables. `installer` can be install using iteself: ```sh -deno -A https://deno.land/std/installer/deno_installer.ts deno_installer https://deno.land/std/installer/deno_installer.ts -A +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` diff --git a/installer/deno_installer.ts b/installer/deno_installer.ts deleted file mode 100644 index 175f6dcb8f62..000000000000 --- a/installer/deno_installer.ts +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env deno --allow-all -// This file was added to install `installer/mod.ts` with the name `deno_install`. -import "./mod.ts";