UNPKG

@sap/cds-dk

Version:

Command line client and development toolkit for the SAP Cloud Application Programming Model

205 lines (177 loc) 6.85 kB
const cp = require('node:child_process'); const fsp = require('node:fs/promises'); const os = require('node:os'); const path = require('node:path'); const { bold } = require('../../../util/term'); const ENTRY_REGEX = /\n{0,2}# cds completion start(.+)# cds completion end/is; const SUPPORTED_LINUX_SHELLS = ['bash', 'fish', 'gitbash', 'zsh']; const SUPPORTED_WINDOWS_SHELLS = ['ps']; const SUPPORTED_SHELLS = [...SUPPORTED_LINUX_SHELLS, ...SUPPORTED_WINDOWS_SHELLS].sort(); module.exports = new class CompletionSetup { supportedShells = SUPPORTED_SHELLS async setup(addEntry, options) { const shell = options.shell || (process.env.MSYSTEM?.includes('MINGW64') ? 'gitbash' : process.env.SHELL?.match(/(bash|fish|zsh)/)?.[0]) || (process.env.PSModulePath ? 'ps' : null); switch (shell) { case 'bash': case 'fish': case 'gitbash': case 'zsh': return this.setupLinuxLike(addEntry, shell); case 'ps': return this.setupPs(addEntry); case null: try { const shellType = this.determineShellLinux(); if (!shellType) { throw `Unsupported shell.\nSupported shells are: ${SUPPORTED_SHELLS.join(', ')}.`; } return this.setupLinuxLike(addEntry, shellType); } catch (err) { throw `Error during completion setup: ${err}`; } default: throw `Unsupported shell: ${shell}.\nSupported shells are: ${SUPPORTED_SHELLS.join(', ')}.`; } } getProfilePathWindows() { const supportedShells = ['pwsh.exe', 'powershell.exe']; try { const parentPID = process.ppid; const parentProcessDetails = cp.spawnSync('tasklist', ['/fi', `PID eq ${parentPID}`, '/nh', '/fo', 'csv'], { encoding: 'utf8' }).stdout.trim(); const parentProcess = parentProcessDetails.split(',')[0].replace(/"/g, '').trim(); if (!supportedShells.includes(parentProcess)) { throw `Shell '${parentProcess}' is not supported. Make sure you are using PowerShell.`; } return cp.spawnSync(parentProcess, ['-NoProfile', '-c', 'echo $PROFILE'], { encoding: 'utf8' }).stdout.trim(); } catch (err) { throw `Error during getting Windows shell profile path:\n${err}`; } } determineShellLinux() { try { const parentPID = process.ppid; return cp.spawnSync('ps', ['--pid', parentPID, '-ocomm='], { encoding: 'utf8' }).stdout.trim(); } catch (err) { throw `Error during getting Linux parent shell: ${err}`; } } async createBackup(fileName) { await fsp.copyFile(fileName, fileName + '.cds.bak'); } async setupLinuxLike(addEntry, shell) { let profileFile; switch (shell) { case 'bash': profileFile = os.platform() === 'darwin' ? '.bash_profile' : '.bashrc'; break; case 'gitbash': profileFile = '.bash_profile' break; case 'zsh': profileFile = '.zshrc'; break; case 'fish': profileFile = '.config/fish/config.fish'; break; default: throw `Completion is only supported for ${SUPPORTED_LINUX_SHELLS.join(', ')}.`; } profileFile = path.join(os.homedir(), profileFile); await this.changeProfile(profileFile, shell, addEntry); const relProfilePath = path.relative(os.homedir(), profileFile); console.log(`\nSource your shell using\n${bold(`source ~/${relProfilePath}`)}\nto activate the changes.\n`); } async setupPs(addEntry) { const profilePath = this.getProfilePathWindows(); try { await fsp.access(profilePath); } catch (err) { if (err.code === 'ENOENT') { throw `Profile file ${bold(profilePath)} does not exist.\nMake sure you are using PowerShell. CMD shell is not supported.` } throw err; } await this.changeProfile(profilePath, 'ps', addEntry); console.log(`\nSource your shell using\n${bold('. $PROFILE')}\nto activate the changes.`); } getScript(shell) { switch (shell) { case 'bash': case 'gitbash': return ` CDS_PROFILE=$(cds completion --shell ${shell} --profile 2> /dev/null) || CDS_PROFILE="" if [ -r "$CDS_PROFILE" ]; then . "$CDS_PROFILE" fi ` case 'fish': return ` if type "cds" >/dev/null 2>&1 set -l CDS_PROFILE (cds completion --shell fish --profile 2> /dev/null) if test -e "$CDS_PROFILE" source "$CDS_PROFILE" end end ` case 'ps': return ` try { $CDS_PROFILE = cds completion --shell ps --profile 2> $null if (Test-Path -Path "$CDS_PROFILE" -PathType Leaf) { . "$CDS_PROFILE" } } catch {} ` case 'zsh': return ` # compinit can be removed here if loaded elsewhere in the script autoload -Uz compinit && compinit CDS_PROFILE=$(cds completion --shell zsh --profile 2> /dev/null) || CDS_PROFILE="" if [ -r "$CDS_PROFILE" ]; then . "$CDS_PROFILE" fi ` default: throw `Unsupported shell: ${shell}`; } } async changeProfile(profileFile, shell, addEntry) { let profile = ''; let profileFileExists = true; try { profile = await fsp.readFile(profileFile, 'utf8'); } catch (err) { profileFileExists = false; await fsp.mkdir(path.dirname(profileFile), { recursive: true }); } let changed; if (profile.match(ENTRY_REGEX)) { profile = profile.replace(ENTRY_REGEX, ''); changed = true; } if (addEntry) { profile = profile + ` # cds completion start ${this.getScript(shell)} # cds completion end`; changed = true; } console.log(); if (changed) { const relProfilePath = path.relative(os.homedir(), profileFile); if (profileFileExists) { await this.createBackup(profileFile); console.log(`Created backup ${bold(`~/${relProfilePath}.cds.bak`)}`); } await fsp.writeFile(profileFile, profile); if (addEntry) { console.log(`Added entry to ${bold(`~/${relProfilePath}`)}`); } else { console.log(`Removed entry from ${bold(`~/${relProfilePath}`)}`); } } } }