-
Notifications
You must be signed in to change notification settings - Fork 623
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
3ec3a8b
commit a3015be
Showing
5 changed files
with
464 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
# deno_installer | ||
|
||
Install remote or local script as executables. | ||
|
||
```` | ||
## Installation | ||
`installer` can be install using iteself: | ||
```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 | ||
$ 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_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. | ||
``` | ||
|
||
Use installed script: | ||
|
||
```sh | ||
$ file_server | ||
HTTP server listening on http://0.0.0.0:4500/ | ||
``` | ||
|
||
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 | ||
> | ||
> ✅ Successfully installed file_server. | ||
``` | ||
|
||
Show help | ||
|
||
```sh | ||
$ deno_installer --help | ||
> 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. | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,270 @@ | ||
#!/usr/bin/env deno --allow-all | ||
|
||
const { | ||
args, | ||
env, | ||
readDirSync, | ||
mkdirSync, | ||
writeFile, | ||
exit, | ||
stdin, | ||
stat, | ||
readAll, | ||
run, | ||
remove | ||
} = Deno; | ||
import * as path from "../fs/path.ts"; | ||
|
||
const encoder = new TextEncoder(); | ||
const decoder = new TextDecoder("utf-8"); | ||
|
||
enum Permission { | ||
Read, | ||
Write, | ||
Net, | ||
Env, | ||
Run, | ||
All | ||
} | ||
|
||
function getPermissionFromFlag(flag: string): Permission | undefined { | ||
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; | ||
} | ||
} | ||
|
||
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 byteArray = new Uint8Array(1024); | ||
await stdin.read(byteArray); | ||
const line = decoder.decode(byteArray); | ||
return line[0]; | ||
} | ||
|
||
async function yesNoPrompt(message: string): Promise<boolean> { | ||
console.log(`${message} [yN]`); | ||
const input = await readCharacter(); | ||
console.log(); | ||
return input === "y" || input === "Y"; | ||
} | ||
|
||
function createDirIfNotExists(path: string): void { | ||
try { | ||
readDirSync(path); | ||
} catch (e) { | ||
mkdirSync(path, true); | ||
} | ||
} | ||
|
||
function checkIfExistsInPath(path: string): boolean { | ||
const { PATH } = env(); | ||
|
||
const paths = (PATH as string).split(":"); | ||
|
||
return paths.includes(path); | ||
} | ||
|
||
function getInstallerDir(): string { | ||
const { HOME } = env(); | ||
|
||
if (!HOME) { | ||
throw new Error("$HOME is not defined."); | ||
} | ||
|
||
return path.join(HOME, ".deno", "bin"); | ||
} | ||
|
||
// 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<any> { | ||
// TODO: `Response` is not exposed in global so 'any' | ||
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; | ||
} | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
async function fetchModule(url: string): Promise<any> { | ||
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); | ||
} | ||
|
||
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. | ||
`); | ||
} | ||
|
||
export async function install( | ||
moduleName: string, | ||
moduleUrl: string, | ||
flags: string[] | ||
): Promise<void> { | ||
const installerDir = getInstallerDir(); | ||
createDirIfNotExists(installerDir); | ||
|
||
const FILE_PATH = path.join(installerDir, moduleName); | ||
|
||
let fileInfo; | ||
try { | ||
fileInfo = await stat(FILE_PATH); | ||
} catch (e) { | ||
// pass | ||
} | ||
|
||
if (fileInfo) { | ||
const msg = `⚠️ ${moduleName} is already installed, do you want to overwrite it?`; | ||
if (!(await yesNoPrompt(msg))) { | ||
return; | ||
} | ||
} | ||
|
||
// 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[] = []; | ||
const scriptArgs: string[] = []; | ||
|
||
for (const flag of flags) { | ||
const permission = getPermissionFromFlag(flag); | ||
if (permission === undefined) { | ||
scriptArgs.push(flag); | ||
} else { | ||
grantedPermissions.push(permission); | ||
} | ||
} | ||
|
||
const commands = [ | ||
"deno", | ||
...grantedPermissions.map(getFlagFromPermission), | ||
moduleUrl, | ||
...scriptArgs, | ||
"$@" | ||
]; | ||
|
||
// TODO: add windows Version | ||
const template = `#/bin/sh\n${commands.join(" ")}`; | ||
await writeFile(FILE_PATH, encoder.encode(template)); | ||
|
||
const makeExecutable = run({ args: ["chmod", "+x", FILE_PATH] }); | ||
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)) { | ||
console.log("\nℹ️ Add ~/.deno/bin to PATH"); | ||
console.log( | ||
" echo 'export PATH=\"$HOME/.deno/bin:$PATH\"' >> ~/.bashrc # change this to your shell" | ||
); | ||
} | ||
} | ||
|
||
export async function uninstall(moduleName: string): Promise<void> { | ||
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) { | ||
throw new Error(`ℹ️ ${moduleName} not found`); | ||
} | ||
} | ||
|
||
await remove(FILE_PATH); | ||
console.log(`ℹ️ Uninstalled ${moduleName}`); | ||
} | ||
|
||
async function main(): Promise<void> { | ||
if (args.length < 3) { | ||
return showHelp(); | ||
} | ||
|
||
if (["-h", "--help"].includes(args[1])) { | ||
return showHelp(); | ||
} | ||
|
||
const moduleName = args[1]; | ||
const moduleUrl = args[2]; | ||
const flags = args.slice(3); | ||
try { | ||
await install(moduleName, moduleUrl, flags); | ||
} catch (e) { | ||
console.log(e); | ||
exit(1); | ||
} | ||
} | ||
|
||
if (import.meta.main) { | ||
main(); | ||
} |
Oops, something went wrong.