mirror of
https://github.com/logos-storage/logos-storage-installer.git
synced 2026-01-07 16:03:07 +00:00
Simplified path handling part 1
This commit is contained in:
parent
29ceec8281
commit
280b00802b
@ -13,8 +13,10 @@ export const mockUiService = {
|
|||||||
|
|
||||||
export const mockConfigService = {
|
export const mockConfigService = {
|
||||||
get: vi.fn(),
|
get: vi.fn(),
|
||||||
saveConfig: vi.fn(),
|
getCodexExe: vi.fn(),
|
||||||
|
getCodexConfigFilePath: vi.fn(),
|
||||||
loadConfig: vi.fn(),
|
loadConfig: vi.fn(),
|
||||||
|
saveConfig: vi.fn(),
|
||||||
writeCodexConfigFile: vi.fn(),
|
writeCodexConfigFile: vi.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -25,11 +27,12 @@ export const mockFsService = {
|
|||||||
isFile: vi.fn(),
|
isFile: vi.fn(),
|
||||||
readDir: vi.fn(),
|
readDir: vi.fn(),
|
||||||
makeDir: vi.fn(),
|
makeDir: vi.fn(),
|
||||||
|
moveDir: vi.fn(),
|
||||||
deleteDir: vi.fn(),
|
deleteDir: vi.fn(),
|
||||||
readJsonFile: vi.fn(),
|
readJsonFile: vi.fn(),
|
||||||
writeJsonFile: vi.fn(),
|
writeJsonFile: vi.fn(),
|
||||||
writeFile: vi.fn(),
|
writeFile: vi.fn(),
|
||||||
toRelativePath: vi.fn(),
|
ensureDirExists: vi.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const mockShellService = {
|
export const mockShellService = {
|
||||||
@ -43,6 +46,7 @@ export const mockOsService = {
|
|||||||
isLinux: vi.fn(),
|
isLinux: vi.fn(),
|
||||||
getWorkingDir: vi.fn(),
|
getWorkingDir: vi.fn(),
|
||||||
listProcesses: vi.fn(),
|
listProcesses: vi.fn(),
|
||||||
|
stopProcess: vi.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const mockCodexGlobals = {
|
export const mockCodexGlobals = {
|
||||||
|
|||||||
@ -12,7 +12,6 @@ import {
|
|||||||
showSuccessMessage,
|
showSuccessMessage,
|
||||||
} from "../utils/messages.js";
|
} from "../utils/messages.js";
|
||||||
import { checkDependencies } from "../services/nodeService.js";
|
import { checkDependencies } from "../services/nodeService.js";
|
||||||
import { getCodexRootPath, getCodexBinPath } from "../utils/appData.js";
|
|
||||||
|
|
||||||
const platform = os.platform();
|
const platform = os.platform();
|
||||||
|
|
||||||
|
|||||||
@ -17,14 +17,15 @@ export class Installer {
|
|||||||
};
|
};
|
||||||
|
|
||||||
getCodexVersion = async () => {
|
getCodexVersion = async () => {
|
||||||
if (this.config.codexExe.length < 1)
|
const codexExe = this.configService.getCodexExe();
|
||||||
throw new Error("Codex not installed.");
|
if (!this.fs.isFile(codexExe)) throw new Error("Codex not installed.");
|
||||||
const version = await this.shell.run(`"${this.config.codexExe}" --version`);
|
const version = await this.shell.run(`"${codexExe}" --version`);
|
||||||
if (version.length < 1) throw new Error("Version info not found.");
|
if (version.length < 1) throw new Error("Version info not found.");
|
||||||
return version;
|
return version;
|
||||||
};
|
};
|
||||||
|
|
||||||
installCodex = async (processCallbacks) => {
|
installCodex = async (processCallbacks) => {
|
||||||
|
this.fs.ensureDirExists(this.config.codexRoot);
|
||||||
if (!(await this.arePrerequisitesCorrect(processCallbacks))) return;
|
if (!(await this.arePrerequisitesCorrect(processCallbacks))) return;
|
||||||
|
|
||||||
processCallbacks.installStarts();
|
processCallbacks.installStarts();
|
||||||
@ -34,14 +35,15 @@ export class Installer {
|
|||||||
await this.installCodexUnix(processCallbacks);
|
await this.installCodexUnix(processCallbacks);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(await this.isCodexInstalled()))
|
if (!(await this.isCodexInstalled())) {
|
||||||
|
processCallbacks.warn("Codex failed to install.");
|
||||||
throw new Error("Codex installation failed.");
|
throw new Error("Codex installation failed.");
|
||||||
|
}
|
||||||
processCallbacks.installSuccessful();
|
processCallbacks.installSuccessful();
|
||||||
};
|
};
|
||||||
|
|
||||||
uninstallCodex = () => {
|
uninstallCodex = () => {
|
||||||
this.fs.deleteDir(this.config.codexInstallPath);
|
this.fs.deleteDir(this.config.codexRoot);
|
||||||
this.fs.deleteDir(this.config.dataDir);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
arePrerequisitesCorrect = async (processCallbacks) => {
|
arePrerequisitesCorrect = async (processCallbacks) => {
|
||||||
@ -49,8 +51,8 @@ export class Installer {
|
|||||||
processCallbacks.warn("Codex is already installed.");
|
processCallbacks.warn("Codex is already installed.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (this.config.codexInstallPath.length < 1) {
|
if (!this.fs.isDir(this.config.codexRoot)) {
|
||||||
processCallbacks.warn("Install path not set.");
|
processCallbacks.warn("Root path doesn't exist.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!(await this.isCurlAvailable())) {
|
if (!(await this.isCurlAvailable())) {
|
||||||
@ -71,10 +73,9 @@ export class Installer {
|
|||||||
);
|
);
|
||||||
processCallbacks.downloadSuccessful();
|
processCallbacks.downloadSuccessful();
|
||||||
await this.shell.run(
|
await this.shell.run(
|
||||||
`set "INSTALL_DIR=${this.config.codexInstallPath}" && ` +
|
`set "INSTALL_DIR=${this.config.codexRoot}" && ` +
|
||||||
`"${this.os.getWorkingDir()}\\install.cmd"`,
|
`"${this.os.getWorkingDir()}\\install.cmd"`,
|
||||||
);
|
);
|
||||||
await this.saveCodexInstallPath("codex.exe");
|
|
||||||
await this.shell.run("del /f install.cmd");
|
await this.shell.run("del /f install.cmd");
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -90,8 +91,6 @@ export class Installer {
|
|||||||
} else {
|
} else {
|
||||||
await this.runInstallerLinux();
|
await this.runInstallerLinux();
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.saveCodexInstallPath("codex");
|
|
||||||
await this.shell.run("rm -f install.sh");
|
await this.shell.run("rm -f install.sh");
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -100,7 +99,7 @@ export class Installer {
|
|||||||
eval {
|
eval {
|
||||||
local $SIG{ALRM} = sub { die "timeout\\n" };
|
local $SIG{ALRM} = sub { die "timeout\\n" };
|
||||||
alarm(120);
|
alarm(120);
|
||||||
system("INSTALL_DIR=\\"${this.config.codexInstallPath}\\" bash install.sh");
|
system("INSTALL_DIR=\\"${this.config.codexRoot}\\" bash install.sh");
|
||||||
alarm(0);
|
alarm(0);
|
||||||
};
|
};
|
||||||
die if $@;
|
die if $@;
|
||||||
@ -110,7 +109,7 @@ export class Installer {
|
|||||||
|
|
||||||
runInstallerLinux = async () => {
|
runInstallerLinux = async () => {
|
||||||
await this.shell.run(
|
await this.shell.run(
|
||||||
`INSTALL_DIR="${this.config.codexInstallPath}" timeout 120 bash install.sh`,
|
`INSTALL_DIR="${this.config.codexRoot}" timeout 120 bash install.sh`,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -122,14 +121,4 @@ export class Installer {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
saveCodexInstallPath = async (codexExe) => {
|
|
||||||
this.config.codexExe = this.fs.pathJoin([
|
|
||||||
this.config.codexInstallPath,
|
|
||||||
codexExe,
|
|
||||||
]);
|
|
||||||
if (!this.fs.isFile(this.config.codexExe))
|
|
||||||
throw new Error("Codex executable not found.");
|
|
||||||
await this.configService.saveConfig();
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,9 +9,10 @@ import { Installer } from "./installer.js";
|
|||||||
|
|
||||||
describe("Installer", () => {
|
describe("Installer", () => {
|
||||||
const config = {
|
const config = {
|
||||||
codexInstallPath: "/install-codex",
|
codexRoot: "/codex-root",
|
||||||
};
|
};
|
||||||
const workingDir = "/working-dir";
|
const workingDir = "/working-dir";
|
||||||
|
const exe = "abc.exe";
|
||||||
const processCallbacks = {
|
const processCallbacks = {
|
||||||
installStarts: vi.fn(),
|
installStarts: vi.fn(),
|
||||||
downloadSuccessful: vi.fn(),
|
downloadSuccessful: vi.fn(),
|
||||||
@ -24,6 +25,7 @@ describe("Installer", () => {
|
|||||||
vi.resetAllMocks();
|
vi.resetAllMocks();
|
||||||
mockConfigService.get.mockReturnValue(config);
|
mockConfigService.get.mockReturnValue(config);
|
||||||
mockOsService.getWorkingDir.mockReturnValue(workingDir);
|
mockOsService.getWorkingDir.mockReturnValue(workingDir);
|
||||||
|
mockConfigService.getCodexExe.mockReturnValue(exe);
|
||||||
|
|
||||||
installer = new Installer(
|
installer = new Installer(
|
||||||
mockConfigService,
|
mockConfigService,
|
||||||
@ -34,15 +36,22 @@ describe("Installer", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("getCodexVersion", () => {
|
describe("getCodexVersion", () => {
|
||||||
it("throws when codex exe is not set", async () => {
|
it("checks if the codex exe file exists", async () => {
|
||||||
config.codexExe = "";
|
mockFsService.isFile.mockReturnValue(true);
|
||||||
|
mockShellService.run.mockResolvedValueOnce("a");
|
||||||
|
await installer.getCodexVersion();
|
||||||
|
expect(mockFsService.isFile).toHaveBeenCalledWith(exe);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws when codex exe is not a file", async () => {
|
||||||
|
mockFsService.isFile.mockReturnValue(false);
|
||||||
await expect(installer.getCodexVersion()).rejects.toThrow(
|
await expect(installer.getCodexVersion()).rejects.toThrow(
|
||||||
"Codex not installed.",
|
"Codex not installed.",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("throws when version info is not found", async () => {
|
it("throws when version info is not found", async () => {
|
||||||
config.codexExe = "codex.exe";
|
mockFsService.isFile.mockReturnValue(true);
|
||||||
mockShellService.run.mockResolvedValueOnce("");
|
mockShellService.run.mockResolvedValueOnce("");
|
||||||
await expect(installer.getCodexVersion()).rejects.toThrow(
|
await expect(installer.getCodexVersion()).rejects.toThrow(
|
||||||
"Version info not found.",
|
"Version info not found.",
|
||||||
@ -50,8 +59,8 @@ describe("Installer", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("returns version info", async () => {
|
it("returns version info", async () => {
|
||||||
|
mockFsService.isFile.mockReturnValue(true);
|
||||||
const versionInfo = "versionInfo";
|
const versionInfo = "versionInfo";
|
||||||
config.codexExe = "codex.exe";
|
|
||||||
mockShellService.run.mockResolvedValueOnce(versionInfo);
|
mockShellService.run.mockResolvedValueOnce(versionInfo);
|
||||||
const version = await installer.getCodexVersion();
|
const version = await installer.getCodexVersion();
|
||||||
expect(version).toBe(versionInfo);
|
expect(version).toBe(versionInfo);
|
||||||
@ -80,6 +89,15 @@ describe("Installer", () => {
|
|||||||
installer.isCodexInstalled = vi.fn();
|
installer.isCodexInstalled = vi.fn();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("ensures codex root dir exists", async () => {
|
||||||
|
installer.arePrerequisitesCorrect.mockResolvedValue(false);
|
||||||
|
await installer.installCodex(processCallbacks);
|
||||||
|
|
||||||
|
expect(mockFsService.ensureDirExists).toHaveBeenCalledWith(
|
||||||
|
config.codexRoot,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("returns early when prerequisites are not correct", async () => {
|
it("returns early when prerequisites are not correct", async () => {
|
||||||
installer.arePrerequisitesCorrect.mockResolvedValue(false);
|
installer.arePrerequisitesCorrect.mockResolvedValue(false);
|
||||||
await installer.installCodex(processCallbacks);
|
await installer.installCodex(processCallbacks);
|
||||||
@ -125,6 +143,16 @@ describe("Installer", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("warns user when codex is not installed after installation", async () => {
|
||||||
|
installer.isCodexInstalled.mockResolvedValue(false);
|
||||||
|
await expect(installer.installCodex(processCallbacks)).rejects.toThrow(
|
||||||
|
"Codex installation failed.",
|
||||||
|
);
|
||||||
|
expect(processCallbacks.warn).toHaveBeenCalledWith(
|
||||||
|
"Codex failed to install.",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("calls installSuccessful when installation is successful", async () => {
|
it("calls installSuccessful when installation is successful", async () => {
|
||||||
await installer.installCodex(processCallbacks);
|
await installer.installCodex(processCallbacks);
|
||||||
expect(processCallbacks.installSuccessful).toHaveBeenCalled();
|
expect(processCallbacks.installSuccessful).toHaveBeenCalled();
|
||||||
@ -136,7 +164,6 @@ describe("Installer", () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
installer.isCodexInstalled = vi.fn();
|
installer.isCodexInstalled = vi.fn();
|
||||||
installer.isCurlAvailable = vi.fn();
|
installer.isCurlAvailable = vi.fn();
|
||||||
config.codexInstallPath = "/install-codex";
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns false when codex is already installed", async () => {
|
it("returns false when codex is already installed", async () => {
|
||||||
@ -149,18 +176,26 @@ describe("Installer", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns false when install path is not set", async () => {
|
it("checks if the root path exists", async () => {
|
||||||
config.codexInstallPath = "";
|
expect(await installer.arePrerequisitesCorrect(processCallbacks)).toBe(
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
expect(mockFsService.isDir).toHaveBeenCalledWith(config.codexRoot);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns false when root path does not exist", async () => {
|
||||||
|
mockFsService.isDir.mockReturnValue(false);
|
||||||
expect(await installer.arePrerequisitesCorrect(processCallbacks)).toBe(
|
expect(await installer.arePrerequisitesCorrect(processCallbacks)).toBe(
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
expect(processCallbacks.warn).toHaveBeenCalledWith(
|
expect(processCallbacks.warn).toHaveBeenCalledWith(
|
||||||
"Install path not set.",
|
"Root path doesn't exist.",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns false when curl is not available", async () => {
|
it("returns false when curl is not available", async () => {
|
||||||
installer.isCodexInstalled.mockResolvedValue(false);
|
installer.isCodexInstalled.mockResolvedValue(false);
|
||||||
|
mockFsService.isDir.mockReturnValue(true);
|
||||||
installer.isCurlAvailable.mockResolvedValue(false);
|
installer.isCurlAvailable.mockResolvedValue(false);
|
||||||
expect(await installer.arePrerequisitesCorrect(processCallbacks)).toBe(
|
expect(await installer.arePrerequisitesCorrect(processCallbacks)).toBe(
|
||||||
false,
|
false,
|
||||||
@ -172,6 +207,7 @@ describe("Installer", () => {
|
|||||||
|
|
||||||
it("returns true when all prerequisites are correct", async () => {
|
it("returns true when all prerequisites are correct", async () => {
|
||||||
installer.isCodexInstalled.mockResolvedValue(false);
|
installer.isCodexInstalled.mockResolvedValue(false);
|
||||||
|
mockFsService.isDir.mockReturnValue(true);
|
||||||
installer.isCurlAvailable.mockResolvedValue(true);
|
installer.isCurlAvailable.mockResolvedValue(true);
|
||||||
const result = await installer.arePrerequisitesCorrect(processCallbacks);
|
const result = await installer.arePrerequisitesCorrect(processCallbacks);
|
||||||
expect(result).toBe(true);
|
expect(result).toBe(true);
|
||||||
@ -215,14 +251,7 @@ describe("Installer", () => {
|
|||||||
it("runs installer script", async () => {
|
it("runs installer script", async () => {
|
||||||
await installer.installCodexWindows(processCallbacks);
|
await installer.installCodexWindows(processCallbacks);
|
||||||
expect(mockShellService.run).toHaveBeenCalledWith(
|
expect(mockShellService.run).toHaveBeenCalledWith(
|
||||||
`set "INSTALL_DIR=${config.codexInstallPath}" && "${workingDir}\\install.cmd"`,
|
`set "INSTALL_DIR=${config.codexRoot}" && "${workingDir}\\install.cmd"`,
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("saves the codex install path", async () => {
|
|
||||||
await installer.installCodexWindows(processCallbacks);
|
|
||||||
expect(installer.saveCodexInstallPath).toHaveBeenCalledWith(
|
|
||||||
"codex.exe",
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -285,11 +314,6 @@ describe("Installer", () => {
|
|||||||
expect(installer.runInstallerLinux).toHaveBeenCalled();
|
expect(installer.runInstallerLinux).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("saves the codex install path", async () => {
|
|
||||||
await installer.installCodexUnix(processCallbacks);
|
|
||||||
expect(installer.saveCodexInstallPath).toHaveBeenCalledWith("codex");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("deletes the installer script", async () => {
|
it("deletes the installer script", async () => {
|
||||||
await installer.installCodexUnix(processCallbacks);
|
await installer.installCodexUnix(processCallbacks);
|
||||||
expect(mockShellService.run).toHaveBeenCalledWith("rm -f install.sh");
|
expect(mockShellService.run).toHaveBeenCalledWith("rm -f install.sh");
|
||||||
@ -303,7 +327,7 @@ describe("Installer", () => {
|
|||||||
eval {
|
eval {
|
||||||
local $SIG{ALRM} = sub { die "timeout\\n" };
|
local $SIG{ALRM} = sub { die "timeout\\n" };
|
||||||
alarm(120);
|
alarm(120);
|
||||||
system("INSTALL_DIR=\\"${config.codexInstallPath}\\" bash install.sh");
|
system("INSTALL_DIR=\\"${config.codexRoot}\\" bash install.sh");
|
||||||
alarm(0);
|
alarm(0);
|
||||||
};
|
};
|
||||||
die if $@;
|
die if $@;
|
||||||
@ -317,7 +341,7 @@ describe("Installer", () => {
|
|||||||
it("runs the installer script using unix timeout command", async () => {
|
it("runs the installer script using unix timeout command", async () => {
|
||||||
await installer.runInstallerLinux();
|
await installer.runInstallerLinux();
|
||||||
expect(mockShellService.run).toHaveBeenCalledWith(
|
expect(mockShellService.run).toHaveBeenCalledWith(
|
||||||
`INSTALL_DIR="${config.codexInstallPath}" timeout 120 bash install.sh`,
|
`INSTALL_DIR="${config.codexRoot}" timeout 120 bash install.sh`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -351,56 +375,11 @@ describe("Installer", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("saveCodexInstallPath", () => {
|
|
||||||
const codexExe = "_codex_.exe";
|
|
||||||
const pathJointResult = "/path-to-codex/_codex_.exe";
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
mockFsService.pathJoin.mockReturnValue(pathJointResult);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("combines the install path with the exe", async () => {
|
|
||||||
mockFsService.isFile.mockReturnValue(true);
|
|
||||||
await installer.saveCodexInstallPath(codexExe);
|
|
||||||
expect(mockFsService.pathJoin).toHaveBeenCalledWith([
|
|
||||||
config.codexInstallPath,
|
|
||||||
codexExe,
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sets the codex exe path", async () => {
|
|
||||||
mockFsService.isFile.mockReturnValue(true);
|
|
||||||
await installer.saveCodexInstallPath(codexExe);
|
|
||||||
expect(config.codexExe).toBe(pathJointResult);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("throws when file does not exist", async () => {
|
|
||||||
mockFsService.isFile.mockReturnValue(false);
|
|
||||||
await expect(installer.saveCodexInstallPath(codexExe)).rejects.toThrow(
|
|
||||||
"Codex executable not found.",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("saves the config", async () => {
|
|
||||||
mockFsService.isFile.mockReturnValue(true);
|
|
||||||
await installer.saveCodexInstallPath(codexExe);
|
|
||||||
expect(mockConfigService.saveConfig).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("uninstallCodex", () => {
|
describe("uninstallCodex", () => {
|
||||||
it("deletes the codex install path", () => {
|
it("deletes the codex root path", () => {
|
||||||
installer.uninstallCodex();
|
installer.uninstallCodex();
|
||||||
|
|
||||||
expect(mockFsService.deleteDir).toHaveBeenCalledWith(
|
expect(mockFsService.deleteDir).toHaveBeenCalledWith(config.codexRoot);
|
||||||
config.codexInstallPath,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("deletes the codex data path", () => {
|
|
||||||
installer.uninstallCodex();
|
|
||||||
|
|
||||||
expect(mockFsService.deleteDir).toHaveBeenCalledWith(config.dataDir);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
export class ProcessControl {
|
export class ProcessControl {
|
||||||
constructor(configService, shellService, osService, fsService, codexGlobals) {
|
constructor(configService, shellService, osService, fsService, codexGlobals) {
|
||||||
this.configService = configService;
|
this.configService = configService;
|
||||||
this.config = configService.get();
|
|
||||||
this.shell = shellService;
|
this.shell = shellService;
|
||||||
this.os = osService;
|
this.os = osService;
|
||||||
this.fs = fsService;
|
this.fs = fsService;
|
||||||
@ -26,7 +25,7 @@ export class ProcessControl {
|
|||||||
if (processes.length < 1) throw new Error("No codex process found");
|
if (processes.length < 1) throw new Error("No codex process found");
|
||||||
|
|
||||||
const pid = processes[0].pid;
|
const pid = processes[0].pid;
|
||||||
process.kill(pid, "SIGINT");
|
this.os.stopProcess(pid);
|
||||||
await this.sleep();
|
await this.sleep();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -51,8 +50,11 @@ export class ProcessControl {
|
|||||||
};
|
};
|
||||||
|
|
||||||
startCodex = async () => {
|
startCodex = async () => {
|
||||||
const executable = this.config.codexExe;
|
const executable = this.configService.getCodexExe();
|
||||||
const args = [`--config-file=${this.config.codexConfigFilePath}`];
|
const workingDir = this.configService.get().codexRoot;
|
||||||
await this.shell.spawnDetachedProcess(executable, args);
|
const args = [
|
||||||
|
`--config-file=${this.configService.getCodexConfigFilePath()}`,
|
||||||
|
];
|
||||||
|
await this.shell.spawnDetachedProcess(executable, workingDir, args);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
165
src/handlers/processControl.test.js
Normal file
165
src/handlers/processControl.test.js
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
import { describe, beforeEach, it, expect, vi } from "vitest";
|
||||||
|
import {
|
||||||
|
mockShellService,
|
||||||
|
mockOsService,
|
||||||
|
mockFsService,
|
||||||
|
mockCodexGlobals,
|
||||||
|
} from "../__mocks__/service.mocks.js";
|
||||||
|
import { mockConfigService } from "../__mocks__/service.mocks.js";
|
||||||
|
import { ProcessControl } from "./processControl.js";
|
||||||
|
|
||||||
|
describe("ProcessControl", () => {
|
||||||
|
let processControl;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.resetAllMocks();
|
||||||
|
|
||||||
|
processControl = new ProcessControl(
|
||||||
|
mockConfigService,
|
||||||
|
mockShellService,
|
||||||
|
mockOsService,
|
||||||
|
mockFsService,
|
||||||
|
mockCodexGlobals,
|
||||||
|
);
|
||||||
|
|
||||||
|
processControl.sleep = vi.fn();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getCodexProcesses", () => {
|
||||||
|
const processes = [
|
||||||
|
{ id: 0, name: "a.exe" },
|
||||||
|
{ id: 1, name: "aaa" },
|
||||||
|
{ id: 2, name: "codex" },
|
||||||
|
{ id: 3, name: "codex.exe" },
|
||||||
|
{ id: 4, name: "notcodex" },
|
||||||
|
{ id: 5, name: "alsonotcodex.exe" },
|
||||||
|
];
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockOsService.listProcesses.mockResolvedValue(processes);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns codex.exe processes on windows", async () => {
|
||||||
|
mockOsService.isWindows.mockReturnValue(true);
|
||||||
|
|
||||||
|
const p = await processControl.getCodexProcesses();
|
||||||
|
|
||||||
|
expect(p.length).toBe(1);
|
||||||
|
expect(p[0]).toBe(processes[3]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns codex processes on non-windows", async () => {
|
||||||
|
mockOsService.isWindows.mockReturnValue(false);
|
||||||
|
|
||||||
|
const p = await processControl.getCodexProcesses();
|
||||||
|
|
||||||
|
expect(p.length).toBe(1);
|
||||||
|
expect(p[0]).toBe(processes[2]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getNumberOfCodexProcesses", () => {
|
||||||
|
it("counts the results of getCodexProcesses", async () => {
|
||||||
|
processControl.getCodexProcesses = vi.fn();
|
||||||
|
processControl.getCodexProcesses.mockResolvedValue(["a", "b", "c"]);
|
||||||
|
|
||||||
|
expect(await processControl.getNumberOfCodexProcesses()).toBe(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("stopCodexProcess", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
processControl.getCodexProcesses = vi.fn();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws when no codex processes are found", async () => {
|
||||||
|
processControl.getCodexProcesses.mockResolvedValue([]);
|
||||||
|
|
||||||
|
await expect(processControl.stopCodexProcess).rejects.toThrow(
|
||||||
|
"No codex process found",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("stops the first codex process", async () => {
|
||||||
|
const pid = 12345;
|
||||||
|
processControl.getCodexProcesses.mockResolvedValue([
|
||||||
|
{ pid: pid },
|
||||||
|
{ pid: 111 },
|
||||||
|
{ pid: 222 },
|
||||||
|
]);
|
||||||
|
|
||||||
|
await processControl.stopCodexProcess();
|
||||||
|
|
||||||
|
expect(mockOsService.stopProcess).toHaveBeenCalledWith(pid);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sleeps", async () => {
|
||||||
|
processControl.getCodexProcesses.mockResolvedValue([
|
||||||
|
{ pid: 111 },
|
||||||
|
{ pid: 222 },
|
||||||
|
]);
|
||||||
|
|
||||||
|
await processControl.stopCodexProcess();
|
||||||
|
|
||||||
|
expect(processControl.sleep).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("startCodexProcess", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
processControl.saveCodexConfigFile = vi.fn();
|
||||||
|
processControl.startCodex = vi.fn();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("saves the config, starts codex, and sleeps", async () => {
|
||||||
|
await processControl.startCodexProcess();
|
||||||
|
|
||||||
|
expect(processControl.saveCodexConfigFile).toHaveBeenCalled();
|
||||||
|
expect(processControl.startCodex).toHaveBeenCalled();
|
||||||
|
expect(processControl.sleep).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("saveCodexConfigFile", () => {
|
||||||
|
const publicIp = "1.2.3.4";
|
||||||
|
const bootNodes = ["a", "b", "c"];
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockCodexGlobals.getPublicIp.mockResolvedValue(publicIp);
|
||||||
|
mockCodexGlobals.getTestnetSPRs.mockResolvedValue(bootNodes);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("writes codex config file using public IP and testnet bootstrap nodes", async () => {
|
||||||
|
await processControl.saveCodexConfigFile();
|
||||||
|
|
||||||
|
expect(mockConfigService.writeCodexConfigFile).toHaveBeenCalledWith(
|
||||||
|
publicIp,
|
||||||
|
bootNodes,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("startCodex", () => {
|
||||||
|
const config = {
|
||||||
|
codexRoot: "/codex-root",
|
||||||
|
};
|
||||||
|
const exe = "abc.exe";
|
||||||
|
const configFile = "/codex/config.toml";
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockConfigService.getCodexExe.mockReturnValue(exe);
|
||||||
|
mockConfigService.get.mockReturnValue(config);
|
||||||
|
mockConfigService.getCodexConfigFilePath.mockReturnValue(configFile);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("spawns a detached codex process in the codex root working directory with the config file as argument", async () => {
|
||||||
|
await processControl.startCodex();
|
||||||
|
|
||||||
|
expect(mockShellService.spawnDetachedProcess).toHaveBeenCalledWith(
|
||||||
|
exe,
|
||||||
|
config.codexRoot,
|
||||||
|
[`--config-file=${configFile}`],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -103,12 +103,12 @@ export async function main() {
|
|||||||
const codexGlobals = new CodexGlobals();
|
const codexGlobals = new CodexGlobals();
|
||||||
const uiService = new UiService();
|
const uiService = new UiService();
|
||||||
const fsService = new FsService();
|
const fsService = new FsService();
|
||||||
const configService = new ConfigService(fsService);
|
|
||||||
const codexApp = new CodexApp(configService);
|
|
||||||
const pathSelector = new PathSelector(uiService, new MenuLoop(), fsService);
|
|
||||||
const numberSelector = new NumberSelector(uiService);
|
|
||||||
const shellService = new ShellService();
|
const shellService = new ShellService();
|
||||||
const osService = new OsService();
|
const osService = new OsService();
|
||||||
|
const numberSelector = new NumberSelector(uiService);
|
||||||
|
const configService = new ConfigService(fsService, osService);
|
||||||
|
const codexApp = new CodexApp(configService);
|
||||||
|
const pathSelector = new PathSelector(uiService, new MenuLoop(), fsService);
|
||||||
const installer = new Installer(
|
const installer = new Installer(
|
||||||
configService,
|
configService,
|
||||||
shellService,
|
shellService,
|
||||||
|
|||||||
@ -1,20 +1,7 @@
|
|||||||
import {
|
import { getAppDataDir, getDefaultCodexRootPath } from "../utils/appData.js";
|
||||||
getAppDataDir,
|
|
||||||
getCodexBinPath,
|
|
||||||
getCodexConfigFilePath,
|
|
||||||
getCodexDataDirDefaultPath,
|
|
||||||
getCodexLogsDefaultPath,
|
|
||||||
} from "../utils/appData.js";
|
|
||||||
|
|
||||||
import path from "path";
|
|
||||||
|
|
||||||
const defaultConfig = {
|
const defaultConfig = {
|
||||||
codexExe: "",
|
codexRoot: getDefaultCodexRootPath(),
|
||||||
// User-selected config options:
|
|
||||||
codexInstallPath: getCodexBinPath(),
|
|
||||||
codexConfigFilePath: getCodexConfigFilePath(),
|
|
||||||
dataDir: getCodexDataDirDefaultPath(),
|
|
||||||
logsDir: getCodexLogsDefaultPath(),
|
|
||||||
storageQuota: 8 * 1024 * 1024 * 1024,
|
storageQuota: 8 * 1024 * 1024 * 1024,
|
||||||
ports: {
|
ports: {
|
||||||
discPort: 8090,
|
discPort: 8090,
|
||||||
@ -23,9 +10,14 @@ const defaultConfig = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const datadir = "datadir";
|
||||||
|
const codexLogFile = "codex.log";
|
||||||
|
const codexConfigFile = "config.toml";
|
||||||
|
|
||||||
export class ConfigService {
|
export class ConfigService {
|
||||||
constructor(fsService) {
|
constructor(fsService, osService) {
|
||||||
this.fs = fsService;
|
this.fs = fsService;
|
||||||
|
this.os = osService;
|
||||||
this.loadConfig();
|
this.loadConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,6 +25,19 @@ export class ConfigService {
|
|||||||
return this.config;
|
return this.config;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
getCodexExe = () => {
|
||||||
|
var codexExe = "codex";
|
||||||
|
if (this.os.isWindows()) {
|
||||||
|
codexExe = "codex.exe";
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.fs.pathJoin([this.config.codexRoot, codexExe]);
|
||||||
|
};
|
||||||
|
|
||||||
|
getCodexConfigFilePath = () => {
|
||||||
|
return this.fs.pathJoin([this.config.codexRoot, codexConfigFile]);
|
||||||
|
};
|
||||||
|
|
||||||
loadConfig = () => {
|
loadConfig = () => {
|
||||||
const filePath = this.getConfigFilename();
|
const filePath = this.getConfigFilename();
|
||||||
try {
|
try {
|
||||||
@ -66,29 +71,7 @@ export class ConfigService {
|
|||||||
return this.fs.pathJoin([getAppDataDir(), "config.json"]);
|
return this.fs.pathJoin([getAppDataDir(), "config.json"]);
|
||||||
};
|
};
|
||||||
|
|
||||||
getLogFilePath = () => {
|
|
||||||
// function getCurrentLogFile(config) {
|
|
||||||
// const timestamp = new Date()
|
|
||||||
// .toISOString()
|
|
||||||
// .replaceAll(":", "-")
|
|
||||||
// .replaceAll(".", "-");
|
|
||||||
// return path.join(config.logsDir, `codex_${timestamp}.log`);
|
|
||||||
// }
|
|
||||||
// todo, maybe use timestamp
|
|
||||||
|
|
||||||
return this.fs.pathJoin([this.config.logsDir, "codex.log"]);
|
|
||||||
};
|
|
||||||
|
|
||||||
missing = (name) => {
|
|
||||||
throw new Error(`Missing config value: ${name}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
validateConfiguration = () => {
|
validateConfiguration = () => {
|
||||||
if (this.config.codexExe.length < 1) this.missing("codexExe");
|
|
||||||
if (this.config.codexConfigFilePath.length < 1)
|
|
||||||
this.missing("codexConfigFilePath");
|
|
||||||
if (this.config.dataDir.length < 1) this.missing("dataDir");
|
|
||||||
if (this.config.logsDir.length < 1) this.missing("logsDir");
|
|
||||||
if (this.config.storageQuota < 1024 * 1024 * 100)
|
if (this.config.storageQuota < 1024 * 1024 * 100)
|
||||||
throw new Error("Storage quota must be at least 100MB");
|
throw new Error("Storage quota must be at least 100MB");
|
||||||
};
|
};
|
||||||
@ -100,10 +83,10 @@ export class ConfigService {
|
|||||||
const bootNodes = bootstrapNodes.map((v) => `"${v}"`).join(",");
|
const bootNodes = bootstrapNodes.map((v) => `"${v}"`).join(",");
|
||||||
|
|
||||||
this.fs.writeFile(
|
this.fs.writeFile(
|
||||||
this.config.codexConfigFilePath,
|
this.getCodexConfigFilePath(),
|
||||||
`data-dir="${this.format(this.toRelative(this.config.dataDir))}"${nl}` +
|
`data-dir="${datadir}"${nl}` +
|
||||||
`log-level="DEBUG"${nl}` +
|
`log-level="DEBUG"${nl}` +
|
||||||
`log-file="${this.format(this.getLogFilePath())}"${nl}` +
|
`log-file="${codexLogFile}"${nl}` +
|
||||||
`storage-quota=${this.config.storageQuota}${nl}` +
|
`storage-quota=${this.config.storageQuota}${nl}` +
|
||||||
`disc-port=${this.config.ports.discPort}${nl}` +
|
`disc-port=${this.config.ports.discPort}${nl}` +
|
||||||
`listen-addrs=["/ip4/0.0.0.0/tcp/${this.config.ports.listenPort}"]${nl}` +
|
`listen-addrs=["/ip4/0.0.0.0/tcp/${this.config.ports.listenPort}"]${nl}` +
|
||||||
@ -113,12 +96,4 @@ export class ConfigService {
|
|||||||
`bootstrap-node=[${bootNodes}]${nl}`,
|
`bootstrap-node=[${bootNodes}]${nl}`,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
format = (str) => {
|
|
||||||
return str.replaceAll("\\", "/");
|
|
||||||
};
|
|
||||||
|
|
||||||
toRelative = (str) => {
|
|
||||||
return this.fs.toRelativePath(this.config.codexInstallPath, str);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,21 +1,11 @@
|
|||||||
import { describe, beforeEach, it, expect, vi } from "vitest";
|
import { describe, beforeEach, it, expect, vi } from "vitest";
|
||||||
import { ConfigService } from "./configService.js";
|
import { ConfigService } from "./configService.js";
|
||||||
import { mockFsService } from "../__mocks__/service.mocks.js";
|
import { mockFsService, mockOsService } from "../__mocks__/service.mocks.js";
|
||||||
import {
|
import { getAppDataDir, getDefaultCodexRootPath } from "../utils/appData.js";
|
||||||
getAppDataDir,
|
|
||||||
getCodexBinPath,
|
|
||||||
getCodexConfigFilePath,
|
|
||||||
getCodexDataDirDefaultPath,
|
|
||||||
getCodexLogsDefaultPath,
|
|
||||||
} from "../utils/appData.js";
|
|
||||||
|
|
||||||
function getDefaultConfig() {
|
function getDefaultConfig() {
|
||||||
return {
|
return {
|
||||||
codexExe: "",
|
codexRoot: getDefaultCodexRootPath(),
|
||||||
codexInstallPath: getCodexBinPath(),
|
|
||||||
codexConfigFilePath: getCodexConfigFilePath(),
|
|
||||||
dataDir: getCodexDataDirDefaultPath(),
|
|
||||||
logsDir: getCodexLogsDefaultPath(),
|
|
||||||
storageQuota: 8 * 1024 * 1024 * 1024,
|
storageQuota: 8 * 1024 * 1024 * 1024,
|
||||||
ports: {
|
ports: {
|
||||||
discPort: 8090,
|
discPort: 8090,
|
||||||
@ -38,7 +28,7 @@ describe("ConfigService", () => {
|
|||||||
|
|
||||||
describe("constructor", () => {
|
describe("constructor", () => {
|
||||||
it("formats the config file path", () => {
|
it("formats the config file path", () => {
|
||||||
new ConfigService(mockFsService);
|
new ConfigService(mockFsService, mockOsService);
|
||||||
|
|
||||||
expect(mockFsService.pathJoin).toHaveBeenCalledWith([
|
expect(mockFsService.pathJoin).toHaveBeenCalledWith([
|
||||||
getAppDataDir(),
|
getAppDataDir(),
|
||||||
@ -49,7 +39,7 @@ describe("ConfigService", () => {
|
|||||||
it("saves the default config when the config.json file does not exist", () => {
|
it("saves the default config when the config.json file does not exist", () => {
|
||||||
mockFsService.isFile.mockReturnValue(false);
|
mockFsService.isFile.mockReturnValue(false);
|
||||||
|
|
||||||
const service = new ConfigService(mockFsService);
|
const service = new ConfigService(mockFsService, mockOsService);
|
||||||
|
|
||||||
expect(mockFsService.isFile).toHaveBeenCalledWith(configPath);
|
expect(mockFsService.isFile).toHaveBeenCalledWith(configPath);
|
||||||
expect(mockFsService.readJsonFile).not.toHaveBeenCalled();
|
expect(mockFsService.readJsonFile).not.toHaveBeenCalled();
|
||||||
@ -67,7 +57,7 @@ describe("ConfigService", () => {
|
|||||||
};
|
};
|
||||||
mockFsService.readJsonFile.mockReturnValue(savedConfig);
|
mockFsService.readJsonFile.mockReturnValue(savedConfig);
|
||||||
|
|
||||||
const service = new ConfigService(mockFsService);
|
const service = new ConfigService(mockFsService, mockOsService);
|
||||||
|
|
||||||
expect(mockFsService.isFile).toHaveBeenCalledWith(configPath);
|
expect(mockFsService.isFile).toHaveBeenCalledWith(configPath);
|
||||||
expect(mockFsService.readJsonFile).toHaveBeenCalledWith(configPath);
|
expect(mockFsService.readJsonFile).toHaveBeenCalledWith(configPath);
|
||||||
@ -76,17 +66,48 @@ describe("ConfigService", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("getLogFilePath", () => {
|
describe("getCodexExe", () => {
|
||||||
it("joins the logsDir with the log filename", () => {
|
var configService;
|
||||||
const service = new ConfigService(mockFsService);
|
const result = "path/to/codex";
|
||||||
|
|
||||||
const result = "path/to/codex.log";
|
beforeEach(() => {
|
||||||
|
mockFsService.isFile.mockReturnValue(false);
|
||||||
mockFsService.pathJoin.mockReturnValue(result);
|
mockFsService.pathJoin.mockReturnValue(result);
|
||||||
|
configService = new ConfigService(mockFsService, mockOsService);
|
||||||
|
});
|
||||||
|
|
||||||
expect(service.getLogFilePath()).toBe(result);
|
it("joins the codex root with the non-Windows specific exe name", () => {
|
||||||
|
mockOsService.isWindows.mockReturnValue(false);
|
||||||
|
|
||||||
|
expect(configService.getCodexExe()).toBe(result);
|
||||||
expect(mockFsService.pathJoin).toHaveBeenCalledWith([
|
expect(mockFsService.pathJoin).toHaveBeenCalledWith([
|
||||||
expectedDefaultConfig.logsDir,
|
expectedDefaultConfig.codexRoot,
|
||||||
"codex.log",
|
"codex",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("joins the codex root with the Windows specific exe name", () => {
|
||||||
|
mockOsService.isWindows.mockReturnValue(true);
|
||||||
|
|
||||||
|
expect(configService.getCodexExe()).toBe(result);
|
||||||
|
expect(mockFsService.pathJoin).toHaveBeenCalledWith([
|
||||||
|
expectedDefaultConfig.codexRoot,
|
||||||
|
"codex.exe",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getCodexConfigFilePath", () => {
|
||||||
|
const result = "path/to/codex";
|
||||||
|
|
||||||
|
it("joins the codex root and codexConfigFile", () => {
|
||||||
|
mockFsService.pathJoin.mockReturnValue(result);
|
||||||
|
const configService = new ConfigService(mockFsService, mockOsService);
|
||||||
|
|
||||||
|
expect(configService.getCodexConfigFilePath()).toBe(result);
|
||||||
|
expect(mockFsService.pathJoin).toHaveBeenCalledWith([
|
||||||
|
expectedDefaultConfig.codexRoot,
|
||||||
|
"config.toml",
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -99,42 +120,10 @@ describe("ConfigService", () => {
|
|||||||
config = expectedDefaultConfig;
|
config = expectedDefaultConfig;
|
||||||
config.codexExe = "codex.exe";
|
config.codexExe = "codex.exe";
|
||||||
|
|
||||||
configService = new ConfigService(mockFsService);
|
configService = new ConfigService(mockFsService, mockOsService);
|
||||||
configService.config = config;
|
configService.config = config;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("throws when codexExe is not set", () => {
|
|
||||||
config.codexExe = "";
|
|
||||||
|
|
||||||
expect(configService.validateConfiguration).toThrow(
|
|
||||||
"Missing config value: codexExe",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("throws when codexConfigFilePath is not set", () => {
|
|
||||||
config.codexConfigFilePath = "";
|
|
||||||
|
|
||||||
expect(configService.validateConfiguration).toThrow(
|
|
||||||
"Missing config value: codexConfigFilePath",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("throws when dataDir is not set", () => {
|
|
||||||
config.dataDir = "";
|
|
||||||
|
|
||||||
expect(configService.validateConfiguration).toThrow(
|
|
||||||
"Missing config value: dataDir",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("throws when logsDir is not set", () => {
|
|
||||||
config.logsDir = "";
|
|
||||||
|
|
||||||
expect(configService.validateConfiguration).toThrow(
|
|
||||||
"Missing config value: logsDir",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("throws when storageQuota is less than 100 MB", () => {
|
it("throws when storageQuota is less than 100 MB", () => {
|
||||||
config.storageQuota = 1024 * 1024 * 99;
|
config.storageQuota = 1024 * 1024 * 99;
|
||||||
|
|
||||||
@ -148,7 +137,7 @@ describe("ConfigService", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("writecodexConfigFile", () => {
|
describe("writeCodexConfigFile", () => {
|
||||||
const logsPath = "C:\\path\\codex.log";
|
const logsPath = "C:\\path\\codex.log";
|
||||||
var configService;
|
var configService;
|
||||||
|
|
||||||
@ -156,32 +145,31 @@ describe("ConfigService", () => {
|
|||||||
// use the default config:
|
// use the default config:
|
||||||
mockFsService.isFile.mockReturnValue(false);
|
mockFsService.isFile.mockReturnValue(false);
|
||||||
|
|
||||||
configService = new ConfigService(mockFsService);
|
configService = new ConfigService(mockFsService, mockOsService);
|
||||||
configService.validateConfiguration = vi.fn();
|
configService.validateConfiguration = vi.fn();
|
||||||
configService.getLogFilePath = vi.fn();
|
configService.getLogFilePath = vi.fn();
|
||||||
configService.getLogFilePath.mockReturnValue(logsPath);
|
configService.getLogFilePath.mockReturnValue(logsPath);
|
||||||
});
|
});
|
||||||
|
|
||||||
function formatPath(str) {
|
|
||||||
return str.replaceAll("\\", "/");
|
|
||||||
}
|
|
||||||
|
|
||||||
it("writes the config file values to the config TOML file", () => {
|
it("writes the config file values to the config TOML file", () => {
|
||||||
const publicIp = "1.2.3.4";
|
const publicIp = "1.2.3.4";
|
||||||
const bootstrapNodes = ["boot111", "boot222", "boot333"];
|
const bootstrapNodes = ["boot111", "boot222", "boot333"];
|
||||||
const relativeDataDirPath = "..\\../datadir";
|
const expectedDataDir = "datadir";
|
||||||
|
const expectedLogFile = "codex.log";
|
||||||
|
const codexConfigFilePath = "/path/to/config.toml";
|
||||||
|
|
||||||
mockFsService.toRelativePath.mockReturnValue(relativeDataDirPath);
|
configService.getCodexConfigFilePath = vi.fn();
|
||||||
|
configService.getCodexConfigFilePath.mockReturnValue(codexConfigFilePath);
|
||||||
|
|
||||||
configService.writeCodexConfigFile(publicIp, bootstrapNodes);
|
configService.writeCodexConfigFile(publicIp, bootstrapNodes);
|
||||||
|
|
||||||
const newLine = "\n";
|
const newLine = "\n";
|
||||||
|
|
||||||
expect(mockFsService.writeFile).toHaveBeenCalledWith(
|
expect(mockFsService.writeFile).toHaveBeenCalledWith(
|
||||||
expectedDefaultConfig.codexConfigFilePath,
|
codexConfigFilePath,
|
||||||
`data-dir=\"${formatPath(relativeDataDirPath)}"${newLine}` +
|
`data-dir=\"${expectedDataDir}"${newLine}` +
|
||||||
`log-level="DEBUG"${newLine}` +
|
`log-level="DEBUG"${newLine}` +
|
||||||
`log-file="${formatPath(logsPath)}"${newLine}` +
|
`log-file="${expectedLogFile}"${newLine}` +
|
||||||
`storage-quota=${expectedDefaultConfig.storageQuota}${newLine}` +
|
`storage-quota=${expectedDefaultConfig.storageQuota}${newLine}` +
|
||||||
`disc-port=${expectedDefaultConfig.ports.discPort}${newLine}` +
|
`disc-port=${expectedDefaultConfig.ports.discPort}${newLine}` +
|
||||||
`listen-addrs=["/ip4/0.0.0.0/tcp/${expectedDefaultConfig.ports.listenPort}"]${newLine}` +
|
`listen-addrs=["/ip4/0.0.0.0/tcp/${expectedDefaultConfig.ports.listenPort}"]${newLine}` +
|
||||||
@ -194,11 +182,6 @@ describe("ConfigService", () => {
|
|||||||
})
|
})
|
||||||
.join(",")}]${newLine}`,
|
.join(",")}]${newLine}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(mockFsService.toRelativePath).toHaveBeenCalledWith(
|
|
||||||
expectedDefaultConfig.codexInstallPath,
|
|
||||||
expectedDefaultConfig.dataDir,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -67,7 +67,10 @@ export class FsService {
|
|||||||
fs.writeFileSync(filePath, content);
|
fs.writeFileSync(filePath, content);
|
||||||
};
|
};
|
||||||
|
|
||||||
toRelativePath = (from, to) => {
|
ensureDirExists = (dir) => {
|
||||||
return path.relative(from, to);
|
if (!fs.existsSync(dir)) {
|
||||||
|
fs.mkdirSync(dir, { recursive: true });
|
||||||
|
}
|
||||||
|
return dir;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,4 +25,8 @@ export class OsService {
|
|||||||
listProcesses = async () => {
|
listProcesses = async () => {
|
||||||
return await psList();
|
return await psList();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
stopProcess = (pid) => {
|
||||||
|
process.kill(pid, "SIGINT");
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,8 +16,9 @@ export class ShellService {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
spawnDetachedProcess = async (cmd, args) => {
|
spawnDetachedProcess = async (cmd, workingDir, args) => {
|
||||||
var child = spawn(cmd, args, {
|
var child = spawn(cmd, args, {
|
||||||
|
cwd: workingDir,
|
||||||
detached: true,
|
detached: true,
|
||||||
stdio: ["ignore", "ignore", "ignore"],
|
stdio: ["ignore", "ignore", "ignore"],
|
||||||
});
|
});
|
||||||
|
|||||||
@ -18,7 +18,7 @@ export class InstallMenu {
|
|||||||
showInstallMenu = async () => {
|
showInstallMenu = async () => {
|
||||||
await this.ui.askMultipleChoice("Configure your Codex installation", [
|
await this.ui.askMultipleChoice("Configure your Codex installation", [
|
||||||
{
|
{
|
||||||
label: "Install path: " + this.config.codexInstallPath,
|
label: "Install path: " + this.config.codexRoot,
|
||||||
action: this.selectInstallPath,
|
action: this.selectInstallPath,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -53,7 +53,8 @@ export class InstallMenu {
|
|||||||
this.ui.showInfoMessage(
|
this.ui.showInfoMessage(
|
||||||
"You are about to:\n" +
|
"You are about to:\n" +
|
||||||
" - Uninstall the Codex application\n" +
|
" - Uninstall the Codex application\n" +
|
||||||
" - Delete the data stored in your Codex node",
|
" - Delete the data stored in your Codex node\n" +
|
||||||
|
" - Delete the log files of your Codex node",
|
||||||
);
|
);
|
||||||
|
|
||||||
await this.ui.askMultipleChoice(
|
await this.ui.askMultipleChoice(
|
||||||
@ -72,8 +73,8 @@ export class InstallMenu {
|
|||||||
};
|
};
|
||||||
|
|
||||||
selectInstallPath = async () => {
|
selectInstallPath = async () => {
|
||||||
this.config.codexInstallPath = await this.pathSelector.show(
|
this.config.codexRoot = await this.pathSelector.show(
|
||||||
this.config.codexInstallPath,
|
this.config.codexRoot,
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
this.configService.saveConfig();
|
this.configService.saveConfig();
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { mockInstaller } from "../__mocks__/handler.mocks.js";
|
|||||||
|
|
||||||
describe("InstallMenu", () => {
|
describe("InstallMenu", () => {
|
||||||
const config = {
|
const config = {
|
||||||
codexInstallPath: "/codex",
|
codexRoot: "/codex",
|
||||||
};
|
};
|
||||||
let installMenu;
|
let installMenu;
|
||||||
|
|
||||||
@ -52,7 +52,7 @@ describe("InstallMenu", () => {
|
|||||||
"Configure your Codex installation",
|
"Configure your Codex installation",
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
label: "Install path: " + config.codexInstallPath,
|
label: "Install path: " + config.codexRoot,
|
||||||
action: installMenu.selectInstallPath,
|
action: installMenu.selectInstallPath,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -94,7 +94,8 @@ describe("InstallMenu", () => {
|
|||||||
expect(mockUiService.showInfoMessage).toHaveBeenCalledWith(
|
expect(mockUiService.showInfoMessage).toHaveBeenCalledWith(
|
||||||
"You are about to:\n" +
|
"You are about to:\n" +
|
||||||
" - Uninstall the Codex application\n" +
|
" - Uninstall the Codex application\n" +
|
||||||
" - Delete the data stored in your Codex node",
|
" - Delete the data stored in your Codex node\n" +
|
||||||
|
" - Delete the log files of your Codex node",
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(mockUiService.askMultipleChoice).toHaveBeenCalledWith(
|
expect(mockUiService.askMultipleChoice).toHaveBeenCalledWith(
|
||||||
@ -113,14 +114,14 @@ describe("InstallMenu", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("allows selecting the install path", async () => {
|
it("allows selecting the install path", async () => {
|
||||||
const originalPath = config.codexInstallPath;
|
const originalPath = config.codexRoot;
|
||||||
const newPath = "/new/path";
|
const newPath = "/new/path";
|
||||||
mockPathSelector.show.mockResolvedValue(newPath);
|
mockPathSelector.show.mockResolvedValue(newPath);
|
||||||
|
|
||||||
await installMenu.selectInstallPath();
|
await installMenu.selectInstallPath();
|
||||||
|
|
||||||
expect(mockPathSelector.show).toHaveBeenCalledWith(originalPath, false);
|
expect(mockPathSelector.show).toHaveBeenCalledWith(originalPath, false);
|
||||||
expect(config.codexInstallPath).toBe(newPath);
|
expect(config.codexRoot).toBe(newPath);
|
||||||
expect(mockConfigService.saveConfig).toHaveBeenCalled();
|
expect(mockConfigService.saveConfig).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -5,28 +5,10 @@ export function getAppDataDir() {
|
|||||||
return ensureExists(appData("codex-cli"));
|
return ensureExists(appData("codex-cli"));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCodexRootPath() {
|
export function getDefaultCodexRootPath() {
|
||||||
return ensureExists(appData("codex"));
|
return ensureExists(appData("codex"));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCodexBinPath() {
|
|
||||||
return ensureExists(path.join(appData("codex"), "bin"));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCodexConfigFilePath() {
|
|
||||||
return path.join(appData("codex"), "bin", "config.toml");
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCodexDataDirDefaultPath() {
|
|
||||||
// This path does not exist on first startup. That's good: Codex will
|
|
||||||
// create it with the required access permissions.
|
|
||||||
return path.join(appData("codex"), "datadir");
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCodexLogsDefaultPath() {
|
|
||||||
return ensureExists(path.join(appData("codex"), "logs"));
|
|
||||||
}
|
|
||||||
|
|
||||||
function ensureExists(dir) {
|
function ensureExists(dir) {
|
||||||
if (!fs.existsSync(dir)) {
|
if (!fs.existsSync(dir)) {
|
||||||
fs.mkdirSync(dir, { recursive: true });
|
fs.mkdirSync(dir, { recursive: true });
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user