Match-id-2ee2e4235032ec1ddfc09c904eae3c6341d20ee2
This commit is contained in:
parent
9be2395a60
commit
331f8a91c4
|
@ -0,0 +1,40 @@
|
||||||
|
module.exports = {
|
||||||
|
'parser': 'babel-eslint',
|
||||||
|
'env': {
|
||||||
|
'amd': true,
|
||||||
|
'es6': true,
|
||||||
|
'browser': true,
|
||||||
|
'node': false
|
||||||
|
},
|
||||||
|
'parserOptions': {
|
||||||
|
'ecmaVersion': 6,
|
||||||
|
'sourceType': 'module',
|
||||||
|
'ecmaFeatures': {
|
||||||
|
'jsx': true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'ignorePatterns': [
|
||||||
|
"src/template"
|
||||||
|
],
|
||||||
|
'rules': {
|
||||||
|
'indent': [
|
||||||
|
'error',
|
||||||
|
4,
|
||||||
|
{
|
||||||
|
SwitchCase: 1,
|
||||||
|
flatTernaryExpressions: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'no-unused-vars': 'off', // 允许变量声明后未使用
|
||||||
|
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||||
|
"no-underscore-dangle": ["off", "always"], // 允许私有变量 _xxx的变量命名方式
|
||||||
|
'filenames/match-exported': 0,
|
||||||
|
'consistent-return': 0,
|
||||||
|
"comma-dangle": [2, "never"], // 组和对象键值对最后一个逗号, never参数:不能带末尾的逗号, always参数:必须带末尾的逗号
|
||||||
|
'global-require': 0, // 允许require语句不出现在顶层中
|
||||||
|
'no-nested-ternary': 0, // 允许嵌套三元表达式
|
||||||
|
'no-unused-expressions': 0, // 允许使用未执行的表达式。比如fn是一个函数,允许 fn && fn()
|
||||||
|
'no-throw-literal': 0, // 允许throw抛出对象格式
|
||||||
|
'@typescript-eslint/member-ordering': 0 // 禁用TypeScript声明规范
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
node_modules/
|
||||||
|
webpack/
|
||||||
|
public/
|
|
@ -0,0 +1,15 @@
|
||||||
|
export default {
|
||||||
|
printWidth: 120, // 一行120字符数,如果超过会进行换行
|
||||||
|
tabWidth: 2, // tab等2个空格
|
||||||
|
useTabs: false, // 用空格缩进行
|
||||||
|
semi: true, // 行尾使用分号
|
||||||
|
singleQuote: true, // 字符串使用单引号
|
||||||
|
quoteProps: 'as-needed', // 仅在需要时在对象属性添加引号
|
||||||
|
jsxSingleQuote: false, // 在JSX中使用双引号
|
||||||
|
trailingComma: 'es5', // 使用尾逗号(对象、数组等)
|
||||||
|
bracketSpacing: true, // 对象的括号间增加空格
|
||||||
|
jsxBracketSameLine: false, // 将多行JSX元素的>放在最后一行的末尾
|
||||||
|
arrowParens: 'avoid', // 在唯一的arrow函数参数周围省略括号
|
||||||
|
vueIndentScriptAndStyle: false, // 不缩进Vue文件中的<script>和<style>标记内的代码
|
||||||
|
endOfLine: 'lf', // 仅限换行(\n)
|
||||||
|
};
|
|
@ -0,0 +1,5 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import run from '../lib/cli/cli.js';
|
||||||
|
|
||||||
|
run();
|
|
@ -0,0 +1,2 @@
|
||||||
|
declare const _default: (api: any) => void;
|
||||||
|
export default _default;
|
|
@ -0,0 +1,49 @@
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
import webpack from 'webpack';
|
||||||
|
import { build } from 'vite';
|
||||||
|
export default (api) => {
|
||||||
|
api.registerCommand({
|
||||||
|
name: 'build',
|
||||||
|
description: 'build application for production',
|
||||||
|
initialState: api.buildConfig,
|
||||||
|
fn: function (args, state) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
switch (api.compileMode) {
|
||||||
|
case 'webpack':
|
||||||
|
if (state) {
|
||||||
|
api.applyHook({ name: 'beforeCompile', args: state });
|
||||||
|
state.forEach((s) => {
|
||||||
|
webpack(s.config, (err, stats) => {
|
||||||
|
// api.applyHook({ name: 'afterCompile' });
|
||||||
|
if (err || stats.hasErrors()) {
|
||||||
|
api.logger.error(`Build failed.err: ${err}, stats:${stats}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
api.logger.error(`Build failed. Can't find build config.`);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'vite':
|
||||||
|
if (state) {
|
||||||
|
api.applyHook({ name: 'beforeCompile' });
|
||||||
|
build(state);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
api.logger.error(`Build failed. Can't find build config.`);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,3 @@
|
||||||
|
import { API } from '../../../types/types';
|
||||||
|
declare const _default: (api: API) => void;
|
||||||
|
export default _default;
|
|
@ -0,0 +1,72 @@
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
import webpack from 'webpack';
|
||||||
|
import WebpackDevServer from 'webpack-dev-server';
|
||||||
|
import { createServer } from 'vite';
|
||||||
|
import setupProxy from '../../../utils/setupProxy.js';
|
||||||
|
export default (api) => {
|
||||||
|
api.registerCommand({
|
||||||
|
name: 'dev',
|
||||||
|
description: 'build application for development',
|
||||||
|
initialState: api.devBuildConfig,
|
||||||
|
fn: function (args, state) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
api.applyHook({ name: 'beforeDevConfig' });
|
||||||
|
switch (api.compileMode) {
|
||||||
|
case 'webpack':
|
||||||
|
if (state) {
|
||||||
|
api.applyHook({ name: 'beforeDevCompile', config: state });
|
||||||
|
const compiler = webpack(state);
|
||||||
|
const devServerOptions = {
|
||||||
|
client: {
|
||||||
|
overlay: false,
|
||||||
|
},
|
||||||
|
host: 'localhost',
|
||||||
|
port: '8888',
|
||||||
|
open: true,
|
||||||
|
historyApiFallback: true,
|
||||||
|
};
|
||||||
|
if (api.userConfig.devBuildConfig.devProxy) {
|
||||||
|
devServerOptions.onBeforeSetupMiddleware = (devServer) => {
|
||||||
|
setupProxy(devServer.app, api);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
api.applyHook({
|
||||||
|
name: 'beforeStartDevServer',
|
||||||
|
config: { compiler: compiler, devServerOptions: devServerOptions },
|
||||||
|
});
|
||||||
|
const server = new WebpackDevServer(compiler, devServerOptions);
|
||||||
|
server.startCallback((err) => {
|
||||||
|
api.applyHook({ name: 'afterStartDevServer' });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
api.logger.error("Can't find config");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'vite':
|
||||||
|
if (state) {
|
||||||
|
yield createServer(state)
|
||||||
|
.then(server => {
|
||||||
|
return server.listen();
|
||||||
|
})
|
||||||
|
.then(server => {
|
||||||
|
server.printUrls();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
api.logger.error("Can't find config");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,3 @@
|
||||||
|
import { API } from '../../../types/types';
|
||||||
|
declare const _default: (api: API) => void;
|
||||||
|
export default _default;
|
|
@ -0,0 +1,100 @@
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
import inquirer from 'inquirer';
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import { copyFile } from '../../../utils/util.js';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
export default (api) => {
|
||||||
|
api.registerCommand({
|
||||||
|
name: 'generate',
|
||||||
|
description: 'generate template',
|
||||||
|
fn: (args) => __awaiter(void 0, void 0, void 0, function* () {
|
||||||
|
if (args._[0] === 'g') {
|
||||||
|
args._.shift();
|
||||||
|
}
|
||||||
|
if (args._.length === 0) {
|
||||||
|
api.logger.warn("Can't find any generate options.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (args._[0]) {
|
||||||
|
case 'jest':
|
||||||
|
args._.shift();
|
||||||
|
const isESM = api.packageJson['type'] === 'module';
|
||||||
|
yield generateJest(args, api.cwd, isESM);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const generateJest = (args, cwd, isESM) => __awaiter(void 0, void 0, void 0, function* () {
|
||||||
|
let isTs = false;
|
||||||
|
if (args['ts']) {
|
||||||
|
isTs = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const answers = yield inquirer.prompt([
|
||||||
|
{
|
||||||
|
name: 'useTs',
|
||||||
|
message: 'Do you want to use TypeScript',
|
||||||
|
type: 'confirm',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
isTs = answers['useTs'];
|
||||||
|
}
|
||||||
|
if (checkJestConfigExist(cwd)) {
|
||||||
|
console.log('The jest config is exist.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const testRootPath = path.join(cwd, 'test');
|
||||||
|
if (!fs.existsSync(testRootPath)) {
|
||||||
|
fs.mkdirSync(testRootPath);
|
||||||
|
}
|
||||||
|
let templateDir = path.resolve(__dirname, '../../../../template/test');
|
||||||
|
// 如果是TS, 拷贝ts
|
||||||
|
if (isTs) {
|
||||||
|
templateDir = path.join(templateDir, 'ts');
|
||||||
|
copyTestTemplate(cwd, testRootPath, templateDir);
|
||||||
|
}
|
||||||
|
// 拷贝mjs
|
||||||
|
if (!isTs && isESM) {
|
||||||
|
templateDir = path.join(templateDir, 'mjs');
|
||||||
|
copyTestTemplate(cwd, testRootPath, templateDir);
|
||||||
|
}
|
||||||
|
// 拷贝cjs
|
||||||
|
if (!isTs && !isESM) {
|
||||||
|
templateDir = path.join(templateDir, 'cjs');
|
||||||
|
copyTestTemplate(cwd, testRootPath, templateDir);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
function checkJestConfigExist(cwd) {
|
||||||
|
const items = fs.readdirSync(cwd);
|
||||||
|
for (const item of items) {
|
||||||
|
const itemPath = path.resolve(cwd, item);
|
||||||
|
const states = fs.statSync(itemPath);
|
||||||
|
if (states.isFile() && item.startsWith('jest.config')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
function copyTestTemplate(cwd, testRootPath, templateDir) {
|
||||||
|
const items = fs.readdirSync(templateDir);
|
||||||
|
for (const item of items) {
|
||||||
|
const itemPath = path.resolve(templateDir, item);
|
||||||
|
if (item.startsWith('jest.config')) {
|
||||||
|
copyFile(path.join(cwd, item), itemPath);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
copyFile(path.join(testRootPath, item), itemPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
declare const _default: (api: any) => void;
|
||||||
|
export default _default;
|
|
@ -0,0 +1,31 @@
|
||||||
|
import chalk from 'chalk';
|
||||||
|
import lodash from 'lodash';
|
||||||
|
function getDescriptions(commands) {
|
||||||
|
return Object.keys(commands)
|
||||||
|
.filter(name => typeof commands[name] !== 'string')
|
||||||
|
.map(name => {
|
||||||
|
return getDescription(commands[name]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function getDescription(command) {
|
||||||
|
return ` ${chalk.green(lodash.padEnd(command.name, 10))}${command.description || ''}`;
|
||||||
|
}
|
||||||
|
function padLeft(str) {
|
||||||
|
return str
|
||||||
|
.split('\n')
|
||||||
|
.map((line) => ` ${line}`)
|
||||||
|
.join('\n');
|
||||||
|
}
|
||||||
|
export default (api) => {
|
||||||
|
api.registerCommand({
|
||||||
|
name: 'help',
|
||||||
|
description: 'show command helps',
|
||||||
|
fn: (args, config) => {
|
||||||
|
console.log(`
|
||||||
|
Usage: inula-cli <command> [options]
|
||||||
|
|
||||||
|
${getDescriptions(api.commands).join('\n')}
|
||||||
|
`);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,2 @@
|
||||||
|
declare const _default: (api: any) => void;
|
||||||
|
export default _default;
|
|
@ -0,0 +1,24 @@
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
import { createRequire } from 'module';
|
||||||
|
import mockServer from '../../../utils/mockServer.js';
|
||||||
|
const require = createRequire(import.meta.url);
|
||||||
|
export default (api) => {
|
||||||
|
api.registerHook({
|
||||||
|
name: 'beforeStartDevServer',
|
||||||
|
fn: (state) => __awaiter(void 0, void 0, void 0, function* () {
|
||||||
|
const { compiler, devServerOptions } = state;
|
||||||
|
devServerOptions.setupMiddlewares = (middlewares, devServer) => {
|
||||||
|
mockServer(devServer.app);
|
||||||
|
return middlewares;
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,2 @@
|
||||||
|
declare const _default: (api: any) => void;
|
||||||
|
export default _default;
|
|
@ -0,0 +1,53 @@
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
import express from 'express';
|
||||||
|
import { createRequire } from 'module';
|
||||||
|
import { createProxyMiddleware } from 'http-proxy-middleware';
|
||||||
|
const require = createRequire(import.meta.url);
|
||||||
|
export default (api) => {
|
||||||
|
api.registerCommand({
|
||||||
|
name: 'proxy',
|
||||||
|
description: 'remote proxy',
|
||||||
|
initialState: api.userConfig.remoteProxy,
|
||||||
|
fn: function (args, state) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
if (!state) {
|
||||||
|
api.logger.error(`Invalid proxy config!`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const app = express();
|
||||||
|
const proxyConfig = state;
|
||||||
|
const staticList = proxyConfig.localStatic;
|
||||||
|
staticList.forEach(function (value) {
|
||||||
|
app.use(value.url, express.static(value.local));
|
||||||
|
});
|
||||||
|
const remoteProxy = createProxyMiddleware(proxyConfig.fowardingURL, {
|
||||||
|
target: proxyConfig.target,
|
||||||
|
secure: false,
|
||||||
|
autoRewrite: true,
|
||||||
|
protocolRewrite: 'http',
|
||||||
|
ws: true,
|
||||||
|
hostRewrite: '',
|
||||||
|
preserveHeaderKeyCase: true,
|
||||||
|
proxyTimeout: 5 * 60 * 60 * 1000,
|
||||||
|
timeout: 5 * 60 * 60 * 1000,
|
||||||
|
onError: handleProxyError,
|
||||||
|
});
|
||||||
|
function handleProxyError(err) {
|
||||||
|
api.logger.error('Local proxy error. Error is ', err);
|
||||||
|
}
|
||||||
|
app.use(remoteProxy);
|
||||||
|
app.listen(proxyConfig.localPort, () => {
|
||||||
|
api.logger.info(`Start proxy client on http://localhost:${proxyConfig.localPort}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,3 @@
|
||||||
|
import { API } from '../../../types/types';
|
||||||
|
declare const _default: (api: API) => void;
|
||||||
|
export default _default;
|
|
@ -0,0 +1,19 @@
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
import jest from 'jest';
|
||||||
|
export default (api) => {
|
||||||
|
api.registerCommand({
|
||||||
|
name: 'jest',
|
||||||
|
description: 'run jest test',
|
||||||
|
fn: (args, config) => __awaiter(void 0, void 0, void 0, function* () {
|
||||||
|
yield jest.run();
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,3 @@
|
||||||
|
import { API } from '../../../types/types';
|
||||||
|
declare const _default: (api: API) => void;
|
||||||
|
export default _default;
|
|
@ -0,0 +1,21 @@
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
const pkgPath = path.resolve(__dirname, '../../../../package.json');
|
||||||
|
// 读取 package.json 文件
|
||||||
|
const packageJson = fs.readFileSync(pkgPath, 'utf8');
|
||||||
|
// 解析 JSON 格式的数据
|
||||||
|
const packageData = JSON.parse(packageJson);
|
||||||
|
// 获取版本号
|
||||||
|
const version = packageData.version;
|
||||||
|
export default (api) => {
|
||||||
|
api.registerCommand({
|
||||||
|
name: 'version',
|
||||||
|
description: 'show inula-cli version',
|
||||||
|
fn: () => {
|
||||||
|
api.logger.info(`Inula-cli version is ${version}.`);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1 @@
|
||||||
|
export default function run(): Promise<void>;
|
|
@ -0,0 +1,74 @@
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
import chalk from 'chalk';
|
||||||
|
import yargsParser from 'yargs-parser';
|
||||||
|
import Hub from '../core/Hub.js';
|
||||||
|
import initializeEnv from '../utils/initializeEnv.js';
|
||||||
|
import { Logger, LogLevel } from '../utils/logger.js';
|
||||||
|
export default function run() {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
const args = yargsParser(process.argv.slice(2));
|
||||||
|
const alias = {
|
||||||
|
h: 'help',
|
||||||
|
v: 'version',
|
||||||
|
g: 'generate',
|
||||||
|
};
|
||||||
|
let command = args._[0];
|
||||||
|
if (!command) {
|
||||||
|
if (args['v'] || args['version']) {
|
||||||
|
command = 'v';
|
||||||
|
}
|
||||||
|
if (args['h'] || args['help']) {
|
||||||
|
command = 'h';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const aliasCommand = alias[command];
|
||||||
|
if (aliasCommand) {
|
||||||
|
command = aliasCommand;
|
||||||
|
}
|
||||||
|
initializeEnv();
|
||||||
|
if (command === 'version' || command === 'help') {
|
||||||
|
process.env.INNER_COMMAND = "true";
|
||||||
|
}
|
||||||
|
switch (command) {
|
||||||
|
case 'build':
|
||||||
|
process.env.NODE_ENV = 'production';
|
||||||
|
break;
|
||||||
|
case 'dev':
|
||||||
|
process.env.NODE_ENV = 'development';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
process.env.NODE_ENV = 'development';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let enableDebug = false;
|
||||||
|
if (process.env.DEBUG === "true") {
|
||||||
|
enableDebug = true;
|
||||||
|
}
|
||||||
|
const logger = new Logger(enableDebug ? LogLevel.DEBUG : LogLevel.INFO);
|
||||||
|
try {
|
||||||
|
new Hub({
|
||||||
|
logger: logger,
|
||||||
|
}).run({
|
||||||
|
command,
|
||||||
|
args,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
if (err instanceof Error) {
|
||||||
|
logger.error(chalk.red(err.message));
|
||||||
|
if (err.stack) {
|
||||||
|
logger.error(err.stack);
|
||||||
|
}
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { Logger } from '../utils/logger.js';
|
||||||
|
import { UserConfig } from '../types/types.js';
|
||||||
|
interface ConfigOpts {
|
||||||
|
cwd: string;
|
||||||
|
isLocal?: boolean;
|
||||||
|
logger: Logger;
|
||||||
|
}
|
||||||
|
export default class Config {
|
||||||
|
cwd: string;
|
||||||
|
isLocal: boolean;
|
||||||
|
configFile?: string | null;
|
||||||
|
logger: Logger;
|
||||||
|
constructor(opts: ConfigOpts);
|
||||||
|
getUserConfig(): Promise<UserConfig>;
|
||||||
|
getConfigFile(): string | null;
|
||||||
|
addModePath(file: string, mode: string): string;
|
||||||
|
requireConfigs(configFiles: string[]): Promise<UserConfig[]>;
|
||||||
|
mergeConfig(...configs: UserConfig[]): UserConfig;
|
||||||
|
}
|
||||||
|
export {};
|
|
@ -0,0 +1,96 @@
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
import { existsSync } from 'fs';
|
||||||
|
import { extname, join } from 'path';
|
||||||
|
import { parseRequireDeps, cleanRequireCache } from '../utils/util.js';
|
||||||
|
import deepmerge from 'deepmerge';
|
||||||
|
import { loadModule } from '../utils/loadModule.js';
|
||||||
|
const DEFAULT_CONFIG_FILES = ['.inula.ts', '.inula.js'];
|
||||||
|
export default class Config {
|
||||||
|
constructor(opts) {
|
||||||
|
this.cwd = opts.cwd || process.cwd();
|
||||||
|
this.isLocal = opts.isLocal || process.env.NODE_ENV === 'development';
|
||||||
|
this.logger = opts.logger;
|
||||||
|
}
|
||||||
|
getUserConfig() {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
const configFile = this.getConfigFile();
|
||||||
|
if (configFile === null) {
|
||||||
|
this.logger.warn(`Can't find .inula.ts or .inula.js in ${this.cwd}`);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
this.configFile = configFile;
|
||||||
|
if (configFile) {
|
||||||
|
let envConfigFile = undefined;
|
||||||
|
if (process.env.RUNNING_MODE) {
|
||||||
|
envConfigFile = this.addModePath(configFile, process.env.RUNNING_MODE);
|
||||||
|
}
|
||||||
|
// 配置文件的来源
|
||||||
|
// 1、默认的configFile 如.inula.ts
|
||||||
|
// 2、带环境变量的configFile 如.inula.cloud.ts
|
||||||
|
// 3、dev模式 包含local 如.inula.local.ts
|
||||||
|
const files = [configFile];
|
||||||
|
if (envConfigFile && existsSync(envConfigFile)) {
|
||||||
|
files.push(envConfigFile);
|
||||||
|
}
|
||||||
|
if (this.isLocal) {
|
||||||
|
const localConfigFile = this.addModePath(configFile, 'local');
|
||||||
|
if (existsSync(localConfigFile)) {
|
||||||
|
files.push(localConfigFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.logger.debug(`Find user config files ${files}`);
|
||||||
|
// 依次加载配置文件中的依赖并刷新require中的缓存
|
||||||
|
const requireDeps = files.reduce((deps, file) => {
|
||||||
|
deps = deps.concat(parseRequireDeps(file));
|
||||||
|
return deps;
|
||||||
|
}, []);
|
||||||
|
requireDeps.forEach(cleanRequireCache);
|
||||||
|
const configs = yield this.requireConfigs(files);
|
||||||
|
return this.mergeConfig(...configs);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
getConfigFile() {
|
||||||
|
const configFileList = DEFAULT_CONFIG_FILES.map(f => join(this.cwd, f));
|
||||||
|
for (let configFile of configFileList) {
|
||||||
|
if (existsSync(configFile)) {
|
||||||
|
return configFile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
addModePath(file, mode) {
|
||||||
|
const ext = extname(file);
|
||||||
|
return file.replace(new RegExp(`${ext}$`), `.${mode}${ext}`);
|
||||||
|
}
|
||||||
|
requireConfigs(configFiles) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
const configs = [];
|
||||||
|
for (const file in configFiles) {
|
||||||
|
const content = yield loadModule(configFiles[file]);
|
||||||
|
if (content) {
|
||||||
|
configs.push(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return configs;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
mergeConfig(...configs) {
|
||||||
|
let ret = {};
|
||||||
|
for (const config of configs) {
|
||||||
|
ret = deepmerge(ret, config);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
import Config from '../config/Config.js';
|
||||||
|
import { BuildConfig, DevProxy, UserConfig } from '../types/types.js';
|
||||||
|
import { ServiceStage } from '../enum/enum.js';
|
||||||
|
import Plugin from '../plugin/Plugin.js';
|
||||||
|
import { Logger } from '../utils/logger.js';
|
||||||
|
import yargsParser from 'yargs-parser';
|
||||||
|
import { PackageJSON } from 'resolve';
|
||||||
|
interface HubOpts {
|
||||||
|
cwd?: string;
|
||||||
|
logger?: Logger;
|
||||||
|
}
|
||||||
|
export default class Hub {
|
||||||
|
args: any;
|
||||||
|
cwd: string;
|
||||||
|
env: string | undefined;
|
||||||
|
configManager: Config;
|
||||||
|
userConfig: UserConfig;
|
||||||
|
packageJson: PackageJSON;
|
||||||
|
stage: ServiceStage;
|
||||||
|
buildConfig: {
|
||||||
|
name: string;
|
||||||
|
config: object;
|
||||||
|
}[];
|
||||||
|
pluginManager: Plugin;
|
||||||
|
buildConfigPath: BuildConfig[];
|
||||||
|
devBuildConfig: object;
|
||||||
|
compileMode: string;
|
||||||
|
builtInPlugins: string[];
|
||||||
|
pluginPaths: string[];
|
||||||
|
devProxy: DevProxy | null;
|
||||||
|
logger: Logger;
|
||||||
|
[key: string]: any;
|
||||||
|
constructor(opts: HubOpts);
|
||||||
|
setStage(stage: ServiceStage): void;
|
||||||
|
init(): Promise<void>;
|
||||||
|
getBuiltInPlugins(): string[];
|
||||||
|
run({ command, args }: {
|
||||||
|
command: string | number;
|
||||||
|
args: yargsParser.Arguments;
|
||||||
|
}): Promise<void>;
|
||||||
|
runCommand({ command, args }: {
|
||||||
|
command: string | number;
|
||||||
|
args: yargsParser.Arguments;
|
||||||
|
}): Promise<void>;
|
||||||
|
setCompileMode(): void;
|
||||||
|
analyzeBuildConfig(): Promise<void>;
|
||||||
|
getConfigName(name: string): string;
|
||||||
|
}
|
||||||
|
export {};
|
|
@ -0,0 +1,188 @@
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
import { join, isAbsolute } from 'path';
|
||||||
|
import Config from '../config/Config.js';
|
||||||
|
import { ServiceStage } from '../enum/enum.js';
|
||||||
|
import Plugin from '../plugin/Plugin.js';
|
||||||
|
import { existsSync } from 'fs';
|
||||||
|
import { createRequire } from 'module';
|
||||||
|
import { Logger, LogLevel } from '../utils/logger.js';
|
||||||
|
import { loadModule } from '../utils/loadModule.js';
|
||||||
|
import readDirectory from '../utils/readDirectory.js';
|
||||||
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import { loadPkg } from '../utils/loadPkg.js';
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
const require = createRequire(import.meta.url);
|
||||||
|
export default class Hub {
|
||||||
|
constructor(opts) {
|
||||||
|
this.userConfig = {};
|
||||||
|
this.stage = ServiceStage.uninitialized;
|
||||||
|
this.buildConfig = [];
|
||||||
|
this.buildConfigPath = [];
|
||||||
|
this.devBuildConfig = {};
|
||||||
|
this.compileMode = '';
|
||||||
|
this.builtInPlugins = [];
|
||||||
|
this.pluginPaths = [];
|
||||||
|
this.devProxy = null;
|
||||||
|
this.setStage(ServiceStage.constructor);
|
||||||
|
this.cwd = opts.cwd || process.cwd();
|
||||||
|
this.env = process.env.NODE_ENV;
|
||||||
|
if (!opts.logger) {
|
||||||
|
this.logger = new Logger(LogLevel.INFO);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.logger = opts.logger;
|
||||||
|
}
|
||||||
|
this.packageJson = loadPkg(path.join(this.cwd, './package.json'));
|
||||||
|
this.configManager = new Config({
|
||||||
|
cwd: this.cwd,
|
||||||
|
isLocal: this.env === 'development',
|
||||||
|
logger: this.logger,
|
||||||
|
});
|
||||||
|
this.pluginManager = new Plugin({
|
||||||
|
cwd: this.cwd,
|
||||||
|
hub: this,
|
||||||
|
logger: this.logger,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setStage(stage) {
|
||||||
|
this.stage = stage;
|
||||||
|
}
|
||||||
|
init() {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
this.setStage(ServiceStage.init);
|
||||||
|
// 获取用户配置
|
||||||
|
this.userConfig = yield this.configManager.getUserConfig();
|
||||||
|
// 设置编译模式
|
||||||
|
this.setCompileMode();
|
||||||
|
// 获取编译配置
|
||||||
|
yield this.analyzeBuildConfig();
|
||||||
|
this.setStage(ServiceStage.initPlugins);
|
||||||
|
this.builtInPlugins = this.getBuiltInPlugins();
|
||||||
|
yield this.pluginManager.register(this.builtInPlugins, this.userConfig.plugins);
|
||||||
|
this.setStage(ServiceStage.initHooks);
|
||||||
|
this.pluginManager.initHook();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
getBuiltInPlugins() {
|
||||||
|
return readDirectory(path.resolve(__dirname, '../builtInPlugins'));
|
||||||
|
}
|
||||||
|
run({ command, args }) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
args._ = args._ || [];
|
||||||
|
if (args._[0] === command) {
|
||||||
|
args._.shift();
|
||||||
|
}
|
||||||
|
this.args = args;
|
||||||
|
yield this.init();
|
||||||
|
this.setStage(ServiceStage.run);
|
||||||
|
return this.runCommand({ command, args });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
runCommand({ command, args }) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
this.logger.debug(`run command ${command}`);
|
||||||
|
const commands = typeof this.pluginManager.commands[command] === 'string'
|
||||||
|
? this.pluginManager.commands[this.pluginManager.commands[command]]
|
||||||
|
: this.pluginManager.commands[command];
|
||||||
|
if (commands === undefined) {
|
||||||
|
this.logger.error(`Invalid command ${command}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { fn } = commands;
|
||||||
|
return fn(args, this.pluginManager.store[command]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setCompileMode() {
|
||||||
|
this.compileMode = this.userConfig.compileMode || 'webpack';
|
||||||
|
this.logger.debug(`current compile mode is ${this.compileMode}`);
|
||||||
|
}
|
||||||
|
analyzeBuildConfig() {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
if (this.userConfig.devBuildConfig) {
|
||||||
|
let { name, path, env } = this.userConfig.devBuildConfig;
|
||||||
|
path = isAbsolute(path) ? path : join(process.cwd(), path);
|
||||||
|
if (!existsSync(path)) {
|
||||||
|
this.logger.warn(`Cant't find dev build config. Path is ${path}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.logger.debug(`Find dev build config. Path is ${path}`);
|
||||||
|
let bc = yield loadModule(path);
|
||||||
|
if (bc == undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let finalBc = {};
|
||||||
|
if (typeof bc === 'function') {
|
||||||
|
finalBc = bc(env);
|
||||||
|
this.devBuildConfig = finalBc;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.devBuildConfig = bc;
|
||||||
|
if (this.userConfig.devBuildConfig.devProxy) {
|
||||||
|
this.devProxy = this.userConfig.devBuildConfig.devProxy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!this.userConfig.buildConfig) {
|
||||||
|
switch (this.compileMode) {
|
||||||
|
case 'webpack':
|
||||||
|
this.buildConfigPath.push({ name: 'default', path: './webpack.config.js' });
|
||||||
|
break;
|
||||||
|
case 'vite':
|
||||||
|
this.buildConfigPath.push({ name: 'default', path: './vite.config.js' });
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.logger.warn(`Unknown compile mode ${this.compileMode}`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.userConfig.buildConfig.forEach((userBuildConfig) => {
|
||||||
|
// if (typeof userBuildConfig === 'string') {
|
||||||
|
// const name = this.getConfigName(userBuildConfig);
|
||||||
|
// this.buildConfigPath.push({name, path: userBuildConfig});
|
||||||
|
// }
|
||||||
|
if (typeof userBuildConfig === 'object') {
|
||||||
|
// const name = userBuildConfig.name;
|
||||||
|
// const path = userBuildConfig.path;
|
||||||
|
this.buildConfigPath.push(userBuildConfig);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.buildConfigPath.forEach((config) => __awaiter(this, void 0, void 0, function* () {
|
||||||
|
let { name, path } = config;
|
||||||
|
path = isAbsolute(path) ? path : join(process.cwd(), path);
|
||||||
|
if (!existsSync(path)) {
|
||||||
|
this.logger.warn(`Cant't find build config. Path is ${path}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.logger.debug(`Find build config. Path is ${path}`);
|
||||||
|
let bc = yield loadModule(path);
|
||||||
|
if (bc == undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let finalBc = {};
|
||||||
|
if (typeof bc === 'function') {
|
||||||
|
finalBc = bc(config.env);
|
||||||
|
this.buildConfig.push({ name: name, config: finalBc });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.buildConfig.push({ name: name, config: bc });
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
getConfigName(name) {
|
||||||
|
name = name.replace('webpack.', '');
|
||||||
|
name = name.replace('.js', '');
|
||||||
|
name = name.replace('.ts', '');
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
export declare enum PluginType {
|
||||||
|
preset = "preset",
|
||||||
|
plugin = "plugin"
|
||||||
|
}
|
||||||
|
export declare enum ServiceStage {
|
||||||
|
uninitialized = 0,
|
||||||
|
constructor = 1,
|
||||||
|
init = 2,
|
||||||
|
initPlugins = 3,
|
||||||
|
initHooks = 4,
|
||||||
|
pluginReady = 5,
|
||||||
|
getConfig = 6,
|
||||||
|
run = 7
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
export var PluginType;
|
||||||
|
(function (PluginType) {
|
||||||
|
PluginType["preset"] = "preset";
|
||||||
|
PluginType["plugin"] = "plugin";
|
||||||
|
})(PluginType || (PluginType = {}));
|
||||||
|
export var ServiceStage;
|
||||||
|
(function (ServiceStage) {
|
||||||
|
ServiceStage[ServiceStage["uninitialized"] = 0] = "uninitialized";
|
||||||
|
ServiceStage[ServiceStage["constructor"] = 1] = "constructor";
|
||||||
|
ServiceStage[ServiceStage["init"] = 2] = "init";
|
||||||
|
ServiceStage[ServiceStage["initPlugins"] = 3] = "initPlugins";
|
||||||
|
ServiceStage[ServiceStage["initHooks"] = 4] = "initHooks";
|
||||||
|
ServiceStage[ServiceStage["pluginReady"] = 5] = "pluginReady";
|
||||||
|
ServiceStage[ServiceStage["getConfig"] = 6] = "getConfig";
|
||||||
|
ServiceStage[ServiceStage["run"] = 7] = "run";
|
||||||
|
})(ServiceStage || (ServiceStage = {}));
|
|
@ -0,0 +1,43 @@
|
||||||
|
import PluginAPI, { IOpts } from './PluginAPI.js';
|
||||||
|
import { IHook, ICommand } from '../types/types.js';
|
||||||
|
import Hub from '../core/Hub';
|
||||||
|
import { Logger } from '../utils/logger.js';
|
||||||
|
interface pluginManagerOpts {
|
||||||
|
cwd: string;
|
||||||
|
hub: Hub;
|
||||||
|
logger: Logger;
|
||||||
|
}
|
||||||
|
export interface IPlugin {
|
||||||
|
id: string;
|
||||||
|
key: string;
|
||||||
|
path: string;
|
||||||
|
apply: Function;
|
||||||
|
}
|
||||||
|
export default class Plugin {
|
||||||
|
cwd: string;
|
||||||
|
builtInPlugins: string[];
|
||||||
|
userPlugins: string[];
|
||||||
|
commands: {
|
||||||
|
[name: string]: ICommand | string;
|
||||||
|
};
|
||||||
|
hooksByPluginPath: {
|
||||||
|
[id: string]: IHook[];
|
||||||
|
};
|
||||||
|
hooks: {
|
||||||
|
[key: string]: IHook[];
|
||||||
|
};
|
||||||
|
store: {
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
hub: Hub;
|
||||||
|
logger: Logger;
|
||||||
|
registerFunction: Function[];
|
||||||
|
[key: string]: any;
|
||||||
|
constructor(opts: pluginManagerOpts);
|
||||||
|
getPluginPaths(builtInPlugins: string[], userPlugins: string[] | undefined): string[];
|
||||||
|
setStore(name: string, initialValue: any): void;
|
||||||
|
register(builtInPlugins: string[], userPlugins: string[] | undefined): Promise<void>;
|
||||||
|
createPluginAPI(opts: IOpts): PluginAPI;
|
||||||
|
initHook(): void;
|
||||||
|
}
|
||||||
|
export {};
|
|
@ -0,0 +1,116 @@
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
import resolve from 'resolve';
|
||||||
|
import chalk from 'chalk';
|
||||||
|
import PluginAPI from './PluginAPI.js';
|
||||||
|
import { loadModule } from '../utils/loadModule.js';
|
||||||
|
export default class Plugin {
|
||||||
|
constructor(opts) {
|
||||||
|
this.builtInPlugins = [];
|
||||||
|
this.userPlugins = [];
|
||||||
|
this.commands = {};
|
||||||
|
this.hooksByPluginPath = {};
|
||||||
|
this.hooks = {};
|
||||||
|
this.store = {};
|
||||||
|
this.registerFunction = [];
|
||||||
|
this.cwd = opts.cwd || process.cwd();
|
||||||
|
this.hub = opts.hub;
|
||||||
|
this.logger = opts.logger;
|
||||||
|
}
|
||||||
|
getPluginPaths(builtInPlugins, userPlugins) {
|
||||||
|
const paths = [];
|
||||||
|
paths.push(...builtInPlugins);
|
||||||
|
if (userPlugins) {
|
||||||
|
paths.push(...userPlugins);
|
||||||
|
}
|
||||||
|
// 获取所有插件文件的绝对路径
|
||||||
|
const absPaths = paths.map(path => {
|
||||||
|
return resolve.sync(path, {
|
||||||
|
basedir: this.cwd,
|
||||||
|
extensions: ['.js', '.ts'],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return absPaths;
|
||||||
|
}
|
||||||
|
setStore(name, initialValue) {
|
||||||
|
const store = this.store;
|
||||||
|
if (this.store[name]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
store[name] = initialValue;
|
||||||
|
}
|
||||||
|
register(builtInPlugins, userPlugins) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
const paths = this.getPluginPaths(builtInPlugins, userPlugins);
|
||||||
|
this.hub.pluginPaths = paths;
|
||||||
|
const objs = paths.map(path => {
|
||||||
|
const api = this.createPluginAPI({
|
||||||
|
path,
|
||||||
|
manager: this,
|
||||||
|
logger: this.logger,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
path,
|
||||||
|
api,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
for (const obj of objs) {
|
||||||
|
const module = yield loadModule(obj.path);
|
||||||
|
if (module) {
|
||||||
|
try {
|
||||||
|
module(obj.api);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
if (err instanceof Error) {
|
||||||
|
this.logger.error(chalk.red(err.message));
|
||||||
|
if (err.stack) {
|
||||||
|
this.logger.error(err.stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// todo 给API换个名字
|
||||||
|
createPluginAPI(opts) {
|
||||||
|
const pluginAPI = new PluginAPI(opts);
|
||||||
|
// 为PluginAPI添加代理
|
||||||
|
// 除了PluginAPI自有的方法之外,为开发者提供更丰富的api
|
||||||
|
return new Proxy(pluginAPI, {
|
||||||
|
get: (target, prop) => {
|
||||||
|
if (['userConfig', 'devBuildConfig', 'buildConfig', 'compileMode', 'packageJson', 'cwd'].includes(prop)) {
|
||||||
|
return typeof this.hub[prop] === 'function'
|
||||||
|
? this.hub[prop].bind(this.hub)
|
||||||
|
: this.hub[prop];
|
||||||
|
}
|
||||||
|
if (['setStore', 'logger', 'commands'].includes(prop)) {
|
||||||
|
return typeof this[prop] === 'function'
|
||||||
|
? this[prop].bind(this)
|
||||||
|
: this[prop];
|
||||||
|
}
|
||||||
|
return target[prop];
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
initHook() {
|
||||||
|
Object.keys(this.hooksByPluginPath).forEach(path => {
|
||||||
|
const hooks = this.hooksByPluginPath[path];
|
||||||
|
hooks.forEach(hook => {
|
||||||
|
const { name } = hook;
|
||||||
|
hook.pluginId = path;
|
||||||
|
if (!this.hooks[name]) {
|
||||||
|
this.hooks[name] = [];
|
||||||
|
}
|
||||||
|
this.hooks[name].push(hook);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
import Plugin from './Plugin.js';
|
||||||
|
import { IHook, ICommand } from '../types/types.js';
|
||||||
|
import { Logger } from '../utils/logger.js';
|
||||||
|
export interface IOpts {
|
||||||
|
path: string;
|
||||||
|
manager: Plugin;
|
||||||
|
logger: Logger;
|
||||||
|
}
|
||||||
|
export default class PluginAPI {
|
||||||
|
path: string;
|
||||||
|
manager: Plugin;
|
||||||
|
logger: Logger;
|
||||||
|
[key: string]: any;
|
||||||
|
constructor(opts: IOpts);
|
||||||
|
register(hook: IHook): void;
|
||||||
|
registerCommand(command: ICommand): void;
|
||||||
|
registerHook(hook: IHook): void;
|
||||||
|
registerMethod(fn: Function): void;
|
||||||
|
applyHook(name: string, args?: any): Promise<any>;
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
export default class PluginAPI {
|
||||||
|
constructor(opts) {
|
||||||
|
this.path = opts.path;
|
||||||
|
this.manager = opts.manager;
|
||||||
|
this.logger = opts.logger;
|
||||||
|
}
|
||||||
|
register(hook) {
|
||||||
|
if (!this.manager.hooksByPluginPath[this.path]) {
|
||||||
|
this.manager.hooksByPluginPath[this.path] = [];
|
||||||
|
}
|
||||||
|
this.manager.hooksByPluginPath[this.path].push(hook);
|
||||||
|
}
|
||||||
|
registerCommand(command) {
|
||||||
|
const { name } = command;
|
||||||
|
this.manager.commands[name] = command;
|
||||||
|
if (command.initialState) {
|
||||||
|
this.manager.setStore(name, command.initialState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
registerHook(hook) {
|
||||||
|
this.register(hook);
|
||||||
|
}
|
||||||
|
registerMethod(fn) {
|
||||||
|
this.manager.registerFunction.push(fn);
|
||||||
|
}
|
||||||
|
applyHook(name, args) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
const hooks = this.manager.hooks[name] || [];
|
||||||
|
let config = undefined;
|
||||||
|
for (const hook of hooks) {
|
||||||
|
if (this.manager.store[name]) {
|
||||||
|
config = this.manager.store[name];
|
||||||
|
}
|
||||||
|
if (hook.fn) {
|
||||||
|
yield hook.fn(args, config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.manager.store[name];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,128 @@
|
||||||
|
/// <reference types="node" />
|
||||||
|
import { PackageJSON } from 'resolve';
|
||||||
|
import yargsParser from 'yargs-parser';
|
||||||
|
import { Logger } from '../utils/logger.js';
|
||||||
|
import type * as http from 'http';
|
||||||
|
import type * as express from 'express';
|
||||||
|
interface Request extends express.Request {
|
||||||
|
}
|
||||||
|
interface Response extends express.Response {
|
||||||
|
}
|
||||||
|
export interface IDep {
|
||||||
|
[name: string]: string;
|
||||||
|
}
|
||||||
|
export interface IPackage {
|
||||||
|
name?: string;
|
||||||
|
dependencies?: IDep;
|
||||||
|
devDependencies?: IDep;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
export interface IPlugin {
|
||||||
|
id: string;
|
||||||
|
key: string;
|
||||||
|
path: string;
|
||||||
|
apply: Function;
|
||||||
|
config?: IPluginConfig;
|
||||||
|
isPreset?: boolean;
|
||||||
|
}
|
||||||
|
export interface IPluginConfig {
|
||||||
|
default?: any;
|
||||||
|
onChange?: string | Function;
|
||||||
|
}
|
||||||
|
export interface IHook {
|
||||||
|
name: string;
|
||||||
|
fn?: {
|
||||||
|
(state: any, config: any): void;
|
||||||
|
};
|
||||||
|
pluginId?: string;
|
||||||
|
}
|
||||||
|
export interface ICommand {
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
details?: string;
|
||||||
|
initialState?: any;
|
||||||
|
fn: {
|
||||||
|
(args: yargsParser.Arguments, config: any): void;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export interface IConfig {
|
||||||
|
plugins?: string[];
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
interface applyHookConfig<T = any> {
|
||||||
|
name: string;
|
||||||
|
config?: T;
|
||||||
|
}
|
||||||
|
export interface API {
|
||||||
|
cwd: string;
|
||||||
|
logger: Logger;
|
||||||
|
userConfig: IConfig;
|
||||||
|
buildConfig: any;
|
||||||
|
devBuildConfig: any;
|
||||||
|
compileMode: string;
|
||||||
|
commands: string[];
|
||||||
|
packageJson: PackageJSON;
|
||||||
|
registerCommand: {
|
||||||
|
(command: ICommand): void;
|
||||||
|
};
|
||||||
|
registerHook: {
|
||||||
|
(hook: IHook): void;
|
||||||
|
};
|
||||||
|
registerMethod: {
|
||||||
|
(method: Function): void;
|
||||||
|
};
|
||||||
|
applyHook: {
|
||||||
|
(opts: applyHookConfig): void;
|
||||||
|
};
|
||||||
|
setStore: {
|
||||||
|
(name: string, initialState: any): void;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export interface RemoteProxy {
|
||||||
|
target: string;
|
||||||
|
localPort?: number;
|
||||||
|
localStatic?: StaticFileMatcher[];
|
||||||
|
fowardingURL?: string[];
|
||||||
|
}
|
||||||
|
export interface StaticFileMatcher {
|
||||||
|
url: string;
|
||||||
|
local: string;
|
||||||
|
}
|
||||||
|
export interface UserConfig {
|
||||||
|
mock?: MockConfig;
|
||||||
|
proxy?: RemoteProxy;
|
||||||
|
plugins?: string[];
|
||||||
|
compileMode?: string;
|
||||||
|
buildConfig?: BuildConfig[];
|
||||||
|
devBuildConfig?: DevBuildConfig;
|
||||||
|
}
|
||||||
|
export interface MockConfig {
|
||||||
|
enableMock?: boolean;
|
||||||
|
mockPath?: string;
|
||||||
|
}
|
||||||
|
export interface DevBuildConfig {
|
||||||
|
name: string;
|
||||||
|
path: string;
|
||||||
|
args?: object;
|
||||||
|
env?: object;
|
||||||
|
devProxy?: DevProxy;
|
||||||
|
}
|
||||||
|
export interface DevProxy {
|
||||||
|
target: string;
|
||||||
|
matcher: ((pathname: string, req: Request) => boolean);
|
||||||
|
onProxyRes: (proxyRes: http.IncomingMessage, req: Request, res: Response) => void;
|
||||||
|
}
|
||||||
|
export interface BuildConfig {
|
||||||
|
name: string;
|
||||||
|
path: string;
|
||||||
|
args?: object;
|
||||||
|
env?: object;
|
||||||
|
}
|
||||||
|
export declare type ExportUserConfig = UserConfig | Promise<UserConfig>;
|
||||||
|
export declare function defineConfig(config: ExportUserConfig): ExportUserConfig;
|
||||||
|
export interface Arguments {
|
||||||
|
_: Array<string | number>;
|
||||||
|
'--'?: Array<string | number>;
|
||||||
|
[argName: string]: any;
|
||||||
|
}
|
||||||
|
export {};
|
|
@ -0,0 +1,3 @@
|
||||||
|
export function defineConfig(config) {
|
||||||
|
return config;
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
declare const buildConfig: (fileName: string, format?: 'esm' | 'cjs') => Promise<string>;
|
||||||
|
export default buildConfig;
|
|
@ -0,0 +1,61 @@
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
import path from 'path';
|
||||||
|
import fs from 'fs';
|
||||||
|
import { build as esbuild } from 'esbuild';
|
||||||
|
const buildConfig = (fileName, format = 'esm') => __awaiter(void 0, void 0, void 0, function* () {
|
||||||
|
// 外部依赖不构建参与构建,减少执行时间
|
||||||
|
const pluginExternalDeps = {
|
||||||
|
name: 'plugin-external-deps',
|
||||||
|
setup(build) {
|
||||||
|
build.onResolve({ filter: /.*/ }, args => {
|
||||||
|
const id = args.path;
|
||||||
|
if (id[0] !== '.' && !path.isAbsolute(id)) {
|
||||||
|
return {
|
||||||
|
external: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
// 将文件中的路径改成确定路径,避免执行时调用错误
|
||||||
|
const pluginReplaceImport = {
|
||||||
|
name: 'plugin-replace-import-meta',
|
||||||
|
setup(build) {
|
||||||
|
build.onLoad({ filter: /\.[jt]s$/ }, args => {
|
||||||
|
const contents = fs.readFileSync(args.path, 'utf8');
|
||||||
|
// 替换import路径
|
||||||
|
contents.replace(/\bimport\.meta\.url\b/g, JSON.stringify(`file://${args.path}`));
|
||||||
|
// 替换当前目录路径
|
||||||
|
contents.replace(/\b__dirname\b/g, JSON.stringify(path.dirname(args.path)));
|
||||||
|
// 替换当前文件路径
|
||||||
|
contents.replace(/\b__filename\b/g, JSON.stringify(args.path));
|
||||||
|
return {
|
||||||
|
loader: args.path.endsWith('.ts') ? 'ts' : 'js',
|
||||||
|
contents: contents
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const result = yield esbuild({
|
||||||
|
entryPoints: [fileName],
|
||||||
|
outfile: 'out.js',
|
||||||
|
write: false,
|
||||||
|
platform: 'node',
|
||||||
|
bundle: true,
|
||||||
|
format,
|
||||||
|
metafile: true,
|
||||||
|
plugins: [pluginExternalDeps, pluginReplaceImport],
|
||||||
|
});
|
||||||
|
const { text } = result.outputFiles[0];
|
||||||
|
return text;
|
||||||
|
});
|
||||||
|
export default buildConfig;
|
|
@ -0,0 +1 @@
|
||||||
|
export default function dynamicImport(filePath: string): Promise<any>;
|
|
@ -0,0 +1,16 @@
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
export default function dynamicImport(filePath) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
let importPath = filePath;
|
||||||
|
importPath = 'file:///' + importPath;
|
||||||
|
return yield import(importPath);
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
export default function initializeEnv(): void;
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { join } from 'path';
|
||||||
|
import { readFileSync, existsSync } from 'fs';
|
||||||
|
import { parse } from 'dotenv';
|
||||||
|
export default function initializeEnv() {
|
||||||
|
const envPath = join(process.cwd(), '.env');
|
||||||
|
const localEnvPath = join(process.cwd(), '.local.env');
|
||||||
|
loadEnv(envPath);
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
loadEnv(localEnvPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function loadEnv(envPath) {
|
||||||
|
if (existsSync(envPath)) {
|
||||||
|
const parsed = parse(readFileSync(envPath, 'utf-8')) || {};
|
||||||
|
Object.keys(parsed).forEach(key => {
|
||||||
|
process.env[key] = parsed[key];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
export declare function loadModule<T>(filePath: string): Promise<T | undefined>;
|
|
@ -0,0 +1,59 @@
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
import { join, isAbsolute } from 'path';
|
||||||
|
import fs from 'fs';
|
||||||
|
import buildConfig from './build.js';
|
||||||
|
import { createRequire } from 'module';
|
||||||
|
import dynamicImport from './dynamicImport.js';
|
||||||
|
const require = createRequire(import.meta.url);
|
||||||
|
export function loadModule(filePath) {
|
||||||
|
var _a;
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
filePath = isAbsolute(filePath) ? filePath : join(process.cwd(), filePath);
|
||||||
|
const isTsFile = filePath.endsWith('ts');
|
||||||
|
const isJsFile = filePath.endsWith('js');
|
||||||
|
let content;
|
||||||
|
// js文件,可以直接通过import引用
|
||||||
|
if (isJsFile) {
|
||||||
|
content = (_a = (yield dynamicImport(filePath))) === null || _a === void 0 ? void 0 : _a.default;
|
||||||
|
}
|
||||||
|
// 如果是ts文件,需要先转为js文件,再读取
|
||||||
|
if (isTsFile) {
|
||||||
|
const code = yield buildConfig(filePath, 'esm');
|
||||||
|
content = yield getTypescriptModule(code, filePath);
|
||||||
|
}
|
||||||
|
return content;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function getTypescriptModule(code, filePath, isEsm = true) {
|
||||||
|
var _a, _b;
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
const tempFile = `${filePath}.${isEsm ? 'm' : 'c'}js`;
|
||||||
|
let content = null;
|
||||||
|
// todo 臨時文件管理
|
||||||
|
fs.writeFileSync(tempFile, code);
|
||||||
|
delete require.cache[require.resolve(tempFile)];
|
||||||
|
try {
|
||||||
|
const raw = isEsm ? yield dynamicImport(tempFile) : require(tempFile);
|
||||||
|
content = (_a = raw === null || raw === void 0 ? void 0 : raw.default) !== null && _a !== void 0 ? _a : raw;
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
fs.unlinkSync(tempFile);
|
||||||
|
if (err instanceof Error) {
|
||||||
|
err.message = err.message.replace(tempFile, filePath);
|
||||||
|
err.stack = (_b = err.stack) === null || _b === void 0 ? void 0 : _b.replace(tempFile, filePath);
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
// todo 刪除失敗加日誌
|
||||||
|
fs.unlinkSync(tempFile);
|
||||||
|
return content;
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
import { PackageJSON } from 'resolve';
|
||||||
|
export declare const loadPkg: (path: string) => PackageJSON;
|
|
@ -0,0 +1,6 @@
|
||||||
|
import fs from 'fs';
|
||||||
|
export const loadPkg = (path) => {
|
||||||
|
const packageJson = fs.readFileSync(path, 'utf8');
|
||||||
|
const packageData = JSON.parse(packageJson);
|
||||||
|
return packageData;
|
||||||
|
};
|
|
@ -0,0 +1,14 @@
|
||||||
|
export declare enum LogLevel {
|
||||||
|
DEBUG = 0,
|
||||||
|
INFO = 1,
|
||||||
|
WARN = 2,
|
||||||
|
ERROR = 3
|
||||||
|
}
|
||||||
|
export declare class Logger {
|
||||||
|
private readonly level;
|
||||||
|
constructor(level?: LogLevel);
|
||||||
|
debug(message: string): void;
|
||||||
|
info(message: string): void;
|
||||||
|
warn(message: string): void;
|
||||||
|
error(message: string, error?: Error): void;
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
export var LogLevel;
|
||||||
|
(function (LogLevel) {
|
||||||
|
LogLevel[LogLevel["DEBUG"] = 0] = "DEBUG";
|
||||||
|
LogLevel[LogLevel["INFO"] = 1] = "INFO";
|
||||||
|
LogLevel[LogLevel["WARN"] = 2] = "WARN";
|
||||||
|
LogLevel[LogLevel["ERROR"] = 3] = "ERROR";
|
||||||
|
})(LogLevel || (LogLevel = {}));
|
||||||
|
export class Logger {
|
||||||
|
constructor(level) {
|
||||||
|
if (level !== undefined) {
|
||||||
|
this.level = level;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.level = LogLevel.INFO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
debug(message) {
|
||||||
|
if (this.level <= LogLevel.DEBUG) {
|
||||||
|
console.debug(`[DEBUG] ${message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
info(message) {
|
||||||
|
if (this.level <= LogLevel.INFO) {
|
||||||
|
console.info(`[INFO] ${message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
warn(message) {
|
||||||
|
if (this.level <= LogLevel.WARN) {
|
||||||
|
console.warn(`[WARN] ${message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
error(message, error) {
|
||||||
|
if (this.level <= LogLevel.ERROR) {
|
||||||
|
console.error(`[ERROR] ${message}`, error || '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
declare const _default: (app: {
|
||||||
|
_router: {
|
||||||
|
stack: any[];
|
||||||
|
};
|
||||||
|
}) => void;
|
||||||
|
export default _default;
|
|
@ -0,0 +1,102 @@
|
||||||
|
import chokidar from 'chokidar';
|
||||||
|
import bodyParser from 'body-parser';
|
||||||
|
import { globSync } from 'glob';
|
||||||
|
import { join } from 'path';
|
||||||
|
import { createRequire } from 'module';
|
||||||
|
const require = createRequire(import.meta.url);
|
||||||
|
const mockDir = join(process.cwd(), 'mock');
|
||||||
|
const HTTP_METHODS = ['get', 'post', 'put', 'patch', 'delete', 'options', 'head'];
|
||||||
|
const jsonParser = bodyParser.json();
|
||||||
|
const urlencodedParser = bodyParser.urlencoded({
|
||||||
|
extended: true,
|
||||||
|
});
|
||||||
|
// 读取 mock 文件夹下的 js 文件
|
||||||
|
function getMocksFile() {
|
||||||
|
const mockFiles = globSync('**/*.js', {
|
||||||
|
cwd: mockDir,
|
||||||
|
});
|
||||||
|
let ret = mockFiles.reduce((mocks, mockFile) => {
|
||||||
|
if (!mockFile.startsWith('_')) {
|
||||||
|
mocks = Object.assign(Object.assign({}, mocks), require(join(mockDir, mockFile)));
|
||||||
|
console.log('mockFile', require(join(mockDir, mockFile)));
|
||||||
|
}
|
||||||
|
return mocks;
|
||||||
|
}, {});
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
function generateRoutes(app) {
|
||||||
|
let mockStartIndex = app._router.stack.length, mocks = {};
|
||||||
|
try {
|
||||||
|
mocks = getMocksFile();
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('Generate mock routes error', error);
|
||||||
|
}
|
||||||
|
for (const mockItem in mocks) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(mocks, mockItem)) {
|
||||||
|
try {
|
||||||
|
const trimMockItemArr = mockItem
|
||||||
|
.replace(/(^\s*)|(\s*$)/g, '')
|
||||||
|
.replace(/\s+/g, ' ')
|
||||||
|
.split(' ');
|
||||||
|
const respond = mocks[mockItem];
|
||||||
|
let mockType = 'get', mockUrl;
|
||||||
|
if (trimMockItemArr.length === 1) {
|
||||||
|
mockUrl = trimMockItemArr[0];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
[mockType, mockUrl] = trimMockItemArr;
|
||||||
|
}
|
||||||
|
const mockTypeLowerCase = mockType.toLowerCase();
|
||||||
|
if (!HTTP_METHODS.includes(mockTypeLowerCase)) {
|
||||||
|
throw new Error(`Invalid HTTP request method ${mockType} for path ${mockUrl}`);
|
||||||
|
}
|
||||||
|
app[mockTypeLowerCase](mockUrl, [jsonParser, urlencodedParser], respond instanceof Function
|
||||||
|
? respond
|
||||||
|
: (_req, res) => {
|
||||||
|
res.send(respond);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
mockRoutesLength: app._router.stack.length - mockStartIndex,
|
||||||
|
mockStartIndex: mockStartIndex,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// 清除 mock 文件下的 require 缓存
|
||||||
|
function cleanRequireCache() {
|
||||||
|
Object.keys(require.cache).forEach(key => {
|
||||||
|
if (key.includes(mockDir)) {
|
||||||
|
delete require.cache[require.resolve(key)];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export default (app) => {
|
||||||
|
const mockRoutes = generateRoutes(app);
|
||||||
|
let { mockRoutesLength } = mockRoutes;
|
||||||
|
let { mockStartIndex } = mockRoutes;
|
||||||
|
// 监听 mock 文件夹下文件变化
|
||||||
|
chokidar
|
||||||
|
.watch(mockDir, {
|
||||||
|
ignoreInitial: true,
|
||||||
|
})
|
||||||
|
.on('all', (event, _path) => {
|
||||||
|
if (event === 'change' || event === 'add') {
|
||||||
|
try {
|
||||||
|
// 删除中间件映射
|
||||||
|
app._router.stack.splice(mockStartIndex, mockRoutesLength);
|
||||||
|
cleanRequireCache();
|
||||||
|
const mockRoutes = generateRoutes(app);
|
||||||
|
mockRoutesLength = mockRoutes.mockRoutesLength;
|
||||||
|
mockStartIndex = mockRoutes.mockStartIndex;
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1 @@
|
||||||
|
export default function readDirectory(directoryPath: string): string[];
|
|
@ -0,0 +1,26 @@
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
export default function readDirectory(directoryPath) {
|
||||||
|
const filesArray = [];
|
||||||
|
const traverseDirectory = (directoryPath) => {
|
||||||
|
const files = fs.readdirSync(directoryPath);
|
||||||
|
for (const file of files) {
|
||||||
|
const filePath = path.join(directoryPath, file);
|
||||||
|
if (fs.statSync(filePath).isDirectory()) {
|
||||||
|
// 如果是目录,则递归读取该目录下的所有文件
|
||||||
|
traverseDirectory(filePath);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (filePath.startsWith('.')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 如果是文件,则将其全路径添加到数组中
|
||||||
|
if (filePath.endsWith('.js')) {
|
||||||
|
filesArray.push(filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
traverseDirectory(directoryPath);
|
||||||
|
return filesArray;
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
import { API } from '../types/types';
|
||||||
|
declare const _default: (app: any, api: API) => void;
|
||||||
|
export default _default;
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { createProxyMiddleware } from 'http-proxy-middleware';
|
||||||
|
export default (app, api) => {
|
||||||
|
const { devProxy } = api.userConfig.devBuildConfig;
|
||||||
|
app.use(createProxyMiddleware(devProxy.matcher, {
|
||||||
|
target: devProxy.target,
|
||||||
|
secure: false,
|
||||||
|
changeOrigin: true,
|
||||||
|
ws: false,
|
||||||
|
onProxyRes: devProxy.onProxyRes
|
||||||
|
}));
|
||||||
|
};
|
|
@ -0,0 +1,4 @@
|
||||||
|
export declare function parseRequireDeps(filePath: string): string[];
|
||||||
|
export declare const isWindows: boolean;
|
||||||
|
export declare function cleanRequireCache(cacheKey: string): void;
|
||||||
|
export declare function copyFile(targetPath: string, sourcePath: string): void;
|
|
@ -0,0 +1,52 @@
|
||||||
|
import { dirname } from 'path';
|
||||||
|
import { readFileSync, writeFileSync } from 'fs';
|
||||||
|
import resolve from 'resolve';
|
||||||
|
// @ts-ignore
|
||||||
|
import crequire from 'crequire';
|
||||||
|
import { createRequire } from 'module';
|
||||||
|
const require = createRequire(import.meta.url);
|
||||||
|
function parse(filePath) {
|
||||||
|
const content = readFileSync(filePath, 'utf-8');
|
||||||
|
return crequire(content)
|
||||||
|
.map(o => o.path)
|
||||||
|
.filter(path => path.charAt(0) === '.')
|
||||||
|
.map(path => resolve.sync(path, {
|
||||||
|
basedir: dirname(filePath),
|
||||||
|
extensions: ['.tsx', '.ts', '.jsx', '.js'],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
export function parseRequireDeps(filePath) {
|
||||||
|
const paths = [filePath];
|
||||||
|
const ret = [filePath];
|
||||||
|
while (paths.length) {
|
||||||
|
const extraPaths = parse(paths.shift()).filter(path => !ret.includes(path));
|
||||||
|
if (extraPaths.length) {
|
||||||
|
paths.push(...extraPaths);
|
||||||
|
ret.push(...extraPaths);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
export const isWindows = typeof process !== 'undefined' && process.platform === 'win32';
|
||||||
|
export function cleanRequireCache(cacheKey) {
|
||||||
|
const cachePath = isWindows ? cacheKey.replace(/\//g, '\\') : cacheKey;
|
||||||
|
if (require.cache[cachePath]) {
|
||||||
|
const cacheParent = require.cache[cachePath].parent;
|
||||||
|
let i = (cacheParent === null || cacheParent === void 0 ? void 0 : cacheParent.children.length) || 0;
|
||||||
|
while (i--) {
|
||||||
|
if (cacheParent.children[i].id === cachePath) {
|
||||||
|
cacheParent.children.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete require.cache[cachePath];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export function copyFile(targetPath, sourcePath) {
|
||||||
|
try {
|
||||||
|
const fileContent = readFileSync(sourcePath);
|
||||||
|
writeFileSync(targetPath, fileContent);
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('Copy file failed.', error);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
{
|
||||||
|
"name": "inula-cli",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"inula-cli": "bin/start.js"
|
||||||
|
},
|
||||||
|
"files":[
|
||||||
|
"bin",
|
||||||
|
"lib",
|
||||||
|
"template",
|
||||||
|
"package.json",
|
||||||
|
"tsconfig.json"
|
||||||
|
],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"type": "module",
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/body-parser": "^1.19.2",
|
||||||
|
"@types/chalk": "^2.2.0",
|
||||||
|
"@types/express": "^4.17.17",
|
||||||
|
"@types/inquirer": "^9.0.3",
|
||||||
|
"@types/lodash": "^4.14.194",
|
||||||
|
"@types/node": "^18.16.1",
|
||||||
|
"@types/resolve": "^1.20.2",
|
||||||
|
"@types/webpack": "^5.28.1",
|
||||||
|
"@types/yargs-parser": "^21.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.22.5",
|
||||||
|
"@types/http-proxy-middleware": "^1.0.0",
|
||||||
|
"@types/jest": "^29.5.2",
|
||||||
|
"babel-eslint": "^10.1.0",
|
||||||
|
"chalk": "^4.0.0",
|
||||||
|
"chokidar": "^3.5.3",
|
||||||
|
"crequire": "^1.8.1",
|
||||||
|
"deepmerge": "^4.3.1",
|
||||||
|
"dotenv": "^16.0.3",
|
||||||
|
"esbuild": "^0.18.17",
|
||||||
|
"express": "^4.18.2",
|
||||||
|
"glob": "^10.3.3",
|
||||||
|
"http-proxy-middleware": "^2.0.6",
|
||||||
|
"inquirer": "^9.2.7",
|
||||||
|
"install": "^0.13.0",
|
||||||
|
"jest": "^29.5.0",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"resolve": "^1.22.3",
|
||||||
|
"ts-jest": "^29.1.0",
|
||||||
|
"ts-node": "^10.9.1",
|
||||||
|
"vite": "4.4.2",
|
||||||
|
"webpack": "^5.0.0",
|
||||||
|
"webpack-dev-server": "^4.13.3",
|
||||||
|
"yargs-parser": "^21.1.1"
|
||||||
|
},
|
||||||
|
"types": "lib/types/types.d.ts",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./lib/types/types.d.ts",
|
||||||
|
"import": "./lib/types/types.js",
|
||||||
|
"require": "./lib/types/types.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
import webpack from 'webpack';
|
||||||
|
import { build } from 'vite';
|
||||||
|
|
||||||
|
export default (api: any) => {
|
||||||
|
api.registerCommand({
|
||||||
|
name: 'build',
|
||||||
|
description: 'build application for production',
|
||||||
|
initialState: api.buildConfig,
|
||||||
|
fn: async function (args: any, state: any) {
|
||||||
|
switch (api.compileMode) {
|
||||||
|
case 'webpack':
|
||||||
|
if (state) {
|
||||||
|
api.applyHook({ name: 'beforeCompile', args: state });
|
||||||
|
state.forEach((s: any) => {
|
||||||
|
webpack(s.config, (err: any, stats: any) => {
|
||||||
|
// api.applyHook({ name: 'afterCompile' });
|
||||||
|
if (err || stats.hasErrors()) {
|
||||||
|
api.logger.error(`Build failed.err: ${err}, stats:${stats}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
api.logger.error(`Build failed. Can't find build config.`);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'vite':
|
||||||
|
if (state) {
|
||||||
|
api.applyHook({ name: 'beforeCompile' });
|
||||||
|
build(state);
|
||||||
|
} else {
|
||||||
|
api.logger.error(`Build failed. Can't find build config.`);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,65 @@
|
||||||
|
import webpack from 'webpack';
|
||||||
|
import WebpackDevServer from 'webpack-dev-server';
|
||||||
|
import { createServer } from 'vite';
|
||||||
|
import { API } from '../../../types/types';
|
||||||
|
import setupProxy from '../../../utils/setupProxy.js';
|
||||||
|
|
||||||
|
|
||||||
|
export default (api: API) => {
|
||||||
|
api.registerCommand({
|
||||||
|
name: 'dev',
|
||||||
|
description: 'build application for development',
|
||||||
|
initialState: api.devBuildConfig,
|
||||||
|
fn: async function (args: any, state: any) {
|
||||||
|
api.applyHook({ name: 'beforeDevConfig' });
|
||||||
|
switch (api.compileMode) {
|
||||||
|
case 'webpack':
|
||||||
|
if (state) {
|
||||||
|
api.applyHook({ name: 'beforeDevCompile', config: state });
|
||||||
|
const compiler = webpack(state);
|
||||||
|
|
||||||
|
const devServerOptions: WebpackDevServer.Configuration = {
|
||||||
|
client: {
|
||||||
|
overlay: false,
|
||||||
|
},
|
||||||
|
host: 'localhost',
|
||||||
|
port: '8888',
|
||||||
|
open: true,
|
||||||
|
historyApiFallback: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (api.userConfig.devBuildConfig.devProxy) {
|
||||||
|
devServerOptions.onBeforeSetupMiddleware = (devServer: WebpackDevServer) => {
|
||||||
|
setupProxy(devServer.app, api)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
api.applyHook({
|
||||||
|
name: 'beforeStartDevServer',
|
||||||
|
config: { compiler: compiler, devServerOptions: devServerOptions },
|
||||||
|
});
|
||||||
|
const server = new WebpackDevServer(compiler, devServerOptions);
|
||||||
|
server.startCallback((err: any) => {
|
||||||
|
api.applyHook({ name: 'afterStartDevServer' });
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
api.logger.error("Can't find config");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'vite':
|
||||||
|
if (state) {
|
||||||
|
await createServer(state)
|
||||||
|
.then(server => {
|
||||||
|
return server.listen();
|
||||||
|
})
|
||||||
|
.then(server => {
|
||||||
|
server.printUrls();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
api.logger.error("Can't find config");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,104 @@
|
||||||
|
import { API } from '../../../types/types';
|
||||||
|
import yargsParser from 'yargs-parser';
|
||||||
|
import inquirer from 'inquirer';
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import { copyFile } from '../../../utils/util.js';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
|
export default (api: API) => {
|
||||||
|
api.registerCommand({
|
||||||
|
name: 'generate',
|
||||||
|
description: 'generate template',
|
||||||
|
fn: async (args: yargsParser.Arguments) => {
|
||||||
|
if (args._[0] === 'g') {
|
||||||
|
args._.shift();
|
||||||
|
}
|
||||||
|
if (args._.length === 0) {
|
||||||
|
api.logger.warn("Can't find any generate options.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (args._[0]) {
|
||||||
|
case 'jest':
|
||||||
|
args._.shift();
|
||||||
|
const isESM = api.packageJson['type'] === 'module';
|
||||||
|
await generateJest(args, api.cwd, isESM);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateJest = async (args: yargsParser.Arguments, cwd: string, isESM: boolean) => {
|
||||||
|
let isTs: boolean = false;
|
||||||
|
if (args['ts']) {
|
||||||
|
isTs = true;
|
||||||
|
} else {
|
||||||
|
const answers = await inquirer.prompt([
|
||||||
|
{
|
||||||
|
name: 'useTs',
|
||||||
|
message: 'Do you want to use TypeScript',
|
||||||
|
type: 'confirm',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
isTs = answers['useTs'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (checkJestConfigExist(cwd)) {
|
||||||
|
console.log('The jest config is exist.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const testRootPath = path.join(cwd, 'test');
|
||||||
|
|
||||||
|
if (!fs.existsSync(testRootPath)) {
|
||||||
|
fs.mkdirSync(testRootPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
let templateDir = path.resolve(__dirname, '../../../../template/test');
|
||||||
|
|
||||||
|
// 如果是TS, 拷贝ts
|
||||||
|
if (isTs) {
|
||||||
|
templateDir = path.join(templateDir, 'ts');
|
||||||
|
copyTestTemplate(cwd, testRootPath, templateDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拷贝mjs
|
||||||
|
if (!isTs && isESM) {
|
||||||
|
templateDir = path.join(templateDir, 'mjs');
|
||||||
|
copyTestTemplate(cwd, testRootPath, templateDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拷贝cjs
|
||||||
|
if (!isTs && !isESM) {
|
||||||
|
templateDir = path.join(templateDir, 'cjs');
|
||||||
|
copyTestTemplate(cwd, testRootPath, templateDir);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function checkJestConfigExist(cwd: string): boolean {
|
||||||
|
const items = fs.readdirSync(cwd);
|
||||||
|
for (const item of items) {
|
||||||
|
const itemPath = path.resolve(cwd, item);
|
||||||
|
const states = fs.statSync(itemPath);
|
||||||
|
if (states.isFile() && item.startsWith('jest.config')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyTestTemplate(cwd: string, testRootPath: string, templateDir: string) {
|
||||||
|
const items = fs.readdirSync(templateDir);
|
||||||
|
for (const item of items) {
|
||||||
|
const itemPath = path.resolve(templateDir, item);
|
||||||
|
if (item.startsWith('jest.config')) {
|
||||||
|
copyFile(path.join(cwd, item), itemPath);
|
||||||
|
} else {
|
||||||
|
copyFile(path.join(testRootPath, item), itemPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
import chalk from 'chalk';
|
||||||
|
import lodash from 'lodash';
|
||||||
|
|
||||||
|
function getDescriptions(commands: any) {
|
||||||
|
return Object.keys(commands)
|
||||||
|
.filter(name => typeof commands[name] !== 'string')
|
||||||
|
.map(name => {
|
||||||
|
return getDescription(commands[name]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDescription(command: any) {
|
||||||
|
return ` ${chalk.green(lodash.padEnd(command.name, 10))}${command.description || ''}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function padLeft(str: string) {
|
||||||
|
return str
|
||||||
|
.split('\n')
|
||||||
|
.map((line: string) => ` ${line}`)
|
||||||
|
.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
export default (api: any) => {
|
||||||
|
api.registerCommand({
|
||||||
|
name: 'help',
|
||||||
|
description: 'show command helps',
|
||||||
|
|
||||||
|
fn: (args: any, config: any) => {
|
||||||
|
console.log(`
|
||||||
|
Usage: inula-cli <command> [options]
|
||||||
|
|
||||||
|
${getDescriptions(api.commands).join('\n')}
|
||||||
|
`);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { createRequire } from 'module';
|
||||||
|
import mockServer from '../../../utils/mockServer.js';
|
||||||
|
const require = createRequire(import.meta.url);
|
||||||
|
|
||||||
|
export default (api: any) => {
|
||||||
|
api.registerHook({
|
||||||
|
name: 'beforeStartDevServer',
|
||||||
|
fn: async (state: any) => {
|
||||||
|
const { compiler, devServerOptions } = state;
|
||||||
|
devServerOptions.setupMiddlewares = (middlewares: any, devServer: { app: any }) => {
|
||||||
|
mockServer(devServer.app);
|
||||||
|
return middlewares;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,44 @@
|
||||||
|
import express from 'express';
|
||||||
|
import { createRequire } from 'module';
|
||||||
|
import { createProxyMiddleware } from 'http-proxy-middleware';
|
||||||
|
const require = createRequire(import.meta.url);
|
||||||
|
|
||||||
|
export default (api: any) => {
|
||||||
|
api.registerCommand({
|
||||||
|
name: 'proxy',
|
||||||
|
description: 'remote proxy',
|
||||||
|
initialState: api.userConfig.remoteProxy,
|
||||||
|
fn: async function (args: any, state: any) {
|
||||||
|
if (!state) {
|
||||||
|
api.logger.error(`Invalid proxy config!`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const app = express();
|
||||||
|
const proxyConfig = state;
|
||||||
|
const staticList = proxyConfig.localStatic;
|
||||||
|
staticList.forEach(function (value: { url: any; local: string }) {
|
||||||
|
app.use(value.url, express.static(value.local));
|
||||||
|
});
|
||||||
|
const remoteProxy = createProxyMiddleware(proxyConfig.fowardingURL, {
|
||||||
|
target: proxyConfig.target,
|
||||||
|
secure: false,
|
||||||
|
autoRewrite: true,
|
||||||
|
protocolRewrite: 'http',
|
||||||
|
ws: true,
|
||||||
|
hostRewrite: '',
|
||||||
|
preserveHeaderKeyCase: true,
|
||||||
|
proxyTimeout: 5 * 60 * 60 * 1000,
|
||||||
|
timeout: 5 * 60 * 60 * 1000,
|
||||||
|
onError: handleProxyError,
|
||||||
|
});
|
||||||
|
function handleProxyError(err: any) {
|
||||||
|
api.logger.error('Local proxy error. Error is ', err);
|
||||||
|
}
|
||||||
|
app.use(remoteProxy);
|
||||||
|
|
||||||
|
app.listen(proxyConfig.localPort, () => {
|
||||||
|
api.logger.info(`Start proxy client on http://localhost:${proxyConfig.localPort}`);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { API } from '../../../types/types';
|
||||||
|
import jest from 'jest';
|
||||||
|
import yargsParser from 'yargs-parser';
|
||||||
|
|
||||||
|
export default (api: API) => {
|
||||||
|
api.registerCommand({
|
||||||
|
name: 'jest',
|
||||||
|
description: 'run jest test',
|
||||||
|
fn: async (args: yargsParser.Arguments, config: any) => {
|
||||||
|
await jest.run();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,31 @@
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import { API } from '../../../types/types';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
interface PackageJson {
|
||||||
|
version: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pkgPath = path.resolve(__dirname, '../../../../package.json');
|
||||||
|
|
||||||
|
// 读取 package.json 文件
|
||||||
|
const packageJson = fs.readFileSync(pkgPath, 'utf8');
|
||||||
|
|
||||||
|
// 解析 JSON 格式的数据
|
||||||
|
const packageData: PackageJson = JSON.parse(packageJson);
|
||||||
|
|
||||||
|
// 获取版本号
|
||||||
|
const version = packageData.version;
|
||||||
|
|
||||||
|
export default (api: API) => {
|
||||||
|
api.registerCommand({
|
||||||
|
name: 'version',
|
||||||
|
description: 'show inula-cli version',
|
||||||
|
fn: () => {
|
||||||
|
api.logger.info(`Inula-cli version is ${version}.`);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,73 @@
|
||||||
|
import chalk from 'chalk';
|
||||||
|
import yargsParser from 'yargs-parser';
|
||||||
|
import Hub from '../core/Hub.js';
|
||||||
|
import initializeEnv from '../utils/initializeEnv.js';
|
||||||
|
import { Logger, LogLevel } from '../utils/logger.js';
|
||||||
|
|
||||||
|
export default async function run() {
|
||||||
|
const args: yargsParser.Arguments = yargsParser(process.argv.slice(2));
|
||||||
|
const alias: Record<string, string> = {
|
||||||
|
h: 'help',
|
||||||
|
v: 'version',
|
||||||
|
g: 'generate',
|
||||||
|
};
|
||||||
|
let command: string | number | undefined = args._[0];
|
||||||
|
|
||||||
|
if (!command) {
|
||||||
|
if (args['v'] || args['version']) {
|
||||||
|
command = 'v';
|
||||||
|
}
|
||||||
|
if (args['h'] || args['help']) {
|
||||||
|
command = 'h';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const aliasCommand: string | undefined = alias[command];
|
||||||
|
|
||||||
|
if (aliasCommand) {
|
||||||
|
command = aliasCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeEnv();
|
||||||
|
|
||||||
|
if (command === 'version' || command === 'help') {
|
||||||
|
process.env.INNER_COMMAND = "true"
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (command) {
|
||||||
|
case 'build':
|
||||||
|
process.env.NODE_ENV = 'production';
|
||||||
|
break;
|
||||||
|
case 'dev':
|
||||||
|
process.env.NODE_ENV = 'development';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
process.env.NODE_ENV = 'development';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let enableDebug: boolean = false;
|
||||||
|
|
||||||
|
if (process.env.DEBUG === "true") {
|
||||||
|
enableDebug = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const logger: Logger = new Logger(enableDebug ? LogLevel.DEBUG : LogLevel.INFO);
|
||||||
|
|
||||||
|
try {
|
||||||
|
new Hub({
|
||||||
|
logger: logger,
|
||||||
|
}).run({
|
||||||
|
command,
|
||||||
|
args,
|
||||||
|
});
|
||||||
|
} catch (err: unknown) {
|
||||||
|
if (err instanceof Error) {
|
||||||
|
logger.error(chalk.red(err.message));
|
||||||
|
if (err.stack) {
|
||||||
|
logger.error(err.stack);
|
||||||
|
}
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
import { existsSync } from 'fs';
|
||||||
|
import { extname, join } from 'path';
|
||||||
|
import { parseRequireDeps, cleanRequireCache } from '../utils/util.js';
|
||||||
|
import deepmerge from 'deepmerge';
|
||||||
|
import { loadModule } from '../utils/loadModule.js';
|
||||||
|
import { Logger } from '../utils/logger.js';
|
||||||
|
import { UserConfig } from '../types/types.js';
|
||||||
|
|
||||||
|
interface ConfigOpts {
|
||||||
|
cwd: string;
|
||||||
|
isLocal?: boolean;
|
||||||
|
logger: Logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_CONFIG_FILES = ['.inula.ts', '.inula.js'];
|
||||||
|
|
||||||
|
export default class Config {
|
||||||
|
cwd: string;
|
||||||
|
isLocal: boolean;
|
||||||
|
configFile?: string | null;
|
||||||
|
logger: Logger;
|
||||||
|
|
||||||
|
constructor(opts: ConfigOpts) {
|
||||||
|
this.cwd = opts.cwd || process.cwd();
|
||||||
|
this.isLocal = opts.isLocal || process.env.NODE_ENV === 'development';
|
||||||
|
this.logger = opts.logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getUserConfig(): Promise<UserConfig> {
|
||||||
|
const configFile: string | null = this.getConfigFile();
|
||||||
|
if (configFile === null) {
|
||||||
|
this.logger.debug(`Can't find .inula.ts or .inula.js in ${this.cwd}`);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
this.configFile = configFile;
|
||||||
|
|
||||||
|
if (configFile) {
|
||||||
|
let envConfigFile: string | undefined = undefined;
|
||||||
|
if (process.env.RUNNING_MODE) {
|
||||||
|
envConfigFile = this.addModePath(configFile, process.env.RUNNING_MODE);
|
||||||
|
}
|
||||||
|
// 配置文件的来源
|
||||||
|
// 1、默认的configFile 如.inula.ts
|
||||||
|
// 2、带环境变量的configFile 如.inula.cloud.ts
|
||||||
|
// 3、dev模式 包含local 如.inula.local.ts
|
||||||
|
const files: string[] = [configFile];
|
||||||
|
if (envConfigFile && existsSync(envConfigFile)) {
|
||||||
|
files.push(envConfigFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isLocal) {
|
||||||
|
const localConfigFile = this.addModePath(configFile, 'local');
|
||||||
|
if (existsSync(localConfigFile)) {
|
||||||
|
files.push(localConfigFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.debug(`Find user config files ${files}`);
|
||||||
|
|
||||||
|
// 依次加载配置文件中的依赖并刷新require中的缓存
|
||||||
|
const requireDeps = files.reduce((deps: string[], file) => {
|
||||||
|
deps = deps.concat(parseRequireDeps(file));
|
||||||
|
return deps;
|
||||||
|
}, []);
|
||||||
|
requireDeps.forEach(cleanRequireCache);
|
||||||
|
|
||||||
|
const configs = await this.requireConfigs(files);
|
||||||
|
return this.mergeConfig(...configs);
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getConfigFile(): string | null {
|
||||||
|
const configFileList: string[] = DEFAULT_CONFIG_FILES.map(f => join(this.cwd, f));
|
||||||
|
for (let configFile of configFileList) {
|
||||||
|
if (existsSync(configFile)) {
|
||||||
|
return configFile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
addModePath(file: string, mode: string) {
|
||||||
|
const ext = extname(file);
|
||||||
|
return file.replace(new RegExp(`${ext}$`), `.${mode}${ext}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async requireConfigs(configFiles: string[]) {
|
||||||
|
const configs: UserConfig[] = [];
|
||||||
|
for (const file in configFiles) {
|
||||||
|
const content: UserConfig | undefined = await loadModule<UserConfig>(configFiles[file]);
|
||||||
|
if (content) {
|
||||||
|
configs.push(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return configs;
|
||||||
|
}
|
||||||
|
|
||||||
|
mergeConfig(...configs: UserConfig[]) {
|
||||||
|
let ret: UserConfig = {};
|
||||||
|
for (const config of configs) {
|
||||||
|
ret = deepmerge<UserConfig>(ret, config);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,220 @@
|
||||||
|
import { join, isAbsolute } from 'path';
|
||||||
|
import Config from '../config/Config.js';
|
||||||
|
import { BuildConfig, DevBuildConfig, DevProxy, ICommand, UserConfig } from '../types/types.js';
|
||||||
|
import { ServiceStage } from '../enum/enum.js';
|
||||||
|
import Plugin from '../plugin/Plugin.js';
|
||||||
|
import { appendFile, existsSync } from 'fs';
|
||||||
|
import { createRequire } from 'module';
|
||||||
|
import { Logger, LogLevel } from '../utils/logger.js';
|
||||||
|
import { loadModule } from '../utils/loadModule.js';
|
||||||
|
import readDirectory from '../utils/readDirectory.js';
|
||||||
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import yargsParser from 'yargs-parser';
|
||||||
|
import { PackageJSON } from 'resolve';
|
||||||
|
import { loadPkg } from '../utils/loadPkg.js';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
const require = createRequire(import.meta.url);
|
||||||
|
|
||||||
|
interface HubOpts {
|
||||||
|
cwd?: string;
|
||||||
|
logger?: Logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Hub {
|
||||||
|
args: any;
|
||||||
|
cwd: string;
|
||||||
|
env: string | undefined;
|
||||||
|
configManager: Config;
|
||||||
|
userConfig: UserConfig = {};
|
||||||
|
packageJson: PackageJSON;
|
||||||
|
stage: ServiceStage = ServiceStage.uninitialized;
|
||||||
|
buildConfig: {name:string, config: object}[] = [];
|
||||||
|
pluginManager: Plugin;
|
||||||
|
buildConfigPath: BuildConfig[] = [];
|
||||||
|
devBuildConfig: object = {};
|
||||||
|
compileMode: string = '';
|
||||||
|
builtInPlugins: string[] = [];
|
||||||
|
pluginPaths: string[] = [];
|
||||||
|
devProxy: DevProxy | null = null;
|
||||||
|
logger: Logger;
|
||||||
|
|
||||||
|
[key: string]: any;
|
||||||
|
|
||||||
|
constructor(opts: HubOpts) {
|
||||||
|
this.setStage(ServiceStage.constructor);
|
||||||
|
this.cwd = opts.cwd || process.cwd();
|
||||||
|
this.env = process.env.NODE_ENV;
|
||||||
|
|
||||||
|
if (!opts.logger) {
|
||||||
|
this.logger = new Logger(LogLevel.INFO);
|
||||||
|
} else {
|
||||||
|
this.logger = opts.logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.packageJson = loadPkg(path.join(this.cwd, './package.json'));
|
||||||
|
|
||||||
|
this.configManager = new Config({
|
||||||
|
cwd: this.cwd,
|
||||||
|
isLocal: this.env === 'development',
|
||||||
|
logger: this.logger,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.pluginManager = new Plugin({
|
||||||
|
cwd: this.cwd,
|
||||||
|
hub: this,
|
||||||
|
logger: this.logger,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setStage(stage: ServiceStage) {
|
||||||
|
this.stage = stage;
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
this.setStage(ServiceStage.init);
|
||||||
|
|
||||||
|
// 获取用户配置
|
||||||
|
this.userConfig = await this.configManager.getUserConfig();
|
||||||
|
|
||||||
|
// 设置编译模式
|
||||||
|
this.setCompileMode()
|
||||||
|
|
||||||
|
// 获取编译配置
|
||||||
|
await this.analyzeBuildConfig();
|
||||||
|
|
||||||
|
this.setStage(ServiceStage.initPlugins);
|
||||||
|
this.builtInPlugins = this.getBuiltInPlugins();
|
||||||
|
await this.pluginManager.register(this.builtInPlugins, this.userConfig.plugins);
|
||||||
|
|
||||||
|
this.setStage(ServiceStage.initHooks);
|
||||||
|
this.pluginManager.initHook();
|
||||||
|
}
|
||||||
|
|
||||||
|
getBuiltInPlugins(): string[] {
|
||||||
|
return readDirectory(path.resolve(__dirname, '../builtInPlugins'));
|
||||||
|
}
|
||||||
|
|
||||||
|
async run({ command, args }: { command: string | number; args: yargsParser.Arguments }) {
|
||||||
|
args._ = args._ || [];
|
||||||
|
if (args._[0] === command) {
|
||||||
|
args._.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.args = args;
|
||||||
|
|
||||||
|
await this.init();
|
||||||
|
|
||||||
|
this.setStage(ServiceStage.run);
|
||||||
|
return this.runCommand({ command, args });
|
||||||
|
}
|
||||||
|
|
||||||
|
async runCommand({ command, args }: { command: string | number; args: yargsParser.Arguments }) {
|
||||||
|
this.logger.debug(`run command ${command}`);
|
||||||
|
|
||||||
|
const commands =
|
||||||
|
typeof this.pluginManager.commands[command] === 'string'
|
||||||
|
? this.pluginManager.commands[this.pluginManager.commands[command] as string]
|
||||||
|
: this.pluginManager.commands[command];
|
||||||
|
|
||||||
|
if (commands === undefined) {
|
||||||
|
this.logger.error(`Invalid command ${command}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const { fn } = commands as ICommand;
|
||||||
|
|
||||||
|
return fn(args, this.pluginManager.store[command]);
|
||||||
|
}
|
||||||
|
|
||||||
|
setCompileMode() {
|
||||||
|
this.compileMode = this.userConfig.compileMode || 'webpack';
|
||||||
|
this.logger.debug(`current compile mode is ${this.compileMode}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async analyzeBuildConfig() {
|
||||||
|
if (this.userConfig.devBuildConfig) {
|
||||||
|
let { name, path, env } = this.userConfig.devBuildConfig;
|
||||||
|
path = isAbsolute(path) ? path : join(process.cwd(), path);
|
||||||
|
if (!existsSync(path)) {
|
||||||
|
this.logger.warn(`Cant't find dev build config. Path is ${path}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.logger.debug(`Find dev build config. Path is ${path}`);
|
||||||
|
let bc = await loadModule<object | Function>(path);
|
||||||
|
if (bc == undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let finalBc = {};
|
||||||
|
if (typeof bc === 'function') {
|
||||||
|
finalBc = bc(env)
|
||||||
|
this.devBuildConfig = finalBc;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.devBuildConfig = bc;
|
||||||
|
|
||||||
|
if (this.userConfig.devBuildConfig.devProxy) {
|
||||||
|
this.devProxy = this.userConfig.devBuildConfig.devProxy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (!this.userConfig.buildConfig) {
|
||||||
|
switch (this.compileMode) {
|
||||||
|
case 'webpack':
|
||||||
|
this.buildConfigPath.push({name:'default', path:'./webpack.config.js'})
|
||||||
|
break;
|
||||||
|
case 'vite':
|
||||||
|
this.buildConfigPath.push({name:'default', path:'./vite.config.js'})
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.logger.warn(`Unknown compile mode ${this.compileMode}`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.userConfig.buildConfig.forEach((userBuildConfig) => {
|
||||||
|
// if (typeof userBuildConfig === 'string') {
|
||||||
|
// const name = this.getConfigName(userBuildConfig);
|
||||||
|
// this.buildConfigPath.push({name, path: userBuildConfig});
|
||||||
|
// }
|
||||||
|
if (typeof userBuildConfig === 'object') {
|
||||||
|
// const name = userBuildConfig.name;
|
||||||
|
// const path = userBuildConfig.path;
|
||||||
|
this.buildConfigPath.push(userBuildConfig);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.buildConfigPath.forEach(async (config) => {
|
||||||
|
let {name, path} = config;
|
||||||
|
path = isAbsolute(path) ? path : join(process.cwd(), path);
|
||||||
|
if (!existsSync(path)) {
|
||||||
|
this.logger.debug(`Cant't find build config. Path is ${path}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.logger.debug(`Find build config. Path is ${path}`);
|
||||||
|
let bc = await loadModule<object | Function >(path);
|
||||||
|
if (bc == undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let finalBc = {};
|
||||||
|
if (typeof bc === 'function') {
|
||||||
|
finalBc = bc(config.env)
|
||||||
|
this.buildConfig.push({name: name, config: finalBc});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.buildConfig.push({name: name, config: bc});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
getConfigName(name: string): string {
|
||||||
|
name = name.replace('webpack.', '');
|
||||||
|
name = name.replace('.js', '');
|
||||||
|
name = name.replace('.ts', '');
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
export enum PluginType {
|
||||||
|
preset = 'preset',
|
||||||
|
plugin = 'plugin',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ServiceStage {
|
||||||
|
uninitialized,
|
||||||
|
constructor,
|
||||||
|
init,
|
||||||
|
initPlugins,
|
||||||
|
initHooks,
|
||||||
|
pluginReady,
|
||||||
|
getConfig,
|
||||||
|
run,
|
||||||
|
}
|
|
@ -0,0 +1,152 @@
|
||||||
|
import resolve from 'resolve';
|
||||||
|
import chalk from 'chalk';
|
||||||
|
import PluginAPI, { IOpts } from './PluginAPI.js';
|
||||||
|
import { IHook, ICommand } from '../types/types.js';
|
||||||
|
import Hub from '../core/Hub';
|
||||||
|
import { loadModule } from '../utils/loadModule.js';
|
||||||
|
import { Logger } from '../utils/logger.js';
|
||||||
|
|
||||||
|
interface pluginManagerOpts {
|
||||||
|
cwd: string;
|
||||||
|
hub: Hub;
|
||||||
|
logger: Logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PluginObj {
|
||||||
|
path: string;
|
||||||
|
api: PluginAPI;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPlugin {
|
||||||
|
id: string;
|
||||||
|
key: string;
|
||||||
|
path: string;
|
||||||
|
apply: Function;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Plugin {
|
||||||
|
cwd: string;
|
||||||
|
builtInPlugins: string[] = [];
|
||||||
|
userPlugins: string[] = [];
|
||||||
|
commands: {
|
||||||
|
[name: string]: ICommand | string;
|
||||||
|
} = {};
|
||||||
|
hooksByPluginPath: {
|
||||||
|
[id: string]: IHook[];
|
||||||
|
} = {};
|
||||||
|
hooks: {
|
||||||
|
[key: string]: IHook[];
|
||||||
|
} = {};
|
||||||
|
store: {
|
||||||
|
[key: string]: any;
|
||||||
|
} = {};
|
||||||
|
hub: Hub;
|
||||||
|
logger: Logger;
|
||||||
|
registerFunction: Function[] = [];
|
||||||
|
// 解决调用this[props]时ts提示属性未知
|
||||||
|
[key: string]: any;
|
||||||
|
|
||||||
|
constructor(opts: pluginManagerOpts) {
|
||||||
|
this.cwd = opts.cwd || process.cwd();
|
||||||
|
this.hub = opts.hub;
|
||||||
|
this.logger = opts.logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
getPluginPaths(builtInPlugins: string[], userPlugins: string[] | undefined): string[] {
|
||||||
|
const paths: string[] = [];
|
||||||
|
paths.push(...builtInPlugins);
|
||||||
|
if (userPlugins) {
|
||||||
|
paths.push(...userPlugins);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取所有插件文件的绝对路径
|
||||||
|
const absPaths: string[] = paths.map(path => {
|
||||||
|
return resolve.sync(path, {
|
||||||
|
basedir: this.cwd,
|
||||||
|
extensions: ['.js', '.ts'],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return absPaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
setStore(name: string, initialValue: any) {
|
||||||
|
const store = this.store;
|
||||||
|
if (this.store[name]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
store[name] = initialValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
async register(builtInPlugins: string[], userPlugins: string[] | undefined) {
|
||||||
|
const paths = this.getPluginPaths(builtInPlugins, userPlugins);
|
||||||
|
this.hub.pluginPaths = paths;
|
||||||
|
|
||||||
|
const objs: PluginObj[] = paths.map(path => {
|
||||||
|
const api: PluginAPI = this.createPluginAPI({
|
||||||
|
path,
|
||||||
|
manager: this,
|
||||||
|
logger: this.logger,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
path,
|
||||||
|
api,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const obj of objs) {
|
||||||
|
const module: Function | undefined = await loadModule(obj.path);
|
||||||
|
if (module) {
|
||||||
|
try {
|
||||||
|
module(obj.api);
|
||||||
|
} catch (err: unknown) {
|
||||||
|
if (err instanceof Error) {
|
||||||
|
this.logger.error(chalk.red(err.message));
|
||||||
|
if (err.stack) {
|
||||||
|
this.logger.error(err.stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo 给API换个名字
|
||||||
|
createPluginAPI(opts: IOpts): PluginAPI {
|
||||||
|
const pluginAPI = new PluginAPI(opts);
|
||||||
|
|
||||||
|
// 为PluginAPI添加代理
|
||||||
|
// 除了PluginAPI自有的方法之外,为开发者提供更丰富的api
|
||||||
|
return new Proxy(pluginAPI, {
|
||||||
|
get: (target: PluginAPI, prop: string) => {
|
||||||
|
if (['userConfig', 'devBuildConfig', 'buildConfig', 'compileMode', 'packageJson', 'cwd'].includes(prop)) {
|
||||||
|
return typeof this.hub[prop] === 'function'
|
||||||
|
? this.hub[prop].bind(this.hub)
|
||||||
|
: this.hub[prop];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (['setStore', 'logger', 'commands'].includes(prop)) {
|
||||||
|
return typeof this[prop] === 'function'
|
||||||
|
? this[prop].bind(this)
|
||||||
|
: this[prop];
|
||||||
|
}
|
||||||
|
|
||||||
|
return target[prop];
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
initHook() {
|
||||||
|
Object.keys(this.hooksByPluginPath).forEach(path => {
|
||||||
|
const hooks = this.hooksByPluginPath[path];
|
||||||
|
hooks.forEach(hook => {
|
||||||
|
const { name } = hook;
|
||||||
|
hook.pluginId = path;
|
||||||
|
if (!this.hooks[name]) {
|
||||||
|
this.hooks[name] = [];
|
||||||
|
}
|
||||||
|
this.hooks[name].push(hook);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
import Plugin from './Plugin.js';
|
||||||
|
import { IHook, ICommand } from '../types/types.js';
|
||||||
|
import { Logger } from '../utils/logger.js';
|
||||||
|
|
||||||
|
export interface IOpts {
|
||||||
|
path: string;
|
||||||
|
manager: Plugin;
|
||||||
|
logger: Logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class PluginAPI {
|
||||||
|
path: string;
|
||||||
|
manager: Plugin;
|
||||||
|
logger: Logger;
|
||||||
|
[key: string]: any;
|
||||||
|
|
||||||
|
constructor(opts: IOpts) {
|
||||||
|
this.path = opts.path;
|
||||||
|
this.manager = opts.manager;
|
||||||
|
this.logger = opts.logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
register(hook: IHook) {
|
||||||
|
if (!this.manager.hooksByPluginPath[this.path]) {
|
||||||
|
this.manager.hooksByPluginPath[this.path] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.manager.hooksByPluginPath[this.path].push(hook);
|
||||||
|
}
|
||||||
|
|
||||||
|
registerCommand(command: ICommand) {
|
||||||
|
const { name } = command;
|
||||||
|
this.manager.commands[name] = command;
|
||||||
|
if (command.initialState) {
|
||||||
|
this.manager.setStore(name, command.initialState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerHook(hook: IHook) {
|
||||||
|
this.register(hook);
|
||||||
|
}
|
||||||
|
|
||||||
|
registerMethod(fn: Function) {
|
||||||
|
this.manager.registerFunction.push(fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
async applyHook(name: string, args?: any ) {
|
||||||
|
const hooks: IHook[] = this.manager.hooks[name] || [];
|
||||||
|
let config: any = undefined;
|
||||||
|
for (const hook of hooks) {
|
||||||
|
if (this.manager.store[name]) {
|
||||||
|
config = this.manager.store[name];
|
||||||
|
}
|
||||||
|
if (hook.fn) {
|
||||||
|
await hook.fn(args, config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.manager.store[name];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
module.exports = {
|
||||||
|
testEnvironment: 'node', // 使用 Node.js 环境进行测试
|
||||||
|
|
||||||
|
// 匹配的测试文件模式
|
||||||
|
testMatch: ['**/__tests__/**/*.js?(x)', '**/?(*.)+(spec|test).js?(x)'],
|
||||||
|
};
|
|
@ -0,0 +1,5 @@
|
||||||
|
function sum(a, b) {
|
||||||
|
return a + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = sum;
|
|
@ -0,0 +1,7 @@
|
||||||
|
const sum = require('./sum');
|
||||||
|
|
||||||
|
describe('sum', () => {
|
||||||
|
it('should add two numbers', () => {
|
||||||
|
expect(sum(1, 2)).toEqual(3);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,6 @@
|
||||||
|
export default {
|
||||||
|
testEnvironment: 'node', // 使用 Node.js 环境进行测试
|
||||||
|
|
||||||
|
// 匹配的测试文件模式
|
||||||
|
testMatch: ['**/__tests__/**/*.js?(x)', '**/?(*.)+(spec|test).js?(x)'],
|
||||||
|
};
|
|
@ -0,0 +1,5 @@
|
||||||
|
function sum(a, b) {
|
||||||
|
return a + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default sum;
|
|
@ -0,0 +1,7 @@
|
||||||
|
import sum from './sum';
|
||||||
|
|
||||||
|
describe('sum', () => {
|
||||||
|
it('should add two numbers', () => {
|
||||||
|
expect(sum(1, 2)).toEqual(3);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,7 @@
|
||||||
|
export default {
|
||||||
|
preset: 'ts-jest',
|
||||||
|
testEnvironment: 'node', // 使用 Node.js 环境进行测试
|
||||||
|
|
||||||
|
// 匹配的测试文件模式
|
||||||
|
testMatch: ['**/__tests__/**/*.js?(x)', '**/?(*.)+(spec|test).(js?(x)|ts?(x))$'],
|
||||||
|
};
|
|
@ -0,0 +1,7 @@
|
||||||
|
import sum from './sum';
|
||||||
|
|
||||||
|
describe('sum', () => {
|
||||||
|
it('should add two numbers', () => {
|
||||||
|
expect(sum(1, 2)).toEqual(3);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,5 @@
|
||||||
|
function sum(a: number, b: number): number {
|
||||||
|
return a + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default sum;
|
|
@ -0,0 +1,153 @@
|
||||||
|
import { PackageJSON } from 'resolve';
|
||||||
|
import yargsParser from 'yargs-parser';
|
||||||
|
import { Logger } from '../utils/logger.js';
|
||||||
|
import type * as http from 'http';
|
||||||
|
import type * as express from 'express';
|
||||||
|
|
||||||
|
|
||||||
|
interface Request extends express.Request {
|
||||||
|
}
|
||||||
|
interface Response extends express.Response {
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IDep {
|
||||||
|
[name: string]: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPackage {
|
||||||
|
name?: string;
|
||||||
|
dependencies?: IDep;
|
||||||
|
devDependencies?: IDep;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPlugin {
|
||||||
|
id: string;
|
||||||
|
key: string;
|
||||||
|
path: string;
|
||||||
|
apply: Function;
|
||||||
|
|
||||||
|
config?: IPluginConfig;
|
||||||
|
isPreset?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPluginConfig {
|
||||||
|
default?: any;
|
||||||
|
onChange?: string | Function;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IHook {
|
||||||
|
// 触发事件名称
|
||||||
|
name: string;
|
||||||
|
fn?: {
|
||||||
|
(state: any, config: any): void;
|
||||||
|
};
|
||||||
|
pluginId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ICommand {
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
details?: string;
|
||||||
|
initialState?: any;
|
||||||
|
fn: {
|
||||||
|
(args: yargsParser.Arguments, config: any): void;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IConfig {
|
||||||
|
plugins?: string[];
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface applyHookConfig<T = any> {
|
||||||
|
name: string;
|
||||||
|
config?: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface API {
|
||||||
|
cwd: string;
|
||||||
|
logger: Logger;
|
||||||
|
userConfig: IConfig;
|
||||||
|
buildConfig: any;
|
||||||
|
devBuildConfig: any;
|
||||||
|
compileMode: string;
|
||||||
|
commands: string[];
|
||||||
|
packageJson: PackageJSON;
|
||||||
|
|
||||||
|
registerCommand: {
|
||||||
|
(command: ICommand): void;
|
||||||
|
};
|
||||||
|
registerHook: {
|
||||||
|
(hook: IHook): void;
|
||||||
|
};
|
||||||
|
registerMethod: {
|
||||||
|
(method: Function): void;
|
||||||
|
}
|
||||||
|
applyHook: {
|
||||||
|
(opts: applyHookConfig): void;
|
||||||
|
};
|
||||||
|
setStore: {
|
||||||
|
(name: string, initialState: any): void;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RemoteProxy {
|
||||||
|
target: string;
|
||||||
|
localPort?: number;
|
||||||
|
localStatic?: StaticFileMatcher[];
|
||||||
|
fowardingURL?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StaticFileMatcher {
|
||||||
|
url: string;
|
||||||
|
local: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserConfig {
|
||||||
|
mock?: MockConfig;
|
||||||
|
proxy?: RemoteProxy;
|
||||||
|
plugins?: string[];
|
||||||
|
compileMode?: string;
|
||||||
|
buildConfig?: BuildConfig[];
|
||||||
|
devBuildConfig?: DevBuildConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MockConfig {
|
||||||
|
enableMock?: boolean;
|
||||||
|
mockPath?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DevBuildConfig {
|
||||||
|
name: string;
|
||||||
|
path: string;
|
||||||
|
args?: object;
|
||||||
|
env?: object;
|
||||||
|
devProxy?: DevProxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DevProxy {
|
||||||
|
target: string;
|
||||||
|
matcher: ((pathname: string, req: Request) => boolean);
|
||||||
|
onProxyRes: (proxyRes: http.IncomingMessage, req: Request, res: Response) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BuildConfig {
|
||||||
|
name: string;
|
||||||
|
path: string;
|
||||||
|
args?: object;
|
||||||
|
env?: object;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ExportUserConfig = UserConfig | Promise<UserConfig>;
|
||||||
|
|
||||||
|
export function defineConfig(config: ExportUserConfig): ExportUserConfig {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Arguments {
|
||||||
|
_: Array<string | number>;
|
||||||
|
'--'?: Array<string | number>;
|
||||||
|
[argName: string]: any;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
import path from 'path';
|
||||||
|
import fs from 'fs';
|
||||||
|
import { build as esbuild, Plugin } from 'esbuild';
|
||||||
|
|
||||||
|
const buildConfig = async (fileName: string, format: 'esm' | 'cjs' = 'esm'): Promise<string> => {
|
||||||
|
|
||||||
|
// 外部依赖不构建参与构建,减少执行时间
|
||||||
|
const pluginExternalDeps: Plugin = {
|
||||||
|
name: 'plugin-external-deps',
|
||||||
|
setup(build) {
|
||||||
|
build.onResolve({ filter: /.*/ }, args => {
|
||||||
|
const id = args.path;
|
||||||
|
if (id[0] !== '.' && !path.isAbsolute(id)) {
|
||||||
|
return {
|
||||||
|
external: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// 将文件中的路径改成确定路径,避免执行时调用错误
|
||||||
|
const pluginReplaceImport: Plugin = {
|
||||||
|
name: 'plugin-replace-import-meta',
|
||||||
|
setup(build) {
|
||||||
|
build.onLoad({ filter: /\.[jt]s$/ }, args => {
|
||||||
|
const contents: string = fs.readFileSync(args.path, 'utf8');
|
||||||
|
|
||||||
|
// 替换import路径
|
||||||
|
contents.replace(/\bimport\.meta\.url\b/g, JSON.stringify(`file://${args.path}`));
|
||||||
|
|
||||||
|
// 替换当前目录路径
|
||||||
|
contents.replace(/\b__dirname\b/g, JSON.stringify(path.dirname(args.path)));
|
||||||
|
|
||||||
|
// 替换当前文件路径
|
||||||
|
contents.replace(/\b__filename\b/g, JSON.stringify(args.path));
|
||||||
|
|
||||||
|
return {
|
||||||
|
loader: args.path.endsWith('.ts') ? 'ts' : 'js',
|
||||||
|
contents: contents
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await esbuild({
|
||||||
|
entryPoints: [fileName],
|
||||||
|
outfile: 'out.js',
|
||||||
|
write: false,
|
||||||
|
platform: 'node',
|
||||||
|
bundle: true,
|
||||||
|
format,
|
||||||
|
metafile: true,
|
||||||
|
plugins: [pluginExternalDeps, pluginReplaceImport],
|
||||||
|
});
|
||||||
|
const { text } = result.outputFiles[0];
|
||||||
|
|
||||||
|
return text;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default buildConfig;
|
|
@ -0,0 +1,6 @@
|
||||||
|
export default async function dynamicImport(filePath: string) {
|
||||||
|
let importPath = filePath;
|
||||||
|
|
||||||
|
importPath = 'file:///' + importPath;
|
||||||
|
return await import(importPath);
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { join } from 'path';
|
||||||
|
import { readFileSync, existsSync } from 'fs';
|
||||||
|
import { parse } from 'dotenv';
|
||||||
|
|
||||||
|
export default function initializeEnv(): void {
|
||||||
|
const envPath: string = join(process.cwd(), '.env');
|
||||||
|
const localEnvPath: string = join(process.cwd(), '.local.env');
|
||||||
|
loadEnv(envPath);
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
loadEnv(localEnvPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadEnv(envPath: string): void {
|
||||||
|
if (existsSync(envPath)) {
|
||||||
|
const parsed = parse(readFileSync(envPath, 'utf-8')) || {};
|
||||||
|
Object.keys(parsed).forEach(key => {
|
||||||
|
process.env[key] = parsed[key];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
import { pathToFileURL } from 'url';
|
||||||
|
import { join, isAbsolute } from 'path';
|
||||||
|
import fs from 'fs';
|
||||||
|
import buildConfig from './build.js';
|
||||||
|
import { createRequire } from 'module';
|
||||||
|
import dynamicImport from './dynamicImport.js';
|
||||||
|
const require = createRequire(import.meta.url);
|
||||||
|
|
||||||
|
export async function loadModule<T>(filePath: string): Promise<T | undefined> {
|
||||||
|
filePath = isAbsolute(filePath) ? filePath : join(process.cwd(), filePath);
|
||||||
|
|
||||||
|
const isTsFile: boolean = filePath.endsWith('ts');
|
||||||
|
const isJsFile: boolean = filePath.endsWith('js');
|
||||||
|
|
||||||
|
|
||||||
|
let content: T | undefined;
|
||||||
|
|
||||||
|
// js文件,可以直接通过import引用
|
||||||
|
if (isJsFile) {
|
||||||
|
content = (await dynamicImport(filePath))?.default;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是ts文件,需要先转为js文件,再读取
|
||||||
|
if (isTsFile) {
|
||||||
|
const code = await buildConfig(filePath, 'esm');
|
||||||
|
content = await getTypescriptModule(code, filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getTypescriptModule(code: string, filePath: string, isEsm = true) {
|
||||||
|
const tempFile = `${filePath}.${isEsm ? 'm' : 'c'}js`;
|
||||||
|
let content = null;
|
||||||
|
|
||||||
|
// todo 臨時文件管理
|
||||||
|
fs.writeFileSync(tempFile, code);
|
||||||
|
|
||||||
|
delete require.cache[require.resolve(tempFile)];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const raw = isEsm ? await dynamicImport(tempFile) : require(tempFile);
|
||||||
|
content = raw?.default ?? raw;
|
||||||
|
} catch (err: unknown) {
|
||||||
|
fs.unlinkSync(tempFile);
|
||||||
|
if (err instanceof Error) {
|
||||||
|
err.message = err.message.replace(tempFile, filePath);
|
||||||
|
err.stack = err.stack?.replace(tempFile, filePath);
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo 刪除失敗加日誌
|
||||||
|
fs.unlinkSync(tempFile);
|
||||||
|
|
||||||
|
return content;
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
import fs from 'fs';
|
||||||
|
import { PackageJSON } from 'resolve';
|
||||||
|
|
||||||
|
export const loadPkg = (path: string): PackageJSON => {
|
||||||
|
const packageJson = fs.readFileSync(path, 'utf8');
|
||||||
|
const packageData: PackageJSON = JSON.parse(packageJson);
|
||||||
|
return packageData;
|
||||||
|
};
|
|
@ -0,0 +1,42 @@
|
||||||
|
export enum LogLevel {
|
||||||
|
DEBUG = 0,
|
||||||
|
INFO = 1,
|
||||||
|
WARN = 2,
|
||||||
|
ERROR = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Logger {
|
||||||
|
private readonly level: LogLevel;
|
||||||
|
|
||||||
|
constructor(level?: LogLevel) {
|
||||||
|
if (level !== undefined) {
|
||||||
|
this.level = level;
|
||||||
|
} else {
|
||||||
|
this.level = LogLevel.INFO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debug(message: string): void {
|
||||||
|
if (this.level <= LogLevel.DEBUG) {
|
||||||
|
console.debug(`[DEBUG] ${message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info(message: string): void {
|
||||||
|
if (this.level <= LogLevel.INFO) {
|
||||||
|
console.info(`[INFO] ${message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
warn(message: string): void {
|
||||||
|
if (this.level <= LogLevel.WARN) {
|
||||||
|
console.warn(`[WARN] ${message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
error(message: string, error?: Error): void {
|
||||||
|
if (this.level <= LogLevel.ERROR) {
|
||||||
|
console.error(`[ERROR] ${message}`, error || '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,130 @@
|
||||||
|
import chokidar from 'chokidar';
|
||||||
|
import bodyParser from 'body-parser';
|
||||||
|
import {globSync} from 'glob';
|
||||||
|
import { join } from 'path';
|
||||||
|
|
||||||
|
import { createRequire } from 'module';
|
||||||
|
const require = createRequire(import.meta.url);
|
||||||
|
|
||||||
|
const mockDir = join(process.cwd(), 'mock');
|
||||||
|
const HTTP_METHODS = ['get', 'post', 'put', 'patch', 'delete', 'options', 'head'];
|
||||||
|
|
||||||
|
const jsonParser = bodyParser.json();
|
||||||
|
const urlencodedParser = bodyParser.urlencoded({
|
||||||
|
extended: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
interface Mock {
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取 mock 文件夹下的 js 文件
|
||||||
|
function getMocksFile() {
|
||||||
|
const mockFiles = globSync('**/*.js', {
|
||||||
|
cwd: mockDir,
|
||||||
|
});
|
||||||
|
let ret = mockFiles.reduce((mocks: any, mockFile: string) => {
|
||||||
|
if (!mockFile.startsWith('_')) {
|
||||||
|
mocks = {
|
||||||
|
...mocks,
|
||||||
|
...require(join(mockDir, mockFile)),
|
||||||
|
};
|
||||||
|
console.log('mockFile', require(join(mockDir, mockFile)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mocks;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateRoutes(app: any) {
|
||||||
|
let mockStartIndex = app._router.stack.length,
|
||||||
|
mocks: Mock = {};
|
||||||
|
|
||||||
|
try {
|
||||||
|
mocks = getMocksFile();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Generate mock routes error', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const mockItem in mocks) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(mocks, mockItem)) {
|
||||||
|
try {
|
||||||
|
const trimMockItemArr = mockItem
|
||||||
|
.replace(/(^\s*)|(\s*$)/g, '')
|
||||||
|
.replace(/\s+/g, ' ')
|
||||||
|
.split(' ');
|
||||||
|
|
||||||
|
const respond = mocks[mockItem];
|
||||||
|
|
||||||
|
let mockType = 'get',
|
||||||
|
mockUrl;
|
||||||
|
if (trimMockItemArr.length === 1) {
|
||||||
|
mockUrl = trimMockItemArr[0];
|
||||||
|
} else {
|
||||||
|
[mockType, mockUrl] = trimMockItemArr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockTypeLowerCase = mockType.toLowerCase();
|
||||||
|
|
||||||
|
if (!HTTP_METHODS.includes(mockTypeLowerCase)) {
|
||||||
|
throw new Error(`Invalid HTTP request method ${mockType} for path ${mockUrl}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
app[mockTypeLowerCase](
|
||||||
|
mockUrl,
|
||||||
|
[jsonParser, urlencodedParser],
|
||||||
|
respond instanceof Function
|
||||||
|
? respond
|
||||||
|
: (_req: any, res: { send: (arg0: any) => void }) => {
|
||||||
|
res.send(respond);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
mockRoutesLength: app._router.stack.length - mockStartIndex,
|
||||||
|
mockStartIndex: mockStartIndex,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除 mock 文件下的 require 缓存
|
||||||
|
function cleanRequireCache() {
|
||||||
|
Object.keys(require.cache).forEach(key => {
|
||||||
|
if (key.includes(mockDir)) {
|
||||||
|
delete require.cache[require.resolve(key)];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default (app: { _router: { stack: any[] } }) => {
|
||||||
|
const mockRoutes = generateRoutes(app);
|
||||||
|
let { mockRoutesLength } = mockRoutes;
|
||||||
|
let { mockStartIndex } = mockRoutes;
|
||||||
|
|
||||||
|
// 监听 mock 文件夹下文件变化
|
||||||
|
chokidar
|
||||||
|
.watch(mockDir, {
|
||||||
|
ignoreInitial: true,
|
||||||
|
})
|
||||||
|
.on('all', (event: string, _path: any) => {
|
||||||
|
if (event === 'change' || event === 'add') {
|
||||||
|
try {
|
||||||
|
// 删除中间件映射
|
||||||
|
app._router.stack.splice(mockStartIndex, mockRoutesLength);
|
||||||
|
|
||||||
|
cleanRequireCache();
|
||||||
|
const mockRoutes = generateRoutes(app);
|
||||||
|
|
||||||
|
mockRoutesLength = mockRoutes.mockRoutesLength;
|
||||||
|
mockStartIndex = mockRoutes.mockStartIndex;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,28 @@
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
export default function readDirectory(directoryPath: string): string[] {
|
||||||
|
const filesArray: string[] = [];
|
||||||
|
const traverseDirectory = (directoryPath: string) => {
|
||||||
|
const files = fs.readdirSync(directoryPath);
|
||||||
|
for (const file of files) {
|
||||||
|
const filePath = path.join(directoryPath, file);
|
||||||
|
|
||||||
|
if (fs.statSync(filePath).isDirectory()) {
|
||||||
|
// 如果是目录,则递归读取该目录下的所有文件
|
||||||
|
traverseDirectory(filePath);
|
||||||
|
} else {
|
||||||
|
if (filePath.startsWith('.')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 如果是文件,则将其全路径添加到数组中
|
||||||
|
if (filePath.endsWith('.js')) {
|
||||||
|
filesArray.push(filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
traverseDirectory(directoryPath);
|
||||||
|
return filesArray;
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { createProxyMiddleware } from 'http-proxy-middleware';
|
||||||
|
import { API } from '../types/types';
|
||||||
|
|
||||||
|
export default (app: any, api: API) => {
|
||||||
|
const { devProxy } = api.userConfig.devBuildConfig;
|
||||||
|
app.use(createProxyMiddleware(devProxy.matcher, {
|
||||||
|
target: devProxy.target,
|
||||||
|
secure: false,
|
||||||
|
changeOrigin: true,
|
||||||
|
ws: false,
|
||||||
|
onProxyRes: devProxy.onProxyRes
|
||||||
|
}));
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
import { dirname } from 'path';
|
||||||
|
import { readFileSync, writeFileSync } from 'fs';
|
||||||
|
import resolve from 'resolve';
|
||||||
|
// @ts-ignore
|
||||||
|
import crequire from 'crequire'
|
||||||
|
import { createRequire } from 'module';
|
||||||
|
const require = createRequire(import.meta.url);
|
||||||
|
|
||||||
|
function parse(filePath: string): string[] {
|
||||||
|
const content = readFileSync(filePath, 'utf-8');
|
||||||
|
return (crequire(content) as any[])
|
||||||
|
.map<string>(o => o.path)
|
||||||
|
.filter(path => path.charAt(0) === '.')
|
||||||
|
.map(path =>
|
||||||
|
resolve.sync(path, {
|
||||||
|
basedir: dirname(filePath),
|
||||||
|
extensions: ['.tsx', '.ts', '.jsx', '.js'],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseRequireDeps(filePath: string): string[] {
|
||||||
|
const paths: string[] = [filePath];
|
||||||
|
const ret: string[] = [filePath];
|
||||||
|
|
||||||
|
while (paths.length) {
|
||||||
|
const extraPaths = parse(paths.shift()!).filter(path => !ret.includes(path));
|
||||||
|
if (extraPaths.length) {
|
||||||
|
paths.push(...extraPaths);
|
||||||
|
ret.push(...extraPaths);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isWindows = typeof process !== 'undefined' && process.platform === 'win32';
|
||||||
|
|
||||||
|
export function cleanRequireCache(cacheKey: string): void {
|
||||||
|
const cachePath = isWindows ? cacheKey.replace(/\//g, '\\') : cacheKey;
|
||||||
|
if (require.cache[cachePath]) {
|
||||||
|
const cacheParent = (require.cache[cachePath] as any).parent;
|
||||||
|
let i = cacheParent?.children.length || 0;
|
||||||
|
while (i--) {
|
||||||
|
if (cacheParent!.children[i].id === cachePath) {
|
||||||
|
cacheParent!.children.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete require.cache[cachePath];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function copyFile(targetPath: string, sourcePath: string): void {
|
||||||
|
try {
|
||||||
|
const fileContent = readFileSync(sourcePath);
|
||||||
|
writeFileSync(targetPath, fileContent);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Copy file failed.', error);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
module.exports = {
|
||||||
|
testEnvironment: 'node', // 使用 Node.js 环境进行测试
|
||||||
|
|
||||||
|
// 匹配的测试文件模式
|
||||||
|
testMatch: ['**/__tests__/**/*.js?(x)', '**/?(*.)+(spec|test).js?(x)'],
|
||||||
|
};
|
|
@ -0,0 +1,5 @@
|
||||||
|
function sum(a, b) {
|
||||||
|
return a + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = sum;
|
|
@ -0,0 +1,7 @@
|
||||||
|
const sum = require('./sum');
|
||||||
|
|
||||||
|
describe('sum', () => {
|
||||||
|
it('should add two numbers', () => {
|
||||||
|
expect(sum(1, 2)).toEqual(3);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,6 @@
|
||||||
|
export default {
|
||||||
|
testEnvironment: 'node', // 使用 Node.js 环境进行测试
|
||||||
|
|
||||||
|
// 匹配的测试文件模式
|
||||||
|
testMatch: ['**/__tests__/**/*.js?(x)', '**/?(*.)+(spec|test).js?(x)'],
|
||||||
|
};
|
|
@ -0,0 +1,5 @@
|
||||||
|
function sum(a, b) {
|
||||||
|
return a + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default sum;
|
|
@ -0,0 +1,7 @@
|
||||||
|
import sum from './sum';
|
||||||
|
|
||||||
|
describe('sum', () => {
|
||||||
|
it('should add two numbers', () => {
|
||||||
|
expect(sum(1, 2)).toEqual(3);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,7 @@
|
||||||
|
export default {
|
||||||
|
preset: 'ts-jest',
|
||||||
|
testEnvironment: 'node', // 使用 Node.js 环境进行测试
|
||||||
|
|
||||||
|
// 匹配的测试文件模式
|
||||||
|
testMatch: ['**/__tests__/**/*.js?(x)', '**/?(*.)+(spec|test).(js?(x)|ts?(x))$'],
|
||||||
|
};
|
|
@ -0,0 +1,7 @@
|
||||||
|
import sum from './sum';
|
||||||
|
|
||||||
|
describe('sum', () => {
|
||||||
|
it('should add two numbers', () => {
|
||||||
|
expect(sum(1, 2)).toEqual(3);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,5 @@
|
||||||
|
function sum(a: number, b: number): number {
|
||||||
|
return a + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default sum;
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es2015",
|
||||||
|
"module": "esnext",
|
||||||
|
"sourceMap": false,
|
||||||
|
"outDir": "./lib",
|
||||||
|
"strict": true,
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"declaration": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"],
|
||||||
|
"exclude": ["node_modules", "**/*.spec.ts", "./src/template/**/*"],
|
||||||
|
"ts-node": {
|
||||||
|
"esm": true,
|
||||||
|
},
|
||||||
|
}
|
Loading…
Reference in New Issue