Skip to content

Commit

Permalink
refactor(command): refactor executable sub-commands (#259)
Browse files Browse the repository at this point in the history
  • Loading branch information
c4spar committed Aug 3, 2021
1 parent 4f05fad commit a13c79f
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 143 deletions.
6 changes: 2 additions & 4 deletions command/_errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,11 +133,9 @@ export class DefaultCommandNotFound extends CommandError {
}

export class CommandExecutableNotFound extends CommandError {
constructor(name: string, files: Array<string>) {
constructor(name: string) {
super(
`Command executable not found: ${name}:\n - ${
files.join("\\n - ")
}`,
`Command executable not found: ${name}`,
);
Object.setPrototypeOf(this, CommandExecutableNotFound.prototype);
}
Expand Down
31 changes: 0 additions & 31 deletions command/_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,6 @@ export type PermissionName =
| "plugin"
| "hrtime";

export function getPermissions(): Promise<Record<PermissionName, boolean>> {
return hasPermissions([
"env",
"hrtime",
"net",
"plugin",
"read",
"run",
"write",
]);
}

export function isUnstable(): boolean {
// deno-lint-ignore no-explicit-any
return !!(Deno as any).permissions;
}

export function didYouMeanCommand(
command: string,
commands: Array<Command>,
Expand All @@ -56,20 +39,6 @@ export async function hasPermission(
}
}

async function hasPermissions<K extends PermissionName>(
names: K[],
): Promise<Record<K, boolean>> {
const permissions: Record<string, boolean> = {};
await Promise.all(
names.map((name: K) =>
hasPermission(name).then((hasPermission) =>
permissions[name] = hasPermission
)
),
);
return permissions as Record<K, boolean>;
}

const ARGUMENT_REGEX = /^[<\[].+[\]>]$/;
const ARGUMENT_DETAILS_REGEX = /[<\[:>\]]/;

Expand Down
135 changes: 27 additions & 108 deletions command/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,10 @@ import {
import { parseFlags } from "../flags/flags.ts";
import type { IFlagOptions, IFlagsResult } from "../flags/types.ts";
import {
getPermissions,
hasPermission,
isUnstable,
parseArgumentsDefinition,
splitArguments,
} from "./_utils.ts";
import type { PermissionName } from "./_utils.ts";
import { bold, red } from "./deps.ts";
import {
CommandExecutableNotFound,
Expand Down Expand Up @@ -852,45 +849,34 @@ export class Command<
/**
* Parse command line arguments and execute matched command.
* @param args Command line args to parse. Ex: `cmd.parse( Deno.args )`
* @param dry Execute command after parsed.
*/
public async parse(
args: string[] = Deno.args,
dry?: boolean,
): Promise<IParseResult<CO, CA, CG, PG, P>> {
try {
this.reset();
this.registerDefaults();
this.rawArgs = args;
const subCommand = args.length > 0 &&
this.getCommand(args[0], true);

if (subCommand) {
subCommand._globalParent = this;
return await subCommand.parse(
this.rawArgs.slice(1),
dry,
) as IParseResult<CO, CA, CG, PG, P>;
}

const result: IParseResult<CO, CA, CG, PG, P> = {
options: {} as PG & CG & CO,
args: this.rawArgs as CA,
cmd: this,
literal: this.literalArgs,
};

if (this.isExecutable) {
if (!dry) {
await this.executeExecutable(this.rawArgs);
if (args.length > 0) {
const subCommand = this.getCommand(args[0], true);
if (subCommand) {
subCommand._globalParent = this;
return subCommand.parse(
this.rawArgs.slice(1),
);
}
}

return result;
if (this.isExecutable) {
await this.executeExecutable(this.rawArgs);
return {
options: {} as PG & CG & CO,
args: [] as unknown as CA,
cmd: this,
literal: [],
};
} else if (this._useRawArgs) {
if (dry) {
return result;
}

return await this.execute({} as PG & CG & CO, ...this.rawArgs as CA);
} else {
const { action, flags, unknown, literal } = this.parseFlags(
Expand All @@ -900,13 +886,10 @@ export class Command<
this.literalArgs = literal;

const params = this.parseArguments(unknown, flags);

await this.validateEnvVars();

if (dry || action) {
if (action) {
await action.call(this, flags, ...params);
}
if (action) {
await action.call(this, flags, ...params);
return {
options: flags as PG & CG & CO,
args: params,
Expand Down Expand Up @@ -1017,88 +1000,24 @@ export class Command<
* @param args Raw command line arguments.
*/
protected async executeExecutable(args: string[]) {
const permissions = await getPermissions();
if (!permissions.read) {
// deno-lint-ignore no-explicit-any
await (Deno as any).permissions?.request({ name: "read" });
}
if (!permissions.run) {
// deno-lint-ignore no-explicit-any
await (Deno as any).permissions?.request({ name: "run" });
}

const [main, ...names] = this.getPath().split(" ");

names.unshift(main.replace(/\.ts$/, ""));

const executableName = names.join("-");
const files: string[] = [];

// deno-lint-ignore no-explicit-any
const parts: string[] = (Deno as any).mainModule.replace(/^file:\/\//g, "")
.split("/");
if (Deno.build.os === "windows" && parts[0] === "") {
parts.shift();
}
parts.pop();
const path: string = parts.join("/");
files.push(
path + "/" + executableName,
path + "/" + executableName + ".ts",
);

files.push(
executableName,
executableName + ".ts",
);

const denoOpts = [];

if (isUnstable()) {
denoOpts.push("--unstable");
}

denoOpts.push(
"--allow-read",
"--allow-run",
);

(Object.keys(permissions) as PermissionName[])
.forEach((name: PermissionName) => {
if (name === "read" || name === "run") {
return;
}
if (permissions[name]) {
denoOpts.push(`--allow-${name}`);
}
});

for (const file of files) {
try {
Deno.lstatSync(file);
} catch (error) {
if (error instanceof Deno.errors.NotFound) {
continue;
}
throw error;
}
const command = this.getPath().replace(/\s+/g, "-");

const cmd = ["deno", "run", ...denoOpts, file, ...args];
await Deno.permissions.request({ name: "run", command });

try {
const process: Deno.Process = Deno.run({
cmd: cmd,
cmd: [command, ...args],
});

const status: Deno.ProcessStatus = await process.status();

if (!status.success) {
Deno.exit(status.code);
}

return;
} catch (error) {
if (error instanceof Deno.errors.NotFound) {
throw new CommandExecutableNotFound(command);
}
throw error;
}

throw new CommandExecutableNotFound(executableName, files);
}

/**
Expand Down

0 comments on commit a13c79f

Please sign in to comment.