Skip to content

Commit a7fec56

Browse files
fix: errors from tns doctor are not visible in CI environment
When `tns doctor` detects errors in the configuration, it should print them to stdout and prompt the user to select how to resolve them. In non-interactive terminal, it should just print the errors and fails as the prompter are not meaningful in non-interactive terminal - there's noone to answer them. The logic works fine when you pipe the output of `tns doctor` to file for example (`tns doctor > out.txt`). CLI correctly detects the terminal as non-interactive, prints the errors and exits. However, most of the CI environments are determined as interactive terminals by CLI. That's because CLI checks if the stdout and stdin of the current process are text terminal (TTY). CI environments set required flags, so the process seems like running in such text terminal. When CLI thinks the process is running in text terminal, it uses some external package (ora) to print pretty lines. However, `ora` package also checks if the process is running in text terminal (which both CLI and `ora` think is true), but it also checks if the environment variable `CI` is set. When it is set, `ora` package decides that it cannot print colored messages and just doesn't print anything. To resolve this, improve the check if interactive terminal in CLI to respect the known environment variables that define the process as running in CI: - for Travis the environment varible `CI` is set. - for CircleCI the environment variable `CI` is set - for Jenkins the environment variable `JENKINS_HOME` is set Whenever one of those environment variables is set, CLI will decide it is running in non-interactive terminal and will not use `ora` (for `tns doctor`). It will also not show any prompters in this case.
1 parent 926957a commit a7fec56

File tree

4 files changed

+83
-14
lines changed

4 files changed

+83
-14
lines changed

lib/common/helpers.ts

+21-1
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,27 @@ export function versionCompare(version1: string | IVersionData, version2: string
309309
}
310310

311311
export function isInteractive(): boolean {
312-
return process.stdout.isTTY && process.stdin.isTTY;
312+
const isInteractive = isRunningInTTY() && !isCIEnvironment();
313+
return isInteractive;
314+
}
315+
316+
/**
317+
* Checks if current process is running in Text Terminal (TTY)
318+
*/
319+
function isRunningInTTY(): boolean {
320+
return process.stdout &&
321+
process.stdout.isTTY &&
322+
process.stdin &&
323+
process.stdin.isTTY;
324+
}
325+
326+
function isCIEnvironment(): boolean {
327+
// The following CI environments set their own environment variables that we respect:
328+
// travis: "CI",
329+
// circleCI: "CI",
330+
// jenkins: "JENKINS_HOME"
331+
332+
return !!(process.env && (process.env.CI || process.env.JENKINS_HOME));
313333
}
314334

315335
export function toBoolean(str: any): boolean {

lib/common/test/unit-tests/helpers.ts

+51
Original file line numberDiff line numberDiff line change
@@ -861,4 +861,55 @@ const test = require("./test");`,
861861
});
862862
});
863863
});
864+
865+
describe("isInteractive", () => {
866+
const originalEnv = process.env;
867+
const originalStdoutIsTTY = process.stdout.isTTY;
868+
const originalStdinIsTTY = process.stdin.isTTY;
869+
beforeEach(() => {
870+
process.env.CI = "";
871+
process.env.JENKINS_HOME = "";
872+
});
873+
874+
afterEach(() => {
875+
process.env = originalEnv;
876+
process.stdout.isTTY = originalStdoutIsTTY;
877+
process.stdin.isTTY = originalStdinIsTTY;
878+
});
879+
880+
it("returns false when stdout is not TTY", () => {
881+
(<any>process.stdout).isTTY = false;
882+
(<any>process.stdin).isTTY = true;
883+
assert.isFalse(helpers.isInteractive());
884+
});
885+
886+
it("returns false when stdin is not TTY", () => {
887+
(<any>process.stdin).isTTY = false;
888+
(<any>process.stdout).isTTY = true;
889+
assert.isFalse(helpers.isInteractive());
890+
});
891+
892+
it("returns false when stdout and stdin are TTY, but CI env var is set", () => {
893+
(<any>process.stdout).isTTY = true;
894+
(<any>process.stdin).isTTY = true;
895+
process.env.CI = "true";
896+
897+
assert.isFalse(helpers.isInteractive());
898+
});
899+
900+
it("returns false when stdout and stdin are TTY, but JENKINS_HOME env var is set", () => {
901+
(<any>process.stdout).isTTY = true;
902+
(<any>process.stdin).isTTY = true;
903+
process.env.JENKINS_HOME = "/usr/local/lib/jenkins";
904+
905+
assert.isFalse(helpers.isInteractive());
906+
});
907+
908+
it("returns true when stdout and stdin are TTY and neither CI or JENKINS_HOME are set", () => {
909+
(<any>process.stdout).isTTY = true;
910+
(<any>process.stdin).isTTY = true;
911+
912+
assert.isTrue(helpers.isInteractive());
913+
});
914+
});
864915
});

test/services/doctor-service.ts

-11
Original file line numberDiff line numberDiff line change
@@ -111,17 +111,6 @@ describe("doctorService", () => {
111111
filesContents: {
112112
file1: `const application = require("application");
113113
const Observable = require("data/observable").Observable;
114-
`
115-
},
116-
expectedShortImports: [
117-
{ file: "file1", line: 'const application = require("application");' },
118-
{ file: "file1", line: 'const Observable = require("data/observable").Observable;' },
119-
]
120-
},
121-
{
122-
filesContents: {
123-
file1: `const application = require("application");
124-
const Observable = require("data/observable").Observable;
125114
`
126115
},
127116
expectedShortImports: [

test/services/platform-environment-requirements.ts

+11-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import { PlatformEnvironmentRequirements } from '../../lib/services/platform-env
33
import * as stubs from "../stubs";
44
import { assert } from "chai";
55
import { EOL } from "os";
6+
const helpers = require("../../lib/common/helpers");
67

8+
const originalIsInteractive = helpers.isInteractive;
79
const platform = "android";
810
const cloudBuildsErrorMessage = `In order to test your application use the $ tns login command to log in with your account and then $ tns cloud build command to build your app in the cloud.`;
911
const manuallySetupErrorMessage = `To be able to build for ${platform}, verify that your environment is configured according to the system requirements described at `;
@@ -34,6 +36,14 @@ function createTestInjector() {
3436
}
3537

3638
describe("platformEnvironmentRequirements ", () => {
39+
beforeEach(() => {
40+
helpers.isInteractive = () => true;
41+
});
42+
43+
afterEach(() => {
44+
helpers.isInteractive = originalIsInteractive;
45+
});
46+
3747
describe("checkRequirements", () => {
3848
let testInjector: IInjector = null;
3949
let platformEnvironmentRequirements: IPlatformEnvironmentRequirements = null;
@@ -221,8 +231,7 @@ describe("platformEnvironmentRequirements ", () => {
221231

222232
describe("when console is non interactive", () => {
223233
beforeEach(() => {
224-
(<any>process).stdout.isTTY = false;
225-
(<any>process.stdin).isTTY = false;
234+
helpers.isInteractive = () => false;
226235
mockDoctorService({ canExecuteLocalBuild: false });
227236
});
228237

0 commit comments

Comments
 (0)