Skip to content

Commit 6a10370

Browse files
committed
fix(doctor): harden asdf Ruby detection in the CocoaPods check
Resolve the asdf Ruby version with an explicit cwd so it reflects the project even when `ns doctor` is invoked outside the project directory, and newline-terminate the `.tool-versions` entry so it cannot merge with existing content. Adds unit tests for the asdf-present and asdf-absent paths.
1 parent b0f2371 commit 6a10370

2 files changed

Lines changed: 135 additions & 3 deletions

File tree

packages/doctor/src/sys-info.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -425,12 +425,13 @@ export class SysInfo implements NativeScriptDoctor.ISysInfo {
425425
);
426426
const xcodeProjectDir = path.join(tempDirectory, "cocoapods");
427427

428-
// If asdf version manager is installed, get the current Ruby version for the project directory and write it to the temporary project directory
428+
// If asdf version manager is installed, get the current Ruby version for the project directory and write it to the temporary project directory.
429+
// Resolve relative to the directory `ns doctor` was invoked from, since it can be run outside of a project directory.
429430
const asdfResult = await this.childProcess.spawnFromEvent(
430431
"asdf",
431432
["current", "ruby"],
432433
"exit",
433-
{ ignoreError: true },
434+
{ ignoreError: true, spawnOptions: { cwd: process.cwd() } },
434435
);
435436

436437
if (asdfResult.exitCode === 0) {
@@ -446,7 +447,7 @@ export class SysInfo implements NativeScriptDoctor.ISysInfo {
446447
);
447448
const wroteASDFConfig = this.fileSystem.appendFile(
448449
asdfConfigPath,
449-
`ruby ${asdfVersion}`,
450+
`ruby ${asdfVersion}\n`,
450451
);
451452
if (!wroteASDFConfig) {
452453
console.warn(

packages/doctor/test/sys-info.ts

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as assert from "assert";
2+
import * as fs from "fs";
23
import * as path from "path";
34
import { EOL } from "os";
45
import { SysInfo } from "../src/sys-info";
@@ -910,4 +911,134 @@ Java HotSpot(TM) 64-Bit Server VM (build 25.202-b08, mixed mode)`),
910911
});
911912
});
912913
});
914+
915+
describe("isCocoaPodsWorkingCorrectly", () => {
916+
interface ICocoaPodsMockOptions {
917+
// Mimics the ChildProcess result for `asdf current ruby`.
918+
// When omitted, asdf is treated as not installed.
919+
asdfResult?: {
920+
stdout?: string;
921+
stderr?: string;
922+
exitCode?: number | string;
923+
};
924+
podExitCode?: number;
925+
}
926+
927+
const createCocoaPodsSysInfo = (options: ICocoaPodsMockOptions) => {
928+
const appendedFiles: { filePath: string; text: string }[] = [];
929+
const spawnCalls: {
930+
command: string;
931+
options?: ISpawnFromEventOptions;
932+
}[] = [];
933+
934+
const childProcess: any = {
935+
spawnFromEvent: async (
936+
command: string,
937+
args: string[],
938+
event: string,
939+
spawnFromEventOptions?: ISpawnFromEventOptions,
940+
) => {
941+
const fullCommand = `${command} ${args.join(" ")}`;
942+
spawnCalls.push({
943+
command: fullCommand,
944+
options: spawnFromEventOptions,
945+
});
946+
947+
if (fullCommand === "asdf current ruby") {
948+
// Mirror the ChildProcess wrapper: with `ignoreError` it always
949+
// resolves, surfacing a non-zero exitCode instead of throwing when
950+
// asdf is missing/misconfigured.
951+
return (
952+
options.asdfResult || {
953+
stdout: "",
954+
stderr: "spawn asdf ENOENT",
955+
exitCode: "ENOENT",
956+
}
957+
);
958+
}
959+
960+
return {
961+
stdout: "",
962+
stderr: "",
963+
exitCode: options.podExitCode ?? 0,
964+
};
965+
},
966+
exec: async () => ({ stdout: "", stderr: "" }),
967+
execFile: async (): Promise<any> => undefined,
968+
execSync: (): string => null,
969+
};
970+
971+
const fileSystem: any = {
972+
exists: () => true,
973+
extractZip: () => Promise.resolve(),
974+
readDirectory: () => [],
975+
appendFile: (filePath: string, text: string) => {
976+
appendedFiles.push({ filePath, text });
977+
return true;
978+
},
979+
deleteEntry: (filePath: string) =>
980+
fs.rmSync(filePath, { recursive: true, force: true }),
981+
};
982+
983+
const hostInfo: any = {
984+
isDarwin: true,
985+
isWindows: false,
986+
isLinux: false,
987+
};
988+
989+
const helpers = new Helpers(hostInfo);
990+
const sysInfo = new SysInfo(
991+
childProcess,
992+
fileSystem,
993+
helpers,
994+
hostInfo,
995+
null,
996+
androidToolsInfo,
997+
);
998+
999+
return { sysInfo, appendedFiles, spawnCalls };
1000+
};
1001+
1002+
it("writes the active Ruby version to .tool-versions when asdf is available", async () => {
1003+
const { sysInfo, appendedFiles, spawnCalls } = createCocoaPodsSysInfo({
1004+
asdfResult: {
1005+
stdout:
1006+
"ruby 3.2.1 /Users/user/app/.tool-versions",
1007+
exitCode: 0,
1008+
},
1009+
});
1010+
1011+
const result = await sysInfo.isCocoaPodsWorkingCorrectly();
1012+
1013+
assert.deepEqual(result, true);
1014+
assert.deepEqual(appendedFiles.length, 1);
1015+
assert.ok(
1016+
appendedFiles[0].filePath.endsWith(
1017+
path.join("cocoapods", ".tool-versions"),
1018+
),
1019+
);
1020+
// The entry must be newline-terminated so it does not merge with existing content.
1021+
assert.deepEqual(appendedFiles[0].text, "ruby 3.2.1\n");
1022+
1023+
const asdfCall = spawnCalls.find(
1024+
(c) => c.command === "asdf current ruby",
1025+
);
1026+
assert.ok(asdfCall, "expected asdf to be probed");
1027+
// The probe must not throw when asdf is missing/misconfigured...
1028+
assert.deepEqual(asdfCall.options.ignoreError, true);
1029+
// ...and it must resolve the version relative to the invocation directory.
1030+
assert.deepEqual(asdfCall.options.spawnOptions.cwd, process.cwd());
1031+
});
1032+
1033+
it("does not write .tool-versions and stays healthy when asdf is not installed", async () => {
1034+
const { sysInfo, appendedFiles } = createCocoaPodsSysInfo({
1035+
// asdf missing -> wrapper resolves with a non-zero (ENOENT) exit code.
1036+
});
1037+
1038+
const result = await sysInfo.isCocoaPodsWorkingCorrectly();
1039+
1040+
assert.deepEqual(result, true);
1041+
assert.deepEqual(appendedFiles.length, 0);
1042+
});
1043+
});
9131044
});

0 commit comments

Comments
 (0)