UNPKG

mcdev

Version:

Accenture Salesforce Marketing Cloud DevTools

254 lines (238 loc) 10.5 kB
'use strict'; import File from './file.js'; import { confirm, input } from '@inquirer/prompts'; import { Util } from './util.js'; import commandExists from 'command-exists'; import { simpleGit } from 'simple-git'; const git = simpleGit(); /** * CLI helper class */ const Init = { /** * check if git repo exists and otherwise create one * * @returns {Promise.<{status: string, repoName: string}>} success flag */ async initGitRepo() { const result = { status: null, repoName: null }; // check if git is installed (https://www.npmjs.com/package/command-exists) if (!commandExists.sync('git')) { Util.logger.error('Git installation not found.'); Util.logger.error( 'Please follow our tutorial on installing Git: https://github.com/Accenture/sfmc-devtools#212-install-the-git-command-line' ); result.status = 'error'; return result; } // 3. test if in git repo const gitRepoFoundInCWD = await File.pathExists('.git'); let newRepoInitialized = null; if (gitRepoFoundInCWD) { Util.logger.info(`✔️ Git repository found`); newRepoInitialized = false; } else { Util.logger.warn('No Git repository found. Initializing git:'); Util.execSync('git', ['init', '--initial-branch=' + Util.defaultGitBranch]); if (await File.pathExists('.git')) { newRepoInitialized = true; } else { Util.logger.error( 'We detected a problem initializing your Git repository. Please run "git init" manually' ); result.status = 'error'; return result; } } Util.logger.info('Ensuring long file paths are not causing issues with git:'); try { Util.execSync('git', ['config', '--local', 'core.longpaths', 'true']); } catch { Util.logger.warn( `Updating your git config failed. We recommend running the above command manually yourself to avoid issues.` ); } Util.logger.info('Ensuring checkout (git pull) as-is and commit Unix-style line endings:'); try { Util.execSync('git', ['config', '--local', 'core.autocrlf', 'input']); } catch { Util.logger.warn( `Updating your git config failed. We recommend running the above command manually yourself to avoid issues.` ); } // offer to update local user.name and user.email await this._updateGitConfigUser(); if (newRepoInitialized) { // offer to insert git remote url now result.repoName = await this._addGitRemote(); } Util.logger.info('✔️ Git initialization done.'); result.status = newRepoInitialized ? 'init' : 'update'; return result; }, /** * offer to push the new repo straight to the server * * @returns {Promise.<void>} - */ async gitPush() { const skipInteraction = Util.skipInteraction; const gitRemotes = (await git.getRemotes(true)).filter((item) => item.name === 'origin'); if (gitRemotes.length && gitRemotes[0].refs.push) { // check if remote repo is still empty (otherwise to risky to blindly push) let remoteBranchesExist; Util.logger.info('Checking remote Git repository for existing branches...'); try { // First, we need to update our local copy of the repo await git.fetch(); // Then, we can check how many remote branches 'git fetch' has found remoteBranchesExist = (await git.branch(['-r'])).all.length > 0; } catch (ex) { Util.logger.error('Could not contact remote git server: ' + ex.message); } if (remoteBranchesExist === false) { // offer git push if no remote branches found Util.logger.info( `Your remote Git repository is still empty and ready to store your initial backup. Hint: This is the server version of the repo which you share with your team.` ); let gitPush; if (!skipInteraction) { gitPush = await confirm({ message: `Would you like to 'push' your backup to the remote Git repo?`, default: true, }); } if (skipInteraction?.gitPush === 'true' || gitPush) { Util.execSync('git', ['push', '-u', 'origin', 'master']); } } else if (remoteBranchesExist === true) { Util.logger.info( 'Your remote Git repository already contains data. Please execute a git push manually.' ); } } }, /** * offers to add the git remote origin * * @returns {Promise.<string>} repo name (optionally) */ async _addGitRemote() { const skipInteraction = Util.skipInteraction; // #1 ask if the user wants to do it now let gitOriginKnown; if (!skipInteraction) { gitOriginKnown = await confirm({ message: `Do you know the remote/clone URL of your Git repo (starts with ssh:// or http:// and ends on '.git')?`, default: true, }); } if (skipInteraction || gitOriginKnown) { // #2 if yes, guide the user to input the right url let answers = {}; if (skipInteraction) { answers = skipInteraction; } else { answers.gitRemoteUrl = await input({ message: 'Git Remote URL', validate: (value) => { value = value.trim(); if (!value || value.length < 10) { return 'Please enter a valid remote URL'; } else if (!value.startsWith('http') && !value.startsWith('ssh')) { return `Your Git Remote URL should start with 'http' or 'ssh'`; } else if (value.endsWith('.git')) { // all good return true; } else { return `Your Git Remote URL should end with '.git'`; } }, }); } if (typeof answers.gitRemoteUrl === 'string') { answers.gitRemoteUrl = answers.gitRemoteUrl.trim(); Util.execSync('git', ['remote', 'add', 'origin', answers.gitRemoteUrl]); return answers.gitRemoteUrl.split('/').pop().split('.')[0]; } } }, /** * checks global config and ask to config the user info and then store it locally * * @returns {Promise.<void>} - */ async _updateGitConfigUser() { const skipInteraction = Util.skipInteraction; const gitUser = (await this._getGitConfigUser()) || {}; Util.logger.info( `Please confirm your Git user name & email. It should be in the format 'FirstName LastName' and 'your.email@accenture.com'. The current (potentially wrong) values are provided as default. If correct, confirm with ENTER, otherwise please update:` ); let responses; /* eslint-disable unicorn/prefer-ternary */ if (skipInteraction) { responses = { name: gitUser['user.name'], email: gitUser['user.email'], }; } else { responses = { name: await input({ message: 'Git user.name', default: gitUser['user.name'] || null, // eslint-disable-next-line jsdoc/require-jsdoc validate: function (value) { if ( !value || value.trim().length < 4 || value.includes('"') || value.includes("'") ) { return 'Please enter valid name'; } return true; }, }), email: await input({ message: 'Git user.email', default: gitUser['user.email'] || null, // eslint-disable-next-line jsdoc/require-jsdoc validate: function (value) { value = value.trim(); const regex = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; if (!value || !regex.test(String(value).toLowerCase())) { return 'Please enter valid email'; } return true; }, }), }; } /* eslint-enable unicorn/prefer-ternary */ if (responses.name && responses.email) { // name can contain spaces - wrap it in quotes const name = `"${responses.name.trim()}"`; const email = responses.email.trim(); try { Util.execSync('git', ['config', '--local', 'user.name', name]); Util.execSync('git', ['config', '--local', 'user.email', email]); } catch (ex) { // if project folder is not a git folder then using --local will lead to a fatal error Util.logger.warn('- Could not update git user name and email'); Util.logger.debug(ex.message); } } }, /** * retrieves the global user.name and user.email values * * @returns {Promise.<{'user.name': string, 'user.email': string}>} user.name and user.email */ async _getGitConfigUser() { const names = await git.getConfig('user.name'); const emails = await git.getConfig('user.email'); return { 'user.name': names.value || '', 'user.email': emails.value || '' }; }, }; export default Init;