Skip to content

Commit

Permalink
feat(cordova): check for native-run before running
Browse files Browse the repository at this point in the history
  • Loading branch information
imhoffd committed Apr 24, 2019
1 parent e60b701 commit 89ffd21
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 21 deletions.
10 changes: 5 additions & 5 deletions packages/ionic/src/commands/cordova/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export const CORDOVA_BUILD_EXAMPLE_COMMANDS = [
export abstract class CordovaCommand extends Command {
private _integration?: Required<ProjectIntegration>;

get integration(): Required<ProjectIntegration> {
protected get integration(): Required<ProjectIntegration> {
if (!this.project) {
throw new FatalException(`Cannot use Cordova outside a project directory.`);
}
Expand All @@ -90,7 +90,7 @@ export abstract class CordovaCommand extends Command {
return this._integration;
}

async checkCordova(runinfo: CommandInstanceInfo) {
protected async checkCordova(runinfo: CommandInstanceInfo) {
if (!this.project) {
throw new FatalException('Cannot use Cordova outside a project directory.');
}
Expand All @@ -102,7 +102,7 @@ export abstract class CordovaCommand extends Command {
}
}

async preRunChecks(runinfo: CommandInstanceInfo) {
protected async preRunChecks(runinfo: CommandInstanceInfo) {
if (!this.project) {
throw new FatalException('Cannot use Cordova outside a project directory.');
}
Expand Down Expand Up @@ -130,7 +130,7 @@ export abstract class CordovaCommand extends Command {
await conf.save();
}

async runCordova(argList: string[], { fatalOnNotFound = false, truncateErrorOutput = 5000, ...options }: IShellRunOptions = {}): Promise<void> {
protected async runCordova(argList: string[], { fatalOnNotFound = false, truncateErrorOutput = 5000, ...options }: IShellRunOptions = {}): Promise<void> {
if (!this.project) {
throw new FatalException('Cannot use Cordova outside a project directory.');
}
Expand Down Expand Up @@ -161,7 +161,7 @@ export abstract class CordovaCommand extends Command {
}
}

async checkForPlatformInstallation(platform: string, { promptToInstall = false, promptToInstallRefusalMsg = `Cannot run this command for the ${input(platform)} platform unless it is installed.` }: { promptToInstall?: boolean; promptToInstallRefusalMsg?: string; } = {}): Promise<void> {
protected async checkForPlatformInstallation(platform: string, { promptToInstall = false, promptToInstallRefusalMsg = `Cannot run this command for the ${input(platform)} platform unless it is installed.` }: { promptToInstall?: boolean; promptToInstallRefusalMsg?: string; } = {}): Promise<void> {
if (!this.project) {
throw new FatalException('Cannot use Cordova outside a project directory.');
}
Expand Down
14 changes: 11 additions & 3 deletions packages/ionic/src/commands/cordova/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { FatalException } from '../../lib/errors';
import { loadConfigXml } from '../../lib/integrations/cordova/config';
import { getPackagePath } from '../../lib/integrations/cordova/project';
import { filterArgumentsForCordova, generateOptionsForCordovaBuild } from '../../lib/integrations/cordova/utils';
import { SUPPORTED_PLATFORMS, createNativeRunArgs, createNativeRunListArgs, runNativeRun } from '../../lib/native-run';
import { SUPPORTED_PLATFORMS, checkNativeRun, createNativeRunArgs, createNativeRunListArgs, runNativeRun } from '../../lib/native-run';
import { COMMON_SERVE_COMMAND_OPTIONS, LOCAL_ADDRESSES, serve } from '../../lib/serve';
import { createPrefixedWriteStream } from '../../lib/utils/logger';

Expand Down Expand Up @@ -148,6 +148,10 @@ Just like with ${input('ionic cordova build')}, you can pass additional options
}

async preRun(inputs: CommandLineInputs, options: CommandLineOptions, runinfo: CommandInstanceInfo): Promise<void> {
if (options['native-run']) {
await this.checkNativeRun();
}

await this.preRunChecks(runinfo);

const metadata = await this.getMetadata();
Expand Down Expand Up @@ -216,7 +220,7 @@ Just like with ${input('ionic cordova build')}, you can pass additional options
}
}

async runServeDeploy(inputs: CommandLineInputs, options: CommandLineOptions) {
protected async runServeDeploy(inputs: CommandLineInputs, options: CommandLineOptions) {
if (!this.project) {
throw new FatalException(`Cannot run ${input('ionic cordova run/emulate')} outside a project directory.`);
}
Expand Down Expand Up @@ -267,7 +271,7 @@ Just like with ${input('ionic cordova build')}, you can pass additional options
}
}

async runBuildDeploy(inputs: CommandLineInputs, options: CommandLineOptions) {
protected async runBuildDeploy(inputs: CommandLineInputs, options: CommandLineOptions) {
if (!this.project) {
throw new FatalException(`Cannot run ${input('ionic cordova run/emulate')} outside a project directory.`);
}
Expand All @@ -291,6 +295,10 @@ Just like with ${input('ionic cordova build')}, you can pass additional options
}
}

protected async checkNativeRun(): Promise<void> {
await checkNativeRun(this.env);
}

protected async runNativeRun(args: readonly string[]): Promise<void> {
if (!this.project) {
throw new FatalException(`Cannot run ${input('ionic cordova run/emulate')} outside a project directory.`);
Expand Down
3 changes: 2 additions & 1 deletion packages/ionic/src/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
PromptModule,
} from '@ionic/cli-framework';
import { NetworkInterface } from '@ionic/utils-network';
import { SubprocessOptions } from '@ionic/utils-subprocess';
import { SubprocessOptions, WhichOptions } from '@ionic/utils-subprocess';
import { ChildProcess, SpawnOptions } from 'child_process';
import * as fs from 'fs';

Expand Down Expand Up @@ -384,6 +384,7 @@ export interface IShell {
output(command: string, args: readonly string[], options: IShellOutputOptions): Promise<string>;
spawn(command: string, args: readonly string[], options: IShellSpawnOptions): Promise<ChildProcess>;
cmdinfo(cmd: string, args?: readonly string[], options?: SubprocessOptions): Promise<string | undefined>;
which(command: string, options?: WhichOptions): Promise<string>;
}

export interface ITelemetry {
Expand Down
41 changes: 31 additions & 10 deletions packages/ionic/src/lib/native-run.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { processExit } from '@ionic/utils-process';
import { ERROR_COMMAND_NOT_FOUND, SubprocessError } from '@ionic/utils-subprocess';
import { ERROR_COMMAND_NOT_FOUND, SubprocessError, which } from '@ionic/utils-subprocess';

import { CommandLineOptions, IConfig, ILogger, IShell, IShellRunOptions } from '../definitions';
import { CommandLineOptions, IConfig, ILogger, IShell, IShellRunOptions, NpmClient } from '../definitions';

import { input, weak } from './color';
import { FatalException } from './errors';
Expand Down Expand Up @@ -68,9 +68,9 @@ export function createNativeRunListArgs(inputs: string[], options: CommandLineOp
}

export interface RunNativeRunDeps {
config: IConfig;
log: ILogger;
shell: IShell;
readonly config: IConfig;
readonly log: ILogger;
readonly shell: IShell;
}

export async function runNativeRun({ config, log, shell }: RunNativeRunDeps, args: readonly string[], options: IShellRunOptions = {}): Promise<void> {
Expand All @@ -81,11 +81,7 @@ export async function runNativeRun({ config, log, shell }: RunNativeRunDeps, arg
await shell.run('native-run', args, { showCommand: !args.includes('--json'), fatalOnNotFound: false, stream, ...options });
} catch (e) {
if (e instanceof SubprocessError && e.code === ERROR_COMMAND_NOT_FOUND) {
const installArgs = await pkgManagerArgs(config.get('npmClient'), { command: 'install', pkg: 'native-run', global: true });
throw new FatalException(
`${input('native-run')} was not found on your PATH. Please install it globally:\n` +
`${input(installArgs.join(' '))}\n`
);
throw createNativeRunNotFoundError(config.get('npmClient'));
}

throw e;
Expand All @@ -98,3 +94,28 @@ export async function runNativeRun({ config, log, shell }: RunNativeRunDeps, arg
processExit(0); // tslint:disable-line:no-floating-promises
}
}

export interface CheckNativeRunDeps {
readonly config: IConfig;
}

export async function checkNativeRun({ config }: CheckNativeRunDeps): Promise<void> {
try {
await which('native-run');
} catch (e) {
if (e.code === 'ENOENT') {
throw await createNativeRunNotFoundError(config.get('npmClient'));
}

throw e;
}
}

async function createNativeRunNotFoundError(npmClient: NpmClient): Promise<FatalException> {
const installArgs = await pkgManagerArgs(npmClient, { command: 'install', pkg: 'native-run', global: true });

return new FatalException(
`${input('native-run')} was not found on your PATH. Please install it globally:\n` +
`${input(installArgs.join(' '))}\n`
);
}
8 changes: 6 additions & 2 deletions packages/ionic/src/lib/shell.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { LOGGER_LEVELS } from '@ionic/cli-framework';
import { createProcessEnv, killProcessTree, onBeforeExit } from '@ionic/utils-process';
import { combineStreams } from '@ionic/utils-stream';
import { ERROR_COMMAND_NOT_FOUND, Subprocess, SubprocessError, which } from '@ionic/utils-subprocess';
import { ERROR_COMMAND_NOT_FOUND, Subprocess, SubprocessError, WhichOptions, which } from '@ionic/utils-subprocess';
import { TERMINAL_INFO } from '@ionic/utils-terminal';
import chalk from 'chalk';
import { ChildProcess, SpawnOptions } from 'child_process';
Expand Down Expand Up @@ -175,7 +175,7 @@ export class Shell implements IShell {
async resolveCommandPath(command: string, options: SpawnOptions): Promise<string> {
if (TERMINAL_INFO.windows) {
try {
return await which(command, { PATH: options.env.PATH });
return await this.which(command, { PATH: options.env && options.env.PATH ? options.env.PATH : process.env.PATH });
} catch (e) {
// ignore
}
Expand All @@ -184,6 +184,10 @@ export class Shell implements IShell {
return command;
}

async which(command: string, { PATH = process.env.PATH }: WhichOptions = {}): Promise<string> {
return which(command, { PATH: this.alterPath(PATH || '') });
}

async spawn(command: string, args: string[], { showCommand = true, ...crossSpawnOptions }: IShellSpawnOptions): Promise<ChildProcess> {
this.prepareSpawnOptions(crossSpawnOptions);

Expand Down

0 comments on commit 89ffd21

Please sign in to comment.