Skip to content

Commit

Permalink
feat: 支持热更新。新增 watch 参数,支持配置文件与规则文件的监听模式
Browse files Browse the repository at this point in the history
  • Loading branch information
renxia committed Feb 18, 2024
1 parent cb5d3cf commit 5458476
Show file tree
Hide file tree
Showing 9 changed files with 131 additions and 10 deletions.
2 changes: 1 addition & 1 deletion .husky/pre-commit
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
. "$(dirname "$0")/_/husky.sh"

pnpm exec flh --tscheck --only-changes
pnpm exec flh --prettier --cache
pnpm exec flh --prettier --cache --fix
11 changes: 11 additions & 0 deletions example/rules/req-header.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/** @type {import('@lzwme/whistle.x-scripts').RuleItem} */
module.exports = {
on: 'req-header',
ruleId: 'hdl_data',
method: '**',
url: 'https://superapp-public.kiwa-tech.com/activity/wxapp/**',
desc: '海底捞小程序签到 token',
handler({ headers }) {
if (headers['_haidilao_app_token']) return { envConfig: { value: headers['_haidilao_app_token'] } };
},
};
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,11 @@
"devDependencies": {
"@lzwme/fe-utils": "^1.6.0",
"@lzwme/fed-lint-helper": "^2.5.2",
"@types/node": "^20.11.5",
"@types/node": "^20.11.19",
"base64-js": "^1.5.1",
"crypto-js": "^4.2.0",
"husky": "^8.0.3",
"prettier": "^3.2.4",
"husky": "^9.0.11",
"prettier": "^3.2.5",
"standard-version": "^9.5.0",
"typescript": "^5.3.3"
}
Expand Down
2 changes: 1 addition & 1 deletion src/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* @Author: renxia
* @Date: 2024-01-15 08:51:18
* @LastEditors: renxia
* @LastEditTime: 2024-01-16 09:27:30
* @LastEditTime: 2024-02-07 15:32:36
* @Description:
*/
import { color } from '@lzwme/fe-utils';
Expand Down
2 changes: 1 addition & 1 deletion src/lib/QingLong.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export class QingLoing {
if (this.config.token) {
this.req.setHeaders({ Authorization: `Bearer ${this.config.token}` });
this.loginStatus = 1;
} else {
} else {
logger.warn('请在配置文件中配置 ql 相关变量');
this.loginStatus--;
}
Expand Down
13 changes: 12 additions & 1 deletion src/lib/getConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* @Author: renxia
* @Date: 2024-01-11 13:17:00
* @LastEditors: renxia
* @LastEditTime: 2024-02-07 09:10:02
* @LastEditTime: 2024-02-07 15:40:34
* @Description:
*/
import type { W2XScriptsConfig } from '../../typings';
Expand All @@ -12,9 +12,11 @@ import { homedir } from 'node:os';
import { assign, color } from '@lzwme/fe-utils';
import { logger } from './helper';
import { rulesManage } from './rulesManage';
import { Watcher } from './watch';

const config: W2XScriptsConfig = {
debug: Boolean(process.env.DEBUG),
watch: 30000,
logType: process.env.LOG_TYPE as never,
ql: {
host: process.env.QL_HOST || 'http://127.0.0.1:5700',
Expand Down Expand Up @@ -47,6 +49,14 @@ export function getConfig(useCache = true) {
assign(config, require(configFilePath));
config.rules.forEach(d => (d._source = configFilePath));
logger.info('配置文件加载成功', color.cyan(configFilePath));

Watcher.add(configFilePath, type => {
if (type === 'update') {
logger.info('配置文件更新', color.cyan(configFilePath));
delete require.cache[require.resolve(configFilePath)];
getConfig(false);
}
});
return true;
} catch (e) {
logger.error('配置文件加载失败', color.red(configFilePath));
Expand Down Expand Up @@ -77,6 +87,7 @@ export function getConfig(useCache = true) {
rulesManage.classifyRules([...allRules], config, !isLoaded);

isLoaded = true;
config.watch ? Watcher.start() : Watcher.stop();
}

return config;
Expand Down
31 changes: 29 additions & 2 deletions src/lib/rulesManage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { color } from '@lzwme/fe-utils';
import { logger } from './helper';
import type { RuleRunOnType, RuleItem, W2XScriptsConfig } from '../../typings';
import { getConfig } from './getConfig';
import { Watcher, WatcherOnChange } from './watch';

const { green, cyan, magenta, magentaBright, greenBright } = color;
const RulesCache: Partial<Record<RuleRunOnType | 'all', Map<string, RuleItem>>> = { all: new Map() };
Expand Down Expand Up @@ -74,17 +75,23 @@ function loadRules(filepaths: string[] = [], isInit = false) {
const findRuleFiles = (filepath: string) => {
if (!filepath || typeof filepath !== 'string' || !existsSync(filepath)) return;

filepath = resolve(process.cwd(), filepath);

if (statSync(filepath).isDirectory()) {
readdirSync(filepath)
.filter(d => /rules|src|(\.c?js)$/.test(d))
.forEach(d => findRuleFiles(resolve(filepath, d)));
} else if (/\.c?js/.test(filepath)) {
filesSet.add(resolve(process.cwd(), filepath));
filesSet.add(filepath);
Watcher.add(filepath, onRuleFileChange);
}
};

// 合入当前目录下名称包含 `x-scripts-rule` 的文件或目录
new Set(filepaths.filter(Boolean).map(d => resolve(d))).forEach(d => findRuleFiles(d));
new Set(filepaths.filter(Boolean).map(d => resolve(d))).forEach(d => {
findRuleFiles(d);
Watcher.add(d, onRuleFileChange);
});

const rules = new Set<RuleItem>();
for (let filepath of filesSet) {
Expand Down Expand Up @@ -127,10 +134,30 @@ function changeRuleStatus(rule: RuleItem, status: boolean, config = getConfig())
return true;
}

const onRuleFileChange: WatcherOnChange = (type, filepath) => {
if (type === 'del') {
let count = 0;
for (const item of Object.values(rulesManage.rules)) {
for (const [key, rule] of item) {
if (rule._source === filepath) {
item.delete(key);
count++;
}
}
}

logger.info(`文件已删除: ${color.yellow(filepath)},删除了 ${color.cyan(count)} 条规则。`);
} else {
const rules = rulesManage.loadRules([filepath], false);
logger.info(`文件${type === 'add' ? '新增' : '修改'}: ${color.yellow(filepath)},更新了条 ${color.cyan(rules.size)} 规则`);
}
};

export const rulesManage = {
rules: RulesCache,
ruleFormat,
classifyRules,
loadRules,
changeRuleStatus,
// onRuleFileChange,
};
70 changes: 70 additions & 0 deletions src/lib/watch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* @Author: renxia
* @Date: 2024-02-07 13:38:29
* @LastEditors: renxia
* @LastEditTime: 2024-02-07 15:37:40
* @Description:
*/
import { existsSync, readdirSync, statSync } from 'node:fs';
import { logger } from './helper';
import { resolve } from 'node:path';

export type WatcherOnChange = (type: 'update' | 'del' | 'add', filepath: string) => void;

const watcherCache = new Map<string, { mtime: number; onchange: WatcherOnChange }>();
let timer: NodeJS.Timeout;

function checkFile(filepath: string, config = watcherCache.get(filepath)) {
if (existsSync(filepath)) {
const stat = statSync(filepath);

if (stat.isFile()) {
if (config.mtime !== stat.mtimeMs) {
config.mtime = stat.mtimeMs;
config.onchange('update', filepath);
}
} else if (stat.isDirectory()) {
readdirSync(filepath).forEach(filename => {
const isJsFile = filename.endsWith('.cjs') || filename.endsWith('.js');
if (isJsFile || /rules|src/.test(filename)) {
const fpath = resolve(filepath, filename);

if (!watcherCache.has(fpath)) {
if (isJsFile) {
Watcher.add(fpath, config.onchange);
config.onchange('add', fpath);
} else {
checkFile(fpath, config);
}
}
}
});
}
} else {
config.onchange('del', filepath);
watcherCache.delete(filepath);
}
}

function checkWatch(interval: number) {
watcherCache.forEach((config, filepath) => checkFile(filepath, config));
clearTimeout(timer);
timer = setTimeout(() => checkWatch(interval), interval);
}

export const Watcher = {
add(filepath: string, onchange: WatcherOnChange) {
if (existsSync(filepath) && !watcherCache.has(filepath)) watcherCache.set(filepath, { mtime: statSync(filepath).mtimeMs, onchange });
},
start: (interval: number | boolean = 3000) => {
checkWatch(Math.max(1000, +interval || 0));
logger.info('Watcher started. cache size:', watcherCache.size);
},
stop: () => {
if (timer) {
clearTimeout(timer);
logger.info('Watcher stoped');
timer = null;
}
},
};
4 changes: 3 additions & 1 deletion typings/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* @Author: renxia
* @Date: 2024-01-11 16:53:50
* @LastEditors: renxia
* @LastEditTime: 2024-02-07 09:34:54
* @LastEditTime: 2024-02-07 15:41:01
* @Description:
*/
/// <reference path="global.d.ts" />
Expand All @@ -13,6 +13,8 @@ type IncomingHttpHeaders = import('http').IncomingHttpHeaders;
export interface W2XScriptsConfig {
/** 是否开启调试模式。默认读取环境变量 DEBUG */
debug?: boolean;
/** 是否开启监听模式。若为 number 则设置监听定时器的时间间隔,单位为 ms。默认为 30000 */
watch?: boolean | number;
/** 日志级别。默认为 info */
logType?: import('@lzwme/fe-utils').LogLevelType;
/** 青龙脚本相关配置 */
Expand Down

0 comments on commit 5458476

Please sign in to comment.