UNPKG

projectman

Version:

Hate opening folders? Select and open your projects in your favourite editor straight from your command line without 'CD'ing into the deeply nested folders.

295 lines (263 loc) 7.51 kB
// external dependencies const prompts = require('prompts'); const program = require('commander'); const didYouMean = require('didyoumean'); // internal modules const path = require('path'); const fs = require('fs'); const util = require('util'); const exec = util.promisify(require('child_process').exec); const os = require('os'); // helper functions const color = require('./colors.js'); // Default settings. const DEFAULT_SETTINGS = { commandToOpen: process.platform === 'darwin' ? 'open -a "/Applications/Visual Studio Code.app"' : 'code', projects: [] }; // Create settings.json if does not exist or just require it if it does exist const SETTINGS_DIR = process.env.NODE_ENV === 'test' ? path.join(__dirname, '..', 'tests', 'dotprojectman') : path.join(os.homedir(), '.projectman'); const SETTINGS_PATH = path.join(SETTINGS_DIR, 'settings.json'); /** * Returns settings if already exists, else creates default settings and returns it * @returns {import('../types/utils').SettingsType} */ const getSettings = () => { let settings; try { settings = require(SETTINGS_PATH); } catch (err) { if (err.code === 'MODULE_NOT_FOUND') { // Create if doesn't exist if (!fs.existsSync(SETTINGS_DIR)) { fs.mkdirSync(SETTINGS_DIR); } fs.writeFileSync( SETTINGS_PATH, JSON.stringify(DEFAULT_SETTINGS, null, 4), 'utf8' ); settings = DEFAULT_SETTINGS; } } return settings; }; // const settings = getSettings(); const logger = { error: (message) => console.log(color.boldRed('>>> ') + message), warn: (message) => console.log(color.boldYellow('>>> ') + message), success: (message) => console.log(color.boldGreen('>>> ') + message + ' ✔') }; function throwCreateIssueError(err) { logger.error( // eslint-disable-next-line max-len 'If you think it is my fault please create issue at https://github.com/saurabhdaware/projectman with below log' ); console.log(color.boldRed('Err:')); console.log(err); } // Takes data as input and writes that data to settings.json function writeSettings( data, command = '<command>', successMessage = 'Settings updated :D' ) { fs.writeFile(SETTINGS_PATH, JSON.stringify(data, null, 2), (err) => { if (err) { if (err.code === 'EACCES') { const errCmd = process.platform == 'win32' ? `an admin` : `a super user ${color.boldYellow(`sudo pm ${command}`)}`; logger.error(`Access Denied! please try again as ${errCmd}`); return; } throwCreateIssueError(err); return; } logger.success(successMessage); }); } async function openURL(url) { let stderr; switch (process.platform) { case 'darwin': ({ stderr } = await exec(`open ${url}`)); break; case 'win32': ({ stderr } = await exec(`start ${url}`)); break; default: ({ stderr } = await exec(`xdg-open ${url}`)); break; } if (stderr) { console.log(stderr); } return stderr; } function isURL(str) { // eslint-disable-next-line max-len const regex = /(http|https):\/\/(\w+:{0,1}\w*)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%!\-\/]))?/; if (!regex.test(str)) { return false; } else { return true; } } const suggestFilter = (input, choices) => { return Promise.resolve( choices.filter((choice) => choice.title.toLowerCase().includes(input.toLowerCase()) ) ); }; const suggestCommands = (cmd) => { const suggestion = didYouMean( cmd, program.commands.map((cmd) => cmd._name) ); if (suggestion) { console.log(); console.log(`Did you mean ${suggestion}?`); } }; const onCancel = () => { logger.error("See ya ('__') /"); process.exit(); return false; }; function getChoices(customFilter = () => true) { const projects = [...settings.projects]; const result = projects.filter(customFilter).map(({ ...project }) => { // Spreading project to make it immutable project.title = project.name; project.value = { name: project.name, path: project.path }; if (project.editor && project.editor !== settings.commandToOpen) { project.title += color.grey( ` (${color.boldGrey('editor:')} ${color.grey(project.editor + ')')}` ); project.value.editor = project.editor; } if (isURL(project.path)) { project.title += color.boldGrey(` (URL)`); project.description = project.path; } return project; }); return result; } async function selectProject(projectName, message, customFilter = () => true) { let selectedProject; if (!projectName) { // Ask which project they want to open const questions = [ { type: 'autocomplete', message: `${message}: `, name: 'selectedProject', choices: getChoices(customFilter), limit: 40, suggest: suggestFilter } ]; // Redirecting to stderr in order for it to be used with command substitution ({ selectedProject } = await prompts(questions, { onCancel })); } else { // If project name is mentioned then open directly selectedProject = settings.projects.find( (project) => project.name.toLowerCase() == projectName.toLowerCase() ); } return selectedProject; } /** * Recursively creates the path * @param {String} pathToCreate path that you want to create */ function createPathIfAbsent(pathToCreate) { // prettier-ignore pathToCreate .split(path.sep) .reduce((prevPath, folder) => { const currentPath = path.join(prevPath, folder, path.sep); if (!fs.existsSync(currentPath)) { fs.mkdirSync(currentPath); } return currentPath; }, ''); } // const isDirectoryIgnored = (ignoredPatterns, filePath) => { // const ignoredDir = ignoredPatterns.find((pattern) => // minimatch(filePath, pattern) // ); // if (ignoredDir) { // console.log('>> ignoring ', filePath); // } // return !!ignoredDir; // }; const isGitIgnored = (filePath, basePath, gitignore) => { return gitignore.denies(path.relative(basePath, filePath)); }; /** * * @param {String} from - Path to copy from * @param {String} to - Path to copy to * @param {Array} ignore - files/directories to ignore * @param {Boolean} ignoreEmptyDirs - Ignore empty directories while copying * @return {void} */ function copyFolderSync(from, to, gitignore, ignoreEmptyDirs = true, basePath) { if (isGitIgnored(from, basePath, gitignore)) { return; } const fromDirectories = fs.readdirSync(from); createPathIfAbsent(to); fromDirectories.forEach((element) => { const fromElement = path.join(from, element); const toElement = path.join(to, element); if (fs.lstatSync(fromElement).isFile()) { if (!isGitIgnored(fromElement, basePath, gitignore)) { fs.copyFileSync(fromElement, toElement); } } else { copyFolderSync( fromElement, toElement, gitignore, ignoreEmptyDirs, basePath ); if (fs.existsSync(toElement) && ignoreEmptyDirs) { try { fs.rmdirSync(toElement); } catch (err) { if (err.code !== 'ENOTEMPTY') throw err; } } } }); } // Helper funcitions [END] module.exports = { settings, logger, getSettings, SETTINGS_PATH, throwCreateIssueError, writeSettings, openURL, isURL, suggestCommands, onCancel, getChoices, selectProject, copyFolderSync };