Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: installer #489

Merged
merged 28 commits into from
Jun 14, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
c689f8e
Add installer
syumai Jun 9, 2019
38aea3d
Update README of deno_install
syumai Jun 9, 2019
8f78c9d
replace wget with fetch
bartlomieju Jun 13, 2019
c865227
more prompts, handle situation without shebang, prompt on overwrite
bartlomieju Jun 13, 2019
b64f0a6
better prompt
bartlomieju Jun 13, 2019
a8d0f63
even better prompt
bartlomieju Jun 13, 2019
8435486
lint & fmt
bartlomieju Jun 13, 2019
fd4d530
remove shebang parsing
bartlomieju Jun 13, 2019
4ff7fec
add help prompt
bartlomieju Jun 13, 2019
ffd3af8
fix arg parsing
bartlomieju Jun 13, 2019
0331109
add uninstall command
bartlomieju Jun 13, 2019
aabd191
don't show PATH prompt if dir in path
bartlomieju Jun 13, 2019
86a6e4e
install local scripts
bartlomieju Jun 14, 2019
86bd510
lint
bartlomieju Jun 14, 2019
1dda37e
add simple test case
bartlomieju Jun 14, 2019
0576c4c
lint
bartlomieju Jun 14, 2019
75ab128
reset CI
bartlomieju Jun 14, 2019
cbd05ea
add env permission
bartlomieju Jun 14, 2019
29d891e
add debug statement
bartlomieju Jun 14, 2019
25b9ae5
remove debug statement
bartlomieju Jun 14, 2019
84143ca
Add missing await
bartlomieju Jun 14, 2019
b7a703b
properly parse script flags
bartlomieju Jun 14, 2019
77c37db
add more tests for installer
bartlomieju Jun 14, 2019
81030d6
fix windows test
bartlomieju Jun 14, 2019
b77746c
update README
bartlomieju Jun 14, 2019
6137f6e
explicitly require name for installed executable
bartlomieju Jun 14, 2019
637a6f3
s/deno_install/deno_installer/
bartlomieju Jun 14, 2019
09ac618
remove installer/deno_installer.ts
bartlomieju Jun 14, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Add installer
  • Loading branch information
syumai authored and bartlomieju committed Jun 13, 2019
commit c689f8ef82766c54c22217c6d4145102370e5c85
47 changes: 47 additions & 0 deletions installer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# deno_install
bartlomieju marked this conversation as resolved.
Show resolved Hide resolved

- 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
```
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems when I run it with no arguments, there's no error message or help text:

~/src/deno_std> deno -A installer/deno_installer.ts
[1/1] Compiling file:https:///Users/rld/src/deno_std/installer/deno_installer.ts
~/src/deno_std>

Copy link
Member Author

@bartlomieju bartlomieju Jun 14, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yikes, can we just drop installer/deno_installer.ts and leave installer/mod.ts?

Removed installer/deno_installer.ts it's not needed anymore - previously it was discovering module name from path, but now it's explicitly passed as an arg.

Please try deno -A installer/mod.ts


- Host script on the web.
- Install script using deno_install.
3 changes: 3 additions & 0 deletions installer/deno_install.ts
Original file line number Diff line number Diff line change
@@ -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';
195 changes: 195 additions & 0 deletions installer/mod.ts
Original file line number Diff line number Diff line change
@@ -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<string> {
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<boolean> {
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<Permission> = [];
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);
}
29 changes: 29 additions & 0 deletions installer/shebang.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export interface Shebang {
path: string;
args: Array<string>;
}

class ShebangImpl implements Shebang {
public readonly path: string;
public readonly args: Array<string>;
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);
}