Merge pull request #6 from codex-storage/feature/codex-config

Config management
This commit is contained in:
Guru 2025-02-25 03:35:59 +05:30 committed by GitHub
commit 72f48bf7d6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 384 additions and 130 deletions

View File

@ -23,6 +23,8 @@
"chalk": "^5.3.0",
"inquirer": "^9.2.12",
"mime-types": "^2.1.35",
"nanospinner": "^1.1.0"
"nanospinner": "^1.1.0",
"fs-extra": "^11.3.0",
"fs-filesystem": "^2.1.2"
}
}

134
src/configmenu.js Normal file
View File

@ -0,0 +1,134 @@
import inquirer from 'inquirer';
import chalk from 'chalk';
import { showErrorMessage, showInfoMessage } from './utils/messages.js';
import { isDir, showPathSelector } from './utils/pathSelector.js';
import { saveConfig } from './services/config.js';
import { showNumberSelector } from './utils/numberSelector.js';
import fs from 'fs-extra';
function bytesAmountToString(numBytes) {
const units = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
var value = numBytes;
var index = 0;
while (value > 1024) {
index = index + 1;
value = value / 1024;
}
if (index == 0) return `${numBytes} Bytes`;
return `${numBytes} Bytes (${value} ${units[index]})`;
}
async function showStorageQuotaSelector(config) {
console.log(showInfoMessage('You can use: "GB" or "gb", etc.'));
const result = await showNumberSelector(config.storageQuota, "Storage quota", true);
if (result < (100 * 1024 * 1024)) {
console.log(showErrorMessage("Storage quote should be >= 100mb."));
return config.storageQuota;
}
return result;
}
export async function showConfigMenu(config) {
var newDataDir = config.dataDir;
try {
while (true) {
console.log(showInfoMessage("Codex Configuration"));
const { choice } = await inquirer.prompt([
{
type: 'list',
name: 'choice',
message: 'Select to edit:',
choices: [
`1. Data path = "${newDataDir}"`,
`2. Logs path = "${config.logsDir}"`,
`3. Storage quota = ${bytesAmountToString(config.storageQuota)}`,
`4. Discovery port = ${config.ports.discPort}`,
`5. P2P listen port = ${config.ports.listenPort}`,
`6. API port = ${config.ports.apiPort}`,
'7. Save changes and exit',
'8. Discard changes and exit'
],
pageSize: 8,
loop: true
}
]).catch(() => {
return;
});
switch (choice.split('.')[0]) {
case '1':
newDataDir = await showPathSelector(config.dataDir, false);
if (isDir(newDataDir)) {
console.log(showInfoMessage("Warning: The new data path already exists. Make sure you know what you're doing."));
}
break;
case '2':
config.logsDir = await showPathSelector(config.logsDir, true);
break;
case '3':
config.storageQuota = await showStorageQuotaSelector(config);
break;
case '4':
config.ports.discPort = await showNumberSelector(config.ports.discPort, "Discovery Port (UDP)", false);
break;
case '5':
config.ports.listenPort = await showNumberSelector(config.ports.listenPort, "Listen Port (TCP)", false);
break;
case '6':
config.ports.apiPort = await showNumberSelector(config.ports.apiPort, "API Port (TCP)", false);
break;
case '7':
// save changes, back to main menu
config = updateDataDir(config, newDataDir);
saveConfig(config);
return;
case '8':
// discard changes, back to main menu
return;
}
}
} catch (error) {
console.error(chalk.red('An error occurred:', error.message));
return;
}
}
function updateDataDir(config, newDataDir) {
if (config.dataDir == newDataDir) return config;
// The Codex dataDir is a little strange:
// If the old one is empty: The new one should not exist, so that codex creates it
// with the correct security permissions.
// If the old one does exist: We move it.
if (isDir(config.dataDir)) {
console.log(showInfoMessage(
'Moving Codex data folder...\n' +
`From: "${config.dataDir}"\n` +
`To: "${newDataDir}"`
));
try {
fs.moveSync(config.dataDir, newDataDir);
} catch (error) {
console.log(showErrorMessage("Error while moving dataDir: " + error.message));
throw error;
}
} else {
// Old data dir does not exist.
if (isDir(newDataDir)) {
console.log(showInfoMessage(
"Warning: the selected data path already exists.\n" +
`New data path = "${newDataDir}"\n` +
"Codex may overwrite data in this folder.\n" +
"Codex will fail to start if this folder does not have the required\n" +
"security permissions."
));
}
}
config.dataDir = newDataDir;
return config;
}

View File

@ -10,8 +10,8 @@ import path from 'path';
import mime from 'mime-types';
import axios from 'axios';
export async function uploadFile(filePath = null, handleCommandLineOperation, showNavigationMenu) {
const nodeRunning = await isNodeRunning();
export async function uploadFile(config, filePath = null, handleCommandLineOperation, showNavigationMenu) {
const nodeRunning = await isNodeRunning(config);
if (!nodeRunning) {
console.log(showErrorMessage('Codex node is not running. Try again after starting the node'));
return handleCommandLineOperation() ? process.exit(1) : showNavigationMenu();
@ -51,7 +51,7 @@ export async function uploadFile(filePath = null, handleCommandLineOperation, sh
const spinner = createSpinner('Uploading file').start();
try {
const result = await runCommand(
`curl -X POST http://localhost:8080/api/codex/v1/data ` +
`curl -X POST http://localhost:${config.ports.apiPort}/api/codex/v1/data ` +
`-H 'Content-Type: ${contentType}' ` +
`-H 'Content-Disposition: attachment; filename="${filename}"' ` +
`-w '\\n' -T "${fileToUpload}"`
@ -71,8 +71,8 @@ export async function uploadFile(filePath = null, handleCommandLineOperation, sh
return handleCommandLineOperation() ? process.exit(0) : showNavigationMenu();
}
export async function downloadFile(cid = null, handleCommandLineOperation, showNavigationMenu) {
const nodeRunning = await isNodeRunning();
export async function downloadFile(config, cid = null, handleCommandLineOperation, showNavigationMenu) {
const nodeRunning = await isNodeRunning(config);
if (!nodeRunning) {
console.log(showErrorMessage('Codex node is not running. Try again after starting the node'));
return handleCommandLineOperation() ? process.exit(1) : showNavigationMenu();
@ -95,7 +95,7 @@ export async function downloadFile(cid = null, handleCommandLineOperation, showN
const spinner = createSpinner('Fetching file metadata...').start();
try {
// First, get the file metadata
const metadataResponse = await axios.post(`http://localhost:8080/api/codex/v1/data/${cidToDownload}/network`);
const metadataResponse = await axios.post(`http://localhost:${config.ports.apiPort}/api/codex/v1/data/${cidToDownload}/network`);
const { manifest } = metadataResponse.data;
const { filename, mimetype } = manifest;
@ -103,7 +103,7 @@ export async function downloadFile(cid = null, handleCommandLineOperation, showN
spinner.start('Downloading file...');
// Then download the file with the correct filename
await runCommand(`curl "http://localhost:8080/api/codex/v1/data/${cidToDownload}/network/stream" -o "${filename}"`);
await runCommand(`curl "http://localhost:${config.ports.apiPort}/api/codex/v1/data/${cidToDownload}/network/stream" -o "${filename}"`);
spinner.success();
console.log(showSuccessMessage(
@ -144,8 +144,8 @@ export async function downloadFile(cid = null, handleCommandLineOperation, showN
return handleCommandLineOperation() ? process.exit(0) : showNavigationMenu();
}
export async function showLocalFiles(showNavigationMenu) {
const nodeRunning = await isNodeRunning();
export async function showLocalFiles(config, showNavigationMenu) {
const nodeRunning = await isNodeRunning(config);
if (!nodeRunning) {
console.log(showErrorMessage('Codex node is not running. Try again after starting the node'));
await showNavigationMenu();
@ -154,11 +154,10 @@ export async function showLocalFiles(showNavigationMenu) {
try {
const spinner = createSpinner('Fetching local files...').start();
const filesResponse = await runCommand('curl http://localhost:8080/api/codex/v1/data -w \'\\n\'');
const filesResponse = await axios.get(`http://localhost:${config.ports.apiPort}/api/codex/v1/data`);
const filesData = filesResponse.data;
spinner.success();
const filesData = JSON.parse(filesResponse);
if (filesData.content && filesData.content.length > 0) {
console.log(showInfoMessage(`Found ${filesData.content.length} local file(s)`));
@ -187,6 +186,8 @@ export async function showLocalFiles(showNavigationMenu) {
}
));
});
} else {
console.log(showInfoMessage("Node contains no datasets."));
}
} catch (error) {
console.log(showErrorMessage(`Failed to fetch local files: ${error.message}`));

View File

@ -9,7 +9,7 @@ import { runCommand } from '../utils/command.js';
import { showErrorMessage, showInfoMessage, showSuccessMessage } from '../utils/messages.js';
import { checkDependencies } from '../services/nodeService.js';
import { saveConfig } from '../services/config.js';
import { getCodexRootPath, getCodexBinPath, getCodexDataDirDefaultPath, getCodexLogsPath } from '../utils/appdata.js';
import { getCodexRootPath, getCodexBinPath } from '../utils/appdata.js';
const platform = os.platform();
@ -68,23 +68,22 @@ export async function getCodexVersion(config) {
}
}
export async function checkCodexInstallation(config, showNavigationMenu) {
export async function installCodex(config, showNavigationMenu) {
const version = await getCodexVersion(config);
if (version.length > 0) {
console.log(chalk.green('Codex is already installed. Version:'));
console.log(chalk.green(version));
await showNavigationMenu();
return false;
} else {
console.log(chalk.cyanBright('Codex is not installed, proceeding with installation...'));
await installCodex(config, showNavigationMenu);
return await performInstall(config);
}
}
async function saveCodexExePathToConfig(config, codexExePath) {
async function saveCodexExePath(config, codexExePath) {
config.codexExe = codexExePath;
config.dataDir = getCodexDataDirDefaultPath();
config.logsDir = getCodexLogsPath();
if (!fs.existsSync(config.codexExe)) {
console.log(showErrorMessage(`Codex executable not found in expected path: ${config.codexExe}`));
throw new Error("Exe not found");
@ -98,17 +97,14 @@ async function saveCodexExePathToConfig(config, codexExePath) {
async function clearCodexExePathFromConfig(config) {
config.codexExe = "";
config.dataDir = "";
config.logsDir = "";
saveConfig(config);
}
export async function installCodex(config, showNavigationMenu) {
async function performInstall(config) {
const agreed = await showPrivacyDisclaimer();
if (!agreed) {
console.log(showInfoMessage('You can find manual setup instructions at docs.codex.storage'));
process.exit(0);
return;
}
const installPath = getCodexBinPath();
@ -117,7 +113,6 @@ export async function installCodex(config, showNavigationMenu) {
const spinner = createSpinner('Installing Codex...').start();
try {
if (platform === 'win32') {
try {
try {
@ -129,7 +124,7 @@ export async function installCodex(config, showNavigationMenu) {
await runCommand('curl -LO --ssl-no-revoke https://get.codex.storage/install.cmd');
await runCommand(`set "INSTALL_DIR=${installPath}" && "${process.cwd()}\\install.cmd"`);
await saveCodexExePathToConfig(config, path.join(installPath, "codex.exe"));
await saveCodexExePath(config, path.join(installPath, "codex.exe"));
try {
await runCommand('del /f install.cmd');
@ -171,7 +166,7 @@ export async function installCodex(config, showNavigationMenu) {
await runCommand(`INSTALL_DIR="${installPath}" timeout 120 bash install.sh`);
}
await saveCodexExePathToConfig(config, path.join(installPath, "codex"));
await saveCodexExePath(config, path.join(installPath, "codex"));
} catch (error) {
if (error.message.includes('ECONNREFUSED') || error.message.includes('ETIMEDOUT')) {
@ -190,21 +185,28 @@ export async function installCodex(config, showNavigationMenu) {
try {
const version = await getCodexVersion(config);
console.log(chalk.green(version));
console.log(showSuccessMessage(
'Codex is successfully installed!\n' +
`Install path: "${config.codexExe}"\n\n` +
`Version: ${version}`
'The default configuration should work for most platforms.\n' +
'Please review the configuration before starting Codex.\n'
));
} catch (error) {
throw new Error('Installation completed but Codex command is not available. Please restart your terminal and try again.');
}
console.log(showInfoMessage(
"Please review the configuration before starting Codex."
));
spinner.success();
await showNavigationMenu();
return true;
} catch (error) {
spinner.error();
console.log(showErrorMessage(`Failed to install Codex: ${error.message}`));
await showNavigationMenu();
return false;
}
}

View File

@ -43,27 +43,12 @@ export async function runCodex(config, showNavigationMenu) {
return;
}
const nodeAlreadyRunning = await isNodeRunning();
const nodeAlreadyRunning = await isNodeRunning(config);
if (nodeAlreadyRunning) {
console.log(showInfoMessage('A Codex node is already running.'));
await showNavigationMenu();
} else {
const { discPort, listenPort } = await inquirer.prompt([
{
type: 'number',
name: 'discPort',
message: 'Enter the discovery port (default is 8090):',
default: 8090
},
{
type: 'number',
name: 'listenPort',
message: 'Enter the listening port (default is 8070):',
default: 8070
}
]);
try {
let nat;
if (platform === 'win32') {
@ -79,15 +64,19 @@ export async function runCodex(config, showNavigationMenu) {
console.log(showInfoMessage(
`Data location: ${config.dataDir}\n` +
`Logs: ${logFilePath}`
`Logs: ${logFilePath}\n` +
`API port: ${config.ports.apiPort}`
));
const executable = config.codexExe;
const args = [
`--data-dir="${config.dataDir}"`,
`--log-level=DEBUG`,
`--log-file="${logFilePath}"`,
`--disc-port=${discPort}`,
`--listen-addrs=/ip4/0.0.0.0/tcp/${listenPort}`,
`--storage-quota="${config.storageQuota}"`,
`--disc-port=${config.ports.discPort}`,
`--listen-addrs=/ip4/0.0.0.0/tcp/${config.ports.listenPort}`,
`--api-port=${config.ports.apiPort}`,
`--nat=${nat}`,
`--api-cors-origin="*"`,
`--bootstrap-node=spr:CiUIAhIhAiJvIcA_ZwPZ9ugVKDbmqwhJZaig5zKyLiuaicRcCGqLEgIDARo8CicAJQgCEiECIm8hwD9nA9n26BUoNuarCEllqKDnMrIuK5qJxFwIaosQ3d6esAYaCwoJBJ_f8zKRAnU6KkYwRAIgM0MvWNJL296kJ9gWvfatfmVvT-A7O2s8Mxp8l9c8EW0CIC-h-H-jBVSgFjg3Eny2u33qF7BDnWFzo7fGfZ7_qc9P`
@ -108,7 +97,7 @@ export async function runCodex(config, showNavigationMenu) {
await new Promise(resolve => setTimeout(resolve, 5000));
try {
const response = await axios.get('http://localhost:8080/api/codex/v1/debug/info');
const response = await axios.get(`http://localhost:${config.ports.apiPort}/api/codex/v1/debug/info`);
if (response.status === 200) {
// Check if wallet exists
try {
@ -126,7 +115,7 @@ export async function runCodex(config, showNavigationMenu) {
}
// Start periodic logging
const stopLogging = await startPeriodicLogging();
const stopLogging = await startPeriodicLogging(config);
nodeProcess.on('exit', () => {
stopLogging();
@ -260,13 +249,13 @@ async function showNodeDetails(data, showNavigationMenu) {
}
}
export async function checkNodeStatus(showNavigationMenu) {
export async function checkNodeStatus(config, showNavigationMenu) {
try {
const nodeRunning = await isNodeRunning();
const nodeRunning = await isNodeRunning(config);
if (nodeRunning) {
const spinner = createSpinner('Checking node status...').start();
const response = await runCommand('curl http://localhost:8080/api/codex/v1/debug/info');
const response = await runCommand(`curl http://localhost:${config.ports.apiPort}/api/codex/v1/debug/info`);
spinner.success();
const data = JSON.parse(response);

View File

@ -6,10 +6,11 @@ import boxen from 'boxen';
import { ASCII_ART } from './constants/ascii.js';
import { handleCommandLineOperation, parseCommandLineArgs } from './cli/commandParser.js';
import { uploadFile, downloadFile, showLocalFiles } from './handlers/fileHandlers.js';
import { checkCodexInstallation, installCodex, uninstallCodex } from './handlers/installationHandlers.js';
import { installCodex, uninstallCodex } from './handlers/installationHandlers.js';
import { runCodex, checkNodeStatus } from './handlers/nodeHandlers.js';
import { showInfoMessage } from './utils/messages.js';
import { loadConfig } from './services/config.js';
import { showConfigMenu } from './configmenu.js';
async function showNavigationMenu() {
console.log('\n')
@ -78,57 +79,62 @@ export async function main() {
message: 'Select an option:',
choices: [
'1. Download and install Codex',
'2. Run Codex node',
'3. Check node status',
'4. Upload a file',
'5. Download a file',
'6. Show local data',
'7. Uninstall Codex node',
'8. Submit feedback',
'9. Exit'
'2. Edit Codex configuration',
'3. Run Codex node',
'4. Check node status',
'5. Upload a file',
'6. Download a file',
'7. Show local data',
'8. Uninstall Codex node',
'9. Submit feedback',
'10. Exit'
],
pageSize: 9,
pageSize: 10,
loop: true
}
]).catch(() => {
handleExit();
return { choice: '9' };
return;
});
if (choice.startsWith('9')) {
handleExit();
break;
}
switch (choice.split('.')[0]) {
case '1':
await checkCodexInstallation(config, showNavigationMenu);
const installed = await installCodex(config, showNavigationMenu);
if (installed) {
await showConfigMenu(config);
}
break;
case '2':
await showConfigMenu(config);
break;
case '3':
await runCodex(config, showNavigationMenu);
return;
case '3':
await checkNodeStatus(showNavigationMenu);
break;
case '4':
await uploadFile(null, handleCommandLineOperation, showNavigationMenu);
await checkNodeStatus(config, showNavigationMenu);
break;
case '5':
await downloadFile(null, handleCommandLineOperation, showNavigationMenu);
await uploadFile(config, null, handleCommandLineOperation, showNavigationMenu);
break;
case '6':
await showLocalFiles(showNavigationMenu);
await downloadFile(config, null, handleCommandLineOperation, showNavigationMenu);
break;
case '7':
await uninstallCodex(config, showNavigationMenu);
await showLocalFiles(config, showNavigationMenu);
break;
case '8':
await uninstallCodex(config, showNavigationMenu);
break;
case '9':
const { exec } = await import('child_process');
const url = 'https://docs.google.com/forms/d/1U21xp6shfDkJWzJSKHhUjwIE7fsYk94gmLUKAbxUMcw/edit';
const command = process.platform === 'win32' ? `start ${url}` : process.platform === 'darwin' ? `open ${url}` : `xdg-open ${url}`;
exec(command);
console.log(showInfoMessage('Opening feedback form in your browser...'));
break;
case '10':
handleExit();
return;
}
console.log('\n');

View File

@ -1,20 +1,19 @@
import fs from 'fs';
import path from 'path';
import { getAppDataDir } from '../utils/appdata.js';
import { getCodexDataDirDefaultPath, getCodexLogsDefaultPath } from '../utils/appdata.js';
const defaultConfig = {
codexExe: "",
// TODO:
// Save user-selected config options. Use these when starting Codex.
dataDir: "",
logsDir: ""
// storageQuota: 0,
// ports: {
// discPort: 8090,
// listenPort: 8070,
// apiPort: 8080
// }
// User-selected config options:
dataDir: getCodexDataDirDefaultPath(),
logsDir: getCodexLogsDefaultPath(),
storageQuota: 8 * 1024 * 1024 * 1024,
ports: {
discPort: 8090,
listenPort: 8070,
apiPort: 8080
}
};
function getConfigFilename() {

View File

@ -21,9 +21,9 @@ export async function getWalletAddress() {
return currentWallet;
}
export async function isNodeRunning() {
export async function isNodeRunning(config) {
try {
const response = await axios.get('http://localhost:8080/api/codex/v1/debug/info');
const response = await axios.get(`http://localhost:${config.ports.apiPort}/api/codex/v1/debug/info`);
return response.status === 200;
} catch (error) {
return false;
@ -105,12 +105,12 @@ export async function checkDependencies() {
return true;
}
export async function startPeriodicLogging() {
export async function startPeriodicLogging(config) {
const FIFTEEN_MINUTES = 15 * 60 * 1000; // 15 minutes in milliseconds
const logNodeInfo = async () => {
try {
const response = await axios.get('http://localhost:8080/api/codex/v1/debug/info');
const response = await axios.get(`http://localhost:${config.ports.apiPort}/api/codex/v1/debug/info`);
if (response.status === 200) {
await logToSupabase(response.data);
}

View File

@ -19,7 +19,7 @@ export function getCodexDataDirDefaultPath() {
return path.join(appData("codex"), "datadir");
}
export function getCodexLogsPath() {
export function getCodexLogsDefaultPath() {
return ensureExists(path.join(appData("codex"), "logs"));
}

View File

@ -0,0 +1,47 @@
import inquirer from 'inquirer';
function getMetricsMult(valueStr, allowMetricPostfixes) {
if (!allowMetricPostfixes) return 1;
const lower = valueStr.toLowerCase();
if (lower.endsWith("tb") || lower.endsWith("t")) return Math.pow(1024, 4);
if (lower.endsWith("gb") || lower.endsWith("g")) return Math.pow(1024, 3);
if (lower.endsWith("mb") || lower.endsWith("m")) return Math.pow(1024, 2);
if (lower.endsWith("kb") || lower.endsWith("k")) return Math.pow(1024, 1);
return 1;
}
function getNumericValue(valueStr) {
try {
const num = valueStr.match(/\d+/g);
const result = parseInt(num);
if (isNaN(result) || !isFinite(result)) {
throw new Error("Invalid input received.");
}
return result;
} catch (error) {
console.log("Failed to parse input: " + error.message);
throw error;
}
}
async function promptForValueStr(promptMessage) {
const response = await inquirer.prompt([
{
type: 'input',
name: 'valueStr',
message: promptMessage
}]);
return response.valueStr;
}
export async function showNumberSelector(currentValue, promptMessage, allowMetricPostfixes) {
try {
var valueStr = await promptForValueStr(promptMessage);
valueStr = valueStr.replaceAll(" ", "");
const mult = getMetricsMult(valueStr, allowMetricPostfixes);
const value = getNumericValue(valueStr);
return value * mult;
} catch {
return currentValue;
}
}

View File

@ -3,6 +3,7 @@ import inquirer from 'inquirer';
import boxen from 'boxen';
import chalk from 'chalk';
import fs from 'fs';
import { filesystemSync } from 'fs-filesystem';
function showMsg(msg) {
console.log(boxen(chalk.white(msg), {
@ -14,13 +15,73 @@ function showMsg(msg) {
}));
}
function getAvailableRoots() {
const devices = filesystemSync();
var mountPoints = [];
Object.keys(devices).forEach(function(key) {
var val = devices[key];
val.volumes.forEach(function(volume) {
mountPoints.push(volume.mountPoint);
});
});
if (mountPoints.length < 1) {
throw new Error("Failed to detect file system devices.");
}
return mountPoints;
}
function splitPath(str) {
return str.replaceAll("\\", "/").split("/");
}
function dropEmptyParts(parts) {
var result = [];
parts.forEach(function(part) {
if (part.length > 0) {
result.push(part);
}
})
return result;
}
function combine(parts) {
const toJoin = dropEmptyParts(parts);
if (toJoin.length == 1) return toJoin[0];
return path.join(...toJoin);
}
function combineWith(parts, extra) {
const toJoin = dropEmptyParts(parts);
if (toJoin.length == 1) return path.join(toJoin[0], extra);
return path.join(...toJoin, extra);
}
function showCurrent(currentPath) {
const len = currentPath.length;
showMsg(`Current path: [${len}]\n` + path.join(...currentPath));
showMsg(`Current path: [${len}]\n` + combine(currentPath));
if (len < 2) {
showMsg(
'Warning - Known issue:\n' +
'Path selection does not work in root paths on some platforms.\n' +
'Use "Enter path" or "Create new folder" to navigate and create folders\n' +
'if this is the case for you.'
);
}
}
function hasValidRoot(roots, checkPath) {
if (checkPath.length < 1) return false;
var result = false;
roots.forEach(function(root) {
if (root.toLowerCase() == checkPath[0].toLowerCase()) {
console.log("valid root: " + combine(checkPath));
result = true;
}
});
if (!result) console.log("invalid root: " + combine(checkPath));
return result;
}
async function showMain(currentPath) {
@ -34,10 +95,9 @@ async function showMain(currentPath) {
'1. Enter path',
'2. Go up one',
'3. Go down one',
'4. Check path exists',
'5. Create new folder here',
'6. Select this path',
'7. Cancel'
'4. Create new folder here',
'5. Select this path',
'6. Cancel'
],
pageSize: 6,
loop: true
@ -50,41 +110,50 @@ async function showMain(currentPath) {
return choice;
}
export async function selectPath() {
var currentPath = splitPath(process.cwd());
export async function showPathSelector(startingPath, pathMustExist) {
const roots = getAvailableRoots();
var currentPath = splitPath(startingPath);
if (!hasValidRoot(roots, currentPath)) {
currentPath = [roots[0]];
}
while (true) {
const choice = await showMain(currentPath);
var newCurrentPath = currentPath;
switch (choice.split('.')[0]) {
case '1':
currentPath = await enterPath();
newCurrentPath = await enterPath(currentPath, pathMustExist);
break;
case '2':
currentPath = upOne(currentPath);
newCurrentPath = upOne(currentPath);
break;
case '3':
currentPath = await downOne(currentPath);
newCurrentPath = await downOne(currentPath);
break;
case '4':
await checkExists(currentPath);
newCurrentPath = await createSubDir(currentPath, pathMustExist);
break;
case '5':
currentPath = await createSubDir(currentPath);
break;
case '6':
if (!isDir(currentPath)) {
if (pathMustExist && !isDir(combine(currentPath))) {
console.log("Current path does not exist.");
break;
} else {
return currentPath;
return combine(currentPath);
}
case '7':
return "";
case '6':
return combine(currentPath);
}
if (hasValidRoot(roots, newCurrentPath)) {
currentPath = newCurrentPath;
} else {
console.log("Selected path has no valid root.");
}
}
}
async function enterPath() {
async function enterPath(currentPath, pathMustExist) {
const response = await inquirer.prompt([
{
type: 'input',
@ -92,6 +161,11 @@ async function enterPath() {
message: 'Enter Path:'
}]);
const newPath = response.path;
if (pathMustExist && !isDir(newPath)) {
console.log("The entered path does not exist.");
return currentPath;
}
return splitPath(response.path);
}
@ -99,22 +173,28 @@ function upOne(currentPath) {
return currentPath.slice(0, currentPath.length - 1);
}
function isDir(dir) {
return fs.lstatSync(dir).isDirectory();
export function isDir(dir) {
try {
return fs.lstatSync(dir).isDirectory();
} catch {
return false;
}
}
function isSubDir(currentPath, entry) {
const newPath = path.join(...currentPath, entry);
const newPath = combineWith(currentPath, entry);
return isDir(newPath);
}
function getSubDirOptions(currentPath) {
const entries = fs.readdirSync(path.join(...currentPath));
const fullPath = combine(currentPath);
const entries = fs.readdirSync(fullPath);
var result = [];
var counter = 1;
entries.forEach(function(entry) {
if (isSubDir(currentPath, entry)) {
result.push(counter + ". " + entry);
result.push(counter + '. ' + entry);
counter = counter + 1;
}
});
return result;
@ -140,19 +220,11 @@ async function downOne(currentPath) {
return currentPath;
});
const subDir = choice.slice(3);
const subDir = choice.split('. ')[1];
return [...currentPath, subDir];
}
async function checkExists(currentPath) {
if (!isDir(path.join(...currentPath))) {
console.log("Current path does not exist.");
} else{
console.log("Current path exists.");
}
}
async function createSubDir(currentPath) {
async function createSubDir(currentPath, pathMustExist) {
const response = await inquirer.prompt([
{
type: 'input',
@ -163,7 +235,9 @@ async function createSubDir(currentPath) {
const name = response.name;
if (name.length < 1) return;
const fullDir = path.join(...currentPath, name);
fs.mkdirSync(fullDir);
const fullDir = combineWith(currentPath, name);
if (pathMustExist && !isDir(fullDir)) {
fs.mkdirSync(fullDir);
}
return [...currentPath, name];
}