@feflow/cli
Version:
A front-end flow tool.
170 lines (156 loc) • 4.94 kB
text/typescript
import os from 'os';
import fs from 'fs';
import path from 'path';
import spawn from 'cross-spawn';
import osenv from 'osenv';
import escapeRegExp from 'lodash/escapeRegExp';
/**
* register the directory to the environment variable path
*/
export default class Binp {
private readonly currentOs: NodeJS.Platform;
constructor() {
this.currentOs = os.platform();
}
register(binPath: string, prior = false, temporary = false) {
if (this.isRegisted(binPath)) {
return;
}
if (temporary) {
let newPath: string;
if (prior) {
newPath = `${binPath}${path.delimiter}${process.env.PATH}`;
} else {
newPath = `${process.env.PATH}${path.delimiter}${binPath}`;
}
process.env.PATH = newPath;
return;
}
if (this.currentOs === 'win32') {
this.registerToWin32(binPath, prior);
} else {
const profile = this.checkTerminal(binPath, prior);
if (profile) {
this.registerToUnixLike(binPath, prior);
this.handleUnsupportedTerminal(profile);
}
}
}
addToPath(file: string, content: string) {
try {
fs.appendFileSync(file, `\n${content}\n`);
} catch (e) {
console.error(e);
console.warn(`registration path to ${file} failed. If the file does not exist, you can try to create it`);
}
}
private isRegisted(binPath: string): boolean {
const pathStr = process.env.PATH;
let pathList: string[] = [];
if (pathStr) {
pathList = pathStr.split(path.delimiter);
}
return pathList.includes(binPath);
}
private registerToWin32(binPath: string, prior: boolean) {
const pathStr = process.env.PATH;
let toPath: string;
if (prior) {
toPath = `${binPath};${pathStr}`;
} else {
toPath = `${pathStr};${binPath}`;
}
spawn.sync('setx', ['path', toPath, '/m'], {
stdio: 'ignore',
windowsHide: true,
});
}
private registerToUnixLike(binPath: string, prior: boolean) {
let toPath: string;
if (prior) {
toPath = `export PATH=${binPath}:$PATH`;
} else {
toPath = `export PATH=$PATH:${binPath}`;
}
const home = osenv.home();
const zshProfile = this.detectZshProfile(home);
this.addToPath(zshProfile, toPath);
const bashProfile = this.detectBashProfile(home);
this.addToPath(bashProfile, toPath);
if (prior) {
toPath = `set path = (${binPath} $path)`;
} else {
toPath = `set path = ($path ${binPath})`;
}
const cshProfile = this.detectCshProfile(home);
this.addToPath(cshProfile, toPath);
}
private checkTerminal(binPath: string, prior: boolean) {
const [profile, setStatement] = this.detectProfile(binPath, prior);
if (!profile || !setStatement) {
console.warn(`unknown terminal, please add ${binPath} to the path`);
return;
}
if (!fs.existsSync(profile)) {
return profile;
}
const content = fs.readFileSync(profile)?.toString() || '';
// 排除包含字符串但被注释的情况
const setStatementRegExp = new RegExp(`(?<!#.*)${escapeRegExp(setStatement)}`);
if (!setStatementRegExp.test(content)) {
return profile;
}
this.handleUnsupportedTerminal(profile);
}
private handleUnsupportedTerminal(profile: string) {
console.error('the current terminal cannot use feflow normally, '
+ 'please open a new terminal or execute the following statement:');
console.error(`source ${profile}`);
process.exit(1);
}
private detectProfile(binPath: string, prior: boolean): [string | undefined, string | undefined] {
const home = osenv.home();
const shell = process.env.SHELL;
let toPath: string;
if (prior) {
toPath = `export PATH=${binPath}:$PATH`;
} else {
toPath = `export PATH=$PATH:${binPath}`;
}
if (!shell) {
return [undefined, undefined];
}
const shellMatch = shell.match(/(zsh|bash|sh|zcsh|csh)/);
if (Array.isArray(shellMatch) && shellMatch.length > 0) {
const shellType = shellMatch[0];
switch (shellType) {
case 'zsh':
return [this.detectZshProfile(home), toPath];
case 'bash':
case 'sh':
return [this.detectBashProfile(home), toPath];
case 'zcsh':
case 'csh':
if (prior) {
toPath = `set path = (${binPath} $path)`;
} else {
toPath = `set path = ($path ${binPath})`;
}
return [this.detectCshProfile(home), toPath];
}
}
return [undefined, undefined];
}
private detectBashProfile(home: string): string {
if (this.currentOs === 'darwin') {
return path.join(home, '.bash_profile');
}
return path.join(home, '.bashrc');
}
private detectCshProfile(home: string): string {
return path.join(home, '.tcshrc');
}
private detectZshProfile(home: string): string {
return path.join(home, '.zshrc');
}
}