diff --git a/.eslintrc.js b/.eslintrc.js index d804c83..60d1aae 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,7 +1,8 @@ module.exports = { env: { es6: true, - node: true + node: true, + jest: true }, extends: ['google', 'prettier'], plugins: ['prettier'], diff --git a/.gitignore b/.gitignore index 9007a4a..12e0ab0 100644 --- a/.gitignore +++ b/.gitignore @@ -63,3 +63,5 @@ typings/ # OSX specific files .DS_Store + +test-app \ No newline at end of file diff --git a/lib/commands/create.js b/lib/commands/create.js index 41d6f2f..c667b24 100644 --- a/lib/commands/create.js +++ b/lib/commands/create.js @@ -5,6 +5,7 @@ const gitIgnoreParser = require('gitignore-parser'); // internal modules const fs = require('fs'); const path = require('path'); +const spawn = require('child_process').spawn; // helper functions const color = require('../colors.js'); @@ -14,7 +15,9 @@ const { onCancel, selectProject, copyFolderSync, - getSettings + getSettings, + isURL, + logger } = require('../helper.js'); // pm create [projectName] @@ -68,6 +71,34 @@ async function createProject(projectName) { const newProjectDirectoryName = projectName.toLowerCase().replace(/ /g, '-'); const newProjectDirectory = path.join(process.cwd(), newProjectDirectoryName); + if (isURL(selectedTemplate.path)) { + const childProcess = spawn( + 'git', + ['clone', selectedTemplate.path, newProjectDirectory], + { + stdio: 'inherit' + } + ); + + childProcess.on('exit', (code) => { + if (code === 0) { + console.log(''); + logger.success( + // eslint-disable-next-line max-len + `${newProjectDirectoryName} is successfully scaffolded from ${selectedTemplate.path}` + ); + } else { + console.log(''); + logger.error( + // eslint-disable-next-line max-len + `Could not scaffold project. Git clone exitted with 1. Check error above` + ); + } + }); + + return; + } + let gitignoreContent = '.git/\n'; const gitignorePath = path.join(selectedTemplate.path, '.gitignore'); diff --git a/lib/helper.js b/lib/helper.js index c8276aa..ef6d8b5 100644 --- a/lib/helper.js +++ b/lib/helper.js @@ -20,7 +20,12 @@ const DEFAULT_SETTINGS = { }; // Create settings.json if does not exist or just require it if it does exist -const SETTINGS_PATH = path.join(os.homedir(), '.projectman', 'settings.json'); +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 @@ -33,9 +38,8 @@ const getSettings = () => { } catch (err) { if (err.code === 'MODULE_NOT_FOUND') { // Create if doesn't exist - const settingsDir = path.join(os.homedir(), '.projectman'); - if (!fs.existsSync(settingsDir)) { - fs.mkdirSync(settingsDir); + if (!fs.existsSync(SETTINGS_DIR)) { + fs.mkdirSync(SETTINGS_DIR); } fs.writeFileSync( SETTINGS_PATH, @@ -272,6 +276,7 @@ function copyFolderSync(from, to, gitignore, ignoreEmptyDirs = true, basePath) { module.exports = { settings, + logger, getSettings, SETTINGS_PATH, throwCreateIssueError, diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 69222f8..1ed4468 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,11 +1,11 @@ { "name": "projectman", - "version": "2.0.0-alpha.2", + "version": "2.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { - "version": "2.0.0-alpha.2", + "version": "2.0.0", "license": "MIT", "dependencies": { "commander": "^3.0.1", @@ -18,6 +18,7 @@ "projectman": "bin/index.js" }, "devDependencies": { + "cli-testing-tool": "^0.2.0", "eslint": "^7.0.0", "eslint-config-google": "^0.14.0", "eslint-config-prettier": "^6.11.0", @@ -1800,6 +1801,16 @@ "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", "dev": true }, + "node_modules/cli-testing-tool": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/cli-testing-tool/-/cli-testing-tool-0.2.0.tgz", + "integrity": "sha512-9hcOy/pYNivxAJ3uIlaU1WbPEeULx478WqF69ozPt305tl6sywCqqF5F7SzftfDytYr/+PLrYnvBs8ZJglJ0yA==", + "dev": true, + "dependencies": { + "node-ansiparser": "^2.2.0", + "node-ansiterminal": "^0.2.1-beta" + } + }, "node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -5206,6 +5217,18 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "node_modules/node-ansiparser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/node-ansiparser/-/node-ansiparser-2.2.0.tgz", + "integrity": "sha1-tyTT5eP6WoVCVrtjrOsJHnIHk60=", + "dev": true + }, + "node_modules/node-ansiterminal": { + "version": "0.2.1-beta", + "resolved": "https://registry.npmjs.org/node-ansiterminal/-/node-ansiterminal-0.2.1-beta.tgz", + "integrity": "sha1-9baE9/E3XyDMcD2V+zPifPRu8pA=", + "dev": true + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -7790,6 +7813,16 @@ "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", "dev": true }, + "cli-testing-tool": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/cli-testing-tool/-/cli-testing-tool-0.2.0.tgz", + "integrity": "sha512-9hcOy/pYNivxAJ3uIlaU1WbPEeULx478WqF69ozPt305tl6sywCqqF5F7SzftfDytYr/+PLrYnvBs8ZJglJ0yA==", + "dev": true, + "requires": { + "node-ansiparser": "^2.2.0", + "node-ansiterminal": "^0.2.1-beta" + } + }, "cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -10350,6 +10383,18 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "node-ansiparser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/node-ansiparser/-/node-ansiparser-2.2.0.tgz", + "integrity": "sha1-tyTT5eP6WoVCVrtjrOsJHnIHk60=", + "dev": true + }, + "node-ansiterminal": { + "version": "0.2.1-beta", + "resolved": "https://registry.npmjs.org/node-ansiterminal/-/node-ansiterminal-0.2.1-beta.tgz", + "integrity": "sha1-9baE9/E3XyDMcD2V+zPifPRu8pA=", + "dev": true + }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", diff --git a/package.json b/package.json index e4e1e5f..baa967c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "projectman", - "version": "2.0.0", + "version": "2.1.0", "description": "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.", "main": "bin/index.js", "bin": { @@ -38,6 +38,7 @@ "prompts": "^2.3.0" }, "devDependencies": { + "cli-testing-tool": "^0.2.0", "eslint": "^7.0.0", "eslint-config-google": "^0.14.0", "eslint-config-prettier": "^6.11.0", diff --git a/tests/helper.spec.js b/tests/helper.spec.js index e120807..84b49f1 100644 --- a/tests/helper.spec.js +++ b/tests/helper.spec.js @@ -1,6 +1,8 @@ const { isURL } = require('../lib/helper'); +const cleanUp = require('./utils/cleanup'); describe('helper', () => { + afterEach(cleanUp); test('#isURL()', () => { // Links expect(isURL('https://www.google.com')).toBe(true); diff --git a/tests/open.spec.js b/tests/open.spec.js new file mode 100644 index 0000000..fbc8b20 --- /dev/null +++ b/tests/open.spec.js @@ -0,0 +1,73 @@ +const path = require('path'); +const { createCommandInterface, parseOutput } = require('cli-testing-tool'); +const cleanUp = require('./utils/cleanup'); +const { STRING_ESC } = require('cli-testing-tool/lib/cli-ansi-parser'); + +const NO_PROJECT_FOUND_ERROR = + '[BOLD_START][RED_START]>>>[COLOR_END][BOLD_END] No projects to open :('; +const CD_TO_ADD_PROJECT_MSG = + // eslint-disable-next-line max-len + '[BOLD_START][BLUE_START]>>>[COLOR_END][BOLD_END] cd /till/project/directory/ and run [YELLOW_START]pm add[COLOR_END] to add projects and get started'; + +async function addProject(dir) { + const addCommandInterface = createCommandInterface( + `node ${__dirname}/../bin/index.js add`, + { + cwd: dir + } + ); + await addCommandInterface.keys.enter(); // selects default name + return addCommandInterface.getOutput(); +} + +describe('projectman open', () => { + afterEach(() => { + cleanUp(); + }); + + test('should log no error found and pm add instructions', async () => { + const commandInterface = createCommandInterface( + 'node ../bin/index.js open', + { + cwd: __dirname + } + ); + + const terminal = await commandInterface.getOutput(); + expect(terminal.tokenizedOutput).toBe( + NO_PROJECT_FOUND_ERROR + '\n' + CD_TO_ADD_PROJECT_MSG + ); + }); + + // eslint-disable-next-line max-len + test('should show dropdown with tests and utils and with arrow on utils', async () => { + // add tests and utils + await addProject(__dirname); + await addProject(path.join(__dirname, 'utils')); + + const openCommandInterface = createCommandInterface( + 'node ../bin/index.js open', + { + cwd: __dirname + } + ); + await openCommandInterface.keys.arrowDown(); // move down to select utils + const { rawOutput } = await openCommandInterface.getOutput(); + /** + * Why slice from last clear line? + * + * Even though we see one output in terminal, sometimes libraries can create multiple outputs + * slicing from the last clear line just makes sure we only get final output. + */ + const outputAfterLastLineClear = rawOutput.slice( + rawOutput.lastIndexOf(`${STRING_ESC}2K`) + ); + const parsedOutputAfterLastLineClear = parseOutput( + outputAfterLastLineClear + ); + + expect(parsedOutputAfterLastLineClear.stringOutput).toBe( + `? Select project to open:› \ntests \nutils` + ); + }); +}); diff --git a/tests/utils/cleanup.js b/tests/utils/cleanup.js new file mode 100644 index 0000000..f831270 --- /dev/null +++ b/tests/utils/cleanup.js @@ -0,0 +1,9 @@ +const fs = require('fs'); +const path = require('path'); + +const dotProjectMan = path.join(__dirname, '..', 'dotprojectman'); +const cleanUp = () => { + fs.rmSync(dotProjectMan, { recursive: true }); +}; + +module.exports = cleanUp;