react-chrome-extension-cli
Version:
The CLI for your next Chrome Extension using React
321 lines (281 loc) • 8.7 kB
JavaScript
;
const path = require('path');
const fs = require('fs-extra');
const chalk = require('chalk');
const spawn = require('cross-spawn');
const commander = require('commander');
const packageFile = require('./package.json');
const { checkAppName, prettifyAppName } = require('./utils/name');
const generateReadme = require('./scripts/readme');
const tryGitInit = require('./scripts/git-init');
let projectName;
const OVERRIDE_PAGES = ['newtab', 'bookmarks', 'history'];
const program = new commander.Command(packageFile.name)
.version(packageFile.version)
.arguments('<project-directory>')
.usage(`${chalk.green('<project-directory>')} [options]`)
.action(name => {
projectName = name;
})
.option(
'--override-page [page-name]',
'override default page like New Tab, Bookmarks, or History page'
)
.option('--devtools', 'add features to Chrome Developer Tools')
.on('--help', () => {
console.log(` Only ${chalk.green('<project-directory>')} is required.`);
})
.parse(process.argv);
// 如果拿不到 项目的名称
if (typeof projectName === 'undefined') {
console.error('Please specify the project directory:');
console.error('请输入初始化目录:');
console.log()
console.log(
` ${chalk.cyan(program.name())} ${chalk.green('<project-directory>')}`
);
console.log();
console.log('For example:');
console.log('比如说:');
console.log(` ${chalk.cyan(program.name())} ${chalk.green('my-extension')}`);
console.log();
console.log(
`Run ${chalk.cyan(`${program.name()} --help`)} to see all options.`
);
process.exit(1);
}
function isOverridePageNameValid(name) {
if (name === true || OVERRIDE_PAGES.includes(name)) {
return true;
}
return false;
}
function logOverridePageError() {
console.error(
`${chalk.red('Invalid page name passed to option:')} ${chalk.cyan(
'--override-page'
)}`
);
console.log();
console.log(
`You can pass page name as ${chalk.cyan('newtab')}, ${chalk.cyan(
'bookmarks'
)} or ${chalk.cyan('history')}.`
);
console.log();
console.log('For example:');
console.log(
` ${chalk.cyan(program.name())} ${chalk.green(
'my-extension'
)} ${chalk.cyan('--override-page')} ${chalk.green('newtab')}`
);
process.exit(1);
}
function logOptionsConflictError() {
console.error(
`${chalk.red(
'You have passed both "--override-page" and "--devtools" options'
)}`
);
console.log(` ${chalk.cyan('Only pass one of the option')}`);
console.log('');
process.exit(1);
}
function createExtension(name, { overridePage, devtools }) {
const root = path.resolve(name);
let overridePageName;
if (overridePage) {
if (isOverridePageNameValid(overridePage)) {
overridePageName = overridePage === true ? 'newtab' : overridePage;
if (devtools) {
logOptionsConflictError();
}
} else {
logOverridePageError();
}
}
checkAppName(name);
fs.ensureDirSync(name);
console.log(`Creating a new Chrome extension in ${chalk.green(root)} using React👏👏👏👏👏`);
console.log();
const appDetails = {
version: '0.1.0',
description: 'My First React Chrome Extension',
};
// 生成 package.json 文件
let appPackage = {
name: name,
...appDetails,
private: true,
scripts: {
watch: 'webpack --mode=development --watch --config config/webpack.config.js',
build: 'webpack --mode=production --config config/webpack.config.js',
}
};
// 写入指定目录的 package.json
fs.writeFileSync(
path.join(root, 'package.json'),
JSON.stringify(appPackage, null, 2)
);
let command = 'npm';
let devDependenciesArgs = [
'install', '--save-dev'
];
// 增加依依赖
devDependenciesArgs.push(
'webpack',
'webpack-cli',
'webpack-merge',
'copy-webpack-plugin',
'size-plugin',
'mini-css-extract-plugin',
'css-loader',
'file-loader',
'@babel/core@7.7.7',
'@babel/preset-env@7.7.7',
'@babel/preset-react@7.7.4',
'babel-loader@8.0.6',
);
console.log('Installing packages. This might take a couple of minutes.');
console.log('正在安装依赖,请稍微等一下哈~');
console.log(
`安装 ${chalk.cyan('webpack')}, ${chalk.cyan(
'webpack-cli'
)} 还有其他的东西`
);
console.log();
// 开始安装 devDependencies
// spawn.sync 执行命令 第三个参数指定命令执行的目录
const devDependenciesOk = spawn.sync(command, devDependenciesArgs, { cwd: root, stdio: 'inherit' });
// 如果返回值不为 0, 则报错
if (devDependenciesOk.status !== 0) {
console.error(`\`${command} ${devDependenciesArgs.join(' ')}\` failed`);
return;
}
let dependenciesArgs = [
'install', '--save'
];
// 增加依依赖
dependenciesArgs.push(
'antd@3.26.5',
'axios@0.19.0',
'react@16.12.0',
'react-dom@16.12.0',
);
// 开始安装 dependencies
const dependencies = spawn.sync(command, dependenciesArgs, { cwd: root, stdio: 'inherit' });
// 如果返回值不为 0, 则报错
if (dependencies.status !== 0) {
console.error(`\`${command} ${dependenciesArgs.join(' ')}\` failed`);
return;
}
// Copy template files to project directory
let templateName;
if (overridePageName) {
templateName = 'override-page';
} else if (devtools) {
templateName = 'devtools';
} else {
templateName = 'popup';
}
// 把本 npm 包的目录下的 template 拷贝到对应的目录
fs.copySync(path.resolve(__dirname, 'templates', templateName), root);
// 把本 npm 包的目录下的 config 拷贝到对应的目录
fs.copySync(path.resolve(__dirname, 'config'), path.join(root, 'config'));
// Rename gitignore after the fact to prevent npm from renaming it to .npmignore
// See: https://github.com/npm/npm/issues/1862
// Source: https://github.com/facebook/create-react-app/blob/47e9e2c7a07bfe60b52011cf71de5ca33bdeb6e3/packages/react-scripts/scripts/init.js#L138
fs.moveSync(
path.join(root, 'gitignore'),
path.join(root, '.gitignore'),
[]
);
// Setup the manifest file
const manifestDetails = {
name: prettifyAppName(name),
...appDetails,
};
let appManifest = {
manifest_version: 2,
...manifestDetails,
icons: {
16: 'icons/logo.png',
32: 'icons/logo.png',
48: 'icons/logo.png',
128: 'icons/logo.png',
},
background: {
scripts: ['background.js'],
persistent: false,
},
};
if (overridePageName) {
appManifest = {
...appManifest,
chrome_url_overrides: {
[overridePageName]: 'index.html',
},
};
} else if (devtools) {
appManifest = {
...appManifest,
devtools_page: 'devtools.html',
};
} else {
appManifest = {
...appManifest,
browser_action: {
default_title: manifestDetails.name,
default_popup: 'popup.html',
},
permissions: ['storage'],
content_scripts: [
{
matches: ['<all_urls>'],
run_at: 'document_idle',
js: ['contentScript.js'],
},
],
};
}
// 创建 manifest.json 文件, chrome extension 所需要的
fs.writeFileSync(
path.join(root, 'public', 'manifest.json'),
JSON.stringify(appManifest, null, 2)
);
// 生成 readme 文件
if (generateReadme(manifestDetails, root)) {
console.log('Generated a README file.');
console.log();
}
// 初始化 git 仓库
if (tryGitInit(root, name)) {
console.log('Initialized a git repository.');
console.log();
}
console.log(`Success! Created ${name} at ${root}`);
console.log('Inside that directory, you can run below commands:');
console.log();
console.log(chalk.cyan(` ${command} run watch`));
console.log(' Listens for files changes and rebuilds automatically.');
console.log();
console.log(chalk.cyan(` ${command} run build`));
console.log(' Bundles the app into static files for Chrome store.');
console.log();
console.log('We suggest that you begin by typing:');
console.log();
console.log(` 1. ${chalk.cyan('cd')} ${name}`);
console.log(` 2. Run ${chalk.cyan(`${command} run watch`)}`);
console.log(` 3. Open ${chalk.cyan('chrome://extensions')}`);
console.log(` 4. Check the ${chalk.cyan('Developer mode')} checkbox`);
console.log(
` 5. Click on the ${chalk.cyan('Load unpacked extension')} button`
);
console.log(` 6. Select the folder ${chalk.cyan(name + '/build')}`);
console.log();
}
createExtension(projectName, {
overridePage: program.overridePage,
devtools: program.devtools,
});