Skip to content

Commit 89ffd21

Browse files
committed
feat(cordova): check for native-run before running
1 parent e60b701 commit 89ffd21

File tree

5 files changed

+55
-21
lines changed

5 files changed

+55
-21
lines changed

packages/ionic/src/commands/cordova/base.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ export const CORDOVA_BUILD_EXAMPLE_COMMANDS = [
7878
export abstract class CordovaCommand extends Command {
7979
private _integration?: Required<ProjectIntegration>;
8080

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

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

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

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

164-
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> {
164+
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> {
165165
if (!this.project) {
166166
throw new FatalException('Cannot use Cordova outside a project directory.');
167167
}

packages/ionic/src/commands/cordova/run.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { FatalException } from '../../lib/errors';
99
import { loadConfigXml } from '../../lib/integrations/cordova/config';
1010
import { getPackagePath } from '../../lib/integrations/cordova/project';
1111
import { filterArgumentsForCordova, generateOptionsForCordovaBuild } from '../../lib/integrations/cordova/utils';
12-
import { SUPPORTED_PLATFORMS, createNativeRunArgs, createNativeRunListArgs, runNativeRun } from '../../lib/native-run';
12+
import { SUPPORTED_PLATFORMS, checkNativeRun, createNativeRunArgs, createNativeRunListArgs, runNativeRun } from '../../lib/native-run';
1313
import { COMMON_SERVE_COMMAND_OPTIONS, LOCAL_ADDRESSES, serve } from '../../lib/serve';
1414
import { createPrefixedWriteStream } from '../../lib/utils/logger';
1515

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

150150
async preRun(inputs: CommandLineInputs, options: CommandLineOptions, runinfo: CommandInstanceInfo): Promise<void> {
151+
if (options['native-run']) {
152+
await this.checkNativeRun();
153+
}
154+
151155
await this.preRunChecks(runinfo);
152156

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

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

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

298+
protected async checkNativeRun(): Promise<void> {
299+
await checkNativeRun(this.env);
300+
}
301+
294302
protected async runNativeRun(args: readonly string[]): Promise<void> {
295303
if (!this.project) {
296304
throw new FatalException(`Cannot run ${input('ionic cordova run/emulate')} outside a project directory.`);

packages/ionic/src/definitions.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
PromptModule,
1616
} from '@ionic/cli-framework';
1717
import { NetworkInterface } from '@ionic/utils-network';
18-
import { SubprocessOptions } from '@ionic/utils-subprocess';
18+
import { SubprocessOptions, WhichOptions } from '@ionic/utils-subprocess';
1919
import { ChildProcess, SpawnOptions } from 'child_process';
2020
import * as fs from 'fs';
2121

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

389390
export interface ITelemetry {

packages/ionic/src/lib/native-run.ts

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { processExit } from '@ionic/utils-process';
2-
import { ERROR_COMMAND_NOT_FOUND, SubprocessError } from '@ionic/utils-subprocess';
2+
import { ERROR_COMMAND_NOT_FOUND, SubprocessError, which } from '@ionic/utils-subprocess';
33

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

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

7070
export interface RunNativeRunDeps {
71-
config: IConfig;
72-
log: ILogger;
73-
shell: IShell;
71+
readonly config: IConfig;
72+
readonly log: ILogger;
73+
readonly shell: IShell;
7474
}
7575

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

9187
throw e;
@@ -98,3 +94,28 @@ export async function runNativeRun({ config, log, shell }: RunNativeRunDeps, arg
9894
processExit(0); // tslint:disable-line:no-floating-promises
9995
}
10096
}
97+
98+
export interface CheckNativeRunDeps {
99+
readonly config: IConfig;
100+
}
101+
102+
export async function checkNativeRun({ config }: CheckNativeRunDeps): Promise<void> {
103+
try {
104+
await which('native-run');
105+
} catch (e) {
106+
if (e.code === 'ENOENT') {
107+
throw await createNativeRunNotFoundError(config.get('npmClient'));
108+
}
109+
110+
throw e;
111+
}
112+
}
113+
114+
async function createNativeRunNotFoundError(npmClient: NpmClient): Promise<FatalException> {
115+
const installArgs = await pkgManagerArgs(npmClient, { command: 'install', pkg: 'native-run', global: true });
116+
117+
return new FatalException(
118+
`${input('native-run')} was not found on your PATH. Please install it globally:\n` +
119+
`${input(installArgs.join(' '))}\n`
120+
);
121+
}

packages/ionic/src/lib/shell.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { LOGGER_LEVELS } from '@ionic/cli-framework';
22
import { createProcessEnv, killProcessTree, onBeforeExit } from '@ionic/utils-process';
33
import { combineStreams } from '@ionic/utils-stream';
4-
import { ERROR_COMMAND_NOT_FOUND, Subprocess, SubprocessError, which } from '@ionic/utils-subprocess';
4+
import { ERROR_COMMAND_NOT_FOUND, Subprocess, SubprocessError, WhichOptions, which } from '@ionic/utils-subprocess';
55
import { TERMINAL_INFO } from '@ionic/utils-terminal';
66
import chalk from 'chalk';
77
import { ChildProcess, SpawnOptions } from 'child_process';
@@ -175,7 +175,7 @@ export class Shell implements IShell {
175175
async resolveCommandPath(command: string, options: SpawnOptions): Promise<string> {
176176
if (TERMINAL_INFO.windows) {
177177
try {
178-
return await which(command, { PATH: options.env.PATH });
178+
return await this.which(command, { PATH: options.env && options.env.PATH ? options.env.PATH : process.env.PATH });
179179
} catch (e) {
180180
// ignore
181181
}
@@ -184,6 +184,10 @@ export class Shell implements IShell {
184184
return command;
185185
}
186186

187+
async which(command: string, { PATH = process.env.PATH }: WhichOptions = {}): Promise<string> {
188+
return which(command, { PATH: this.alterPath(PATH || '') });
189+
}
190+
187191
async spawn(command: string, args: string[], { showCommand = true, ...crossSpawnOptions }: IShellSpawnOptions): Promise<ChildProcess> {
188192
this.prepareSpawnOptions(crossSpawnOptions);
189193

0 commit comments

Comments
 (0)