Chore: make ESLint Happy

This commit is contained in:
SukkaW 2024-10-10 21:40:54 +08:00
parent 40cda6997a
commit e40979e50e
31 changed files with 228 additions and 220 deletions

View File

@ -64,46 +64,48 @@ export const buildCommon = task(require.main === module, __filename)(async (span
const $skip = Symbol('skip'); const $skip = Symbol('skip');
const processFile = (span: Span, sourcePath: string) => span.traceChildAsync(`process file: ${sourcePath}`, async () => { function processFile(span: Span, sourcePath: string) {
const lines: string[] = []; return span.traceChildAsync(`process file: ${sourcePath}`, async () => {
const lines: string[] = [];
let title = ''; let title = '';
const descriptions: string[] = []; const descriptions: string[] = [];
let sgmodulePathname: string | null = null; let sgmodulePathname: string | null = null;
try { try {
for await (const line of readFileByLine(sourcePath)) { for await (const line of readFileByLine(sourcePath)) {
if (line.startsWith(MAGIC_COMMAND_SKIP)) { if (line.startsWith(MAGIC_COMMAND_SKIP)) {
return $skip; return $skip;
} }
if (line.startsWith(MAGIC_COMMAND_TITLE)) { if (line.startsWith(MAGIC_COMMAND_TITLE)) {
title = line.slice(MAGIC_COMMAND_TITLE.length).trim(); title = line.slice(MAGIC_COMMAND_TITLE.length).trim();
continue; continue;
} }
if (line.startsWith(MAGIC_COMMAND_DESCRIPTION)) { if (line.startsWith(MAGIC_COMMAND_DESCRIPTION)) {
descriptions.push(line.slice(MAGIC_COMMAND_DESCRIPTION.length).trim()); descriptions.push(line.slice(MAGIC_COMMAND_DESCRIPTION.length).trim());
continue; continue;
} }
if (line.startsWith(MAGIC_COMMAND_SGMODULE_MITM_HOSTNAMES)) { if (line.startsWith(MAGIC_COMMAND_SGMODULE_MITM_HOSTNAMES)) {
sgmodulePathname = line.slice(MAGIC_COMMAND_SGMODULE_MITM_HOSTNAMES.length).trim(); sgmodulePathname = line.slice(MAGIC_COMMAND_SGMODULE_MITM_HOSTNAMES.length).trim();
continue; continue;
} }
const l = processLine(line); const l = processLine(line);
if (l) { if (l) {
lines.push(l); lines.push(l);
}
} }
} catch (e) {
console.error('Error processing', sourcePath);
console.trace(e);
} }
} catch (e) {
console.error('Error processing', sourcePath);
console.trace(e);
}
return [title, descriptions, lines, sgmodulePathname] as const; return [title, descriptions, lines, sgmodulePathname] as const;
}); });
}
function transformDomainset(parentSpan: Span, sourcePath: string, relativePath: string) { function transformDomainset(parentSpan: Span, sourcePath: string, relativePath: string) {
return parentSpan return parentSpan

View File

@ -12,7 +12,7 @@ import { appendArrayInPlace } from './lib/append-array-in-place';
import { OUTPUT_INTERNAL_DIR, OUTPUT_MODULES_DIR, SOURCE_DIR } from './constants/dir'; import { OUTPUT_INTERNAL_DIR, OUTPUT_MODULES_DIR, SOURCE_DIR } from './constants/dir';
import { RulesetOutput } from './lib/create-file'; import { RulesetOutput } from './lib/create-file';
const getRule = (domain: string) => { function getRule(domain: string) {
switch (domain[0]) { switch (domain[0]) {
case '+': case '+':
case '$': case '$':
@ -20,7 +20,7 @@ const getRule = (domain: string) => {
default: default:
return `DOMAIN-SUFFIX,${domain}`; return `DOMAIN-SUFFIX,${domain}`;
} }
}; }
export const getDomesticAndDirectDomainsRulesetPromise = createMemoizedPromise(async () => { export const getDomesticAndDirectDomainsRulesetPromise = createMemoizedPromise(async () => {
const domestics = await readFileIntoProcessedArray(path.join(SOURCE_DIR, 'non_ip/domestic.conf')); const domestics = await readFileIntoProcessedArray(path.join(SOURCE_DIR, 'non_ip/domestic.conf'));
const directs = await readFileIntoProcessedArray(path.resolve(SOURCE_DIR, 'non_ip/direct.conf')); const directs = await readFileIntoProcessedArray(path.resolve(SOURCE_DIR, 'non_ip/direct.conf'));

View File

@ -14,7 +14,7 @@ import { compareAndWriteFile } from './lib/create-file';
const mockDir = path.join(ROOT_DIR, 'Mock'); const mockDir = path.join(ROOT_DIR, 'Mock');
const modulesDir = path.join(ROOT_DIR, 'Modules'); const modulesDir = path.join(ROOT_DIR, 'Modules');
const copyDirContents = async (srcDir: string, destDir: string) => { async function copyDirContents(srcDir: string, destDir: string) {
const promises: Array<Promise<void>> = []; const promises: Array<Promise<void>> = [];
for await (const entry of await fsp.opendir(srcDir)) { for await (const entry of await fsp.opendir(srcDir)) {
@ -28,7 +28,7 @@ const copyDirContents = async (srcDir: string, destDir: string) => {
} }
return Promise.all(promises); return Promise.all(promises);
}; }
export const buildPublic = task(require.main === module, __filename)(async (span) => { export const buildPublic = task(require.main === module, __filename)(async (span) => {
await span.traceChildAsync('copy rest of the files', async () => { await span.traceChildAsync('copy rest of the files', async () => {
@ -83,7 +83,7 @@ const prioritySorter = (a: TreeType, b: TreeType) => ((priorityOrder[a.name] ||
const html = (string: TemplateStringsArray, ...values: any[]) => string.reduce((acc, str, i) => acc + str + (values[i] ?? ''), ''); const html = (string: TemplateStringsArray, ...values: any[]) => string.reduce((acc, str, i) => acc + str + (values[i] ?? ''), '');
const walk = (tree: TreeTypeArray) => { function walk(tree: TreeTypeArray) {
let result = ''; let result = '';
tree.sort(prioritySorter); tree.sort(prioritySorter);
for (let i = 0, len = tree.length; i < len; i++) { for (let i = 0, len = tree.length; i < len; i++) {
@ -102,7 +102,7 @@ const walk = (tree: TreeTypeArray) => {
} }
} }
return result; return result;
}; }
function generateHtml(tree: TreeTypeArray) { function generateHtml(tree: TreeTypeArray) {
return html` return html`

View File

@ -141,7 +141,7 @@ const latestTopUserAgentsPromise = $fetch('https://cdn.jsdelivr.net/npm/top-user
.then(res => res.json()) .then(res => res.json())
.then((userAgents: string[]) => userAgents.filter(ua => ua.startsWith('Mozilla/5.0 '))); .then((userAgents: string[]) => userAgents.filter(ua => ua.startsWith('Mozilla/5.0 ')));
const querySpeedtestApi = async (keyword: string): Promise<Array<string | null>> => { async function querySpeedtestApi(keyword: string): Promise<Array<string | null>> {
const topUserAgents = await latestTopUserAgentsPromise; const topUserAgents = await latestTopUserAgentsPromise;
const url = `https://www.speedtest.net/api/js/servers?engine=js&search=${keyword}&limit=100`; const url = `https://www.speedtest.net/api/js/servers?engine=js&search=${keyword}&limit=100`;
@ -181,7 +181,7 @@ const querySpeedtestApi = async (keyword: string): Promise<Array<string | null>>
console.error(e); console.error(e);
return []; return [];
} }
}; }
export const buildSpeedtestDomainSet = task(require.main === module, __filename)(async (span) => { export const buildSpeedtestDomainSet = task(require.main === module, __filename)(async (span) => {
const output = new DomainsetOutput(span, 'speedtest') const output = new DomainsetOutput(span, 'speedtest')

View File

@ -6,33 +6,33 @@ import { ALL, NORTH_AMERICA, EU, HK, TW, JP, KR } from '../Source/stream';
import { SHARED_DESCRIPTION } from './lib/constants'; import { SHARED_DESCRIPTION } from './lib/constants';
import { RulesetOutput } from './lib/create-file'; import { RulesetOutput } from './lib/create-file';
export const createRulesetForStreamService = ( export function createRulesetForStreamService(span: Span,
span: Span,
fileId: string, title: string, fileId: string, title: string,
streamServices: Array<import('../Source/stream').StreamService> streamServices: Array<import('../Source/stream').StreamService>) {
) => span.traceChildAsync(fileId, async (childSpan) => Promise.all([ return span.traceChildAsync(fileId, async (childSpan) => Promise.all([
// Domains // Domains
new RulesetOutput(childSpan, fileId, 'non_ip') new RulesetOutput(childSpan, fileId, 'non_ip')
.withTitle(`Sukka's Ruleset - Stream Services: ${title}`) .withTitle(`Sukka's Ruleset - Stream Services: ${title}`)
.withDescription([ .withDescription([
...SHARED_DESCRIPTION, ...SHARED_DESCRIPTION,
'', '',
...streamServices.map((i) => `- ${i.name}`) ...streamServices.map((i) => `- ${i.name}`)
]) ])
.addFromRuleset(streamServices.flatMap((i) => i.rules)) .addFromRuleset(streamServices.flatMap((i) => i.rules))
.write(), .write(),
// IP // IP
new RulesetOutput(childSpan, fileId, 'ip') new RulesetOutput(childSpan, fileId, 'ip')
.withTitle(`Sukka's Ruleset - Stream Services IPs: ${title}`) .withTitle(`Sukka's Ruleset - Stream Services IPs: ${title}`)
.withDescription([ .withDescription([
...SHARED_DESCRIPTION, ...SHARED_DESCRIPTION,
'', '',
...streamServices.map((i) => `- ${i.name}`) ...streamServices.map((i) => `- ${i.name}`)
]) ])
.bulkAddCIDR4NoResolve(streamServices.flatMap(i => i.ip?.v4 ?? [])) .bulkAddCIDR4NoResolve(streamServices.flatMap(i => i.ip?.v4 ?? []))
.bulkAddCIDR6NoResolve(streamServices.flatMap(i => i.ip?.v6 ?? [])) .bulkAddCIDR6NoResolve(streamServices.flatMap(i => i.ip?.v6 ?? []))
.write() .write()
])); ]));
}
export const buildStreamService = task(require.main === module, __filename)(async (span) => Promise.all([ export const buildStreamService = task(require.main === module, __filename)(async (span) => Promise.all([
createRulesetForStreamService(span, 'stream', 'All', ALL), createRulesetForStreamService(span, 'stream', 'All', ALL),

View File

@ -6,14 +6,14 @@ type Node = Map<string, Node> & {
[FAIL]: Node | undefined [FAIL]: Node | undefined
}; };
const createNode = (): Node => { function createNode(): Node {
const node = new Map<string, Node | undefined>() as Node; const node = new Map<string, Node | undefined>() as Node;
node[WORDEND] = false; node[WORDEND] = false;
node[FAIL] = undefined; node[FAIL] = undefined;
return node; return node;
}; }
const createKeywordFilter = (keys: string[] | Set<string>) => { function createKeywordFilter(keys: string[] | Set<string>) {
const root = createNode(); const root = createNode();
// Create a trie with extra fields and information // Create a trie with extra fields and information
@ -82,6 +82,6 @@ const createKeywordFilter = (keys: string[] | Set<string>) => {
return false; return false;
}; };
}; }
export default createKeywordFilter; export default createKeywordFilter;

View File

@ -1,9 +1,9 @@
import type { Writable } from 'node:stream'; import type { Writable } from 'node:stream';
import { once } from 'node:events'; import { once } from 'node:events';
export const asyncWriteToStream = <T>(stream: Writable, chunk: T) => { export function asyncWriteToStream<T>(stream: Writable, chunk: T) {
const res = stream.write(chunk); const res = stream.write(chunk);
if (!res) { if (!res) {
return once(stream, 'drain'); // returns a promise only if needed return once(stream, 'drain'); // returns a promise only if needed
} }
}; }

View File

@ -2,11 +2,11 @@
export const pack = (a: number, b: number): number => (a << 16) | b; export const pack = (a: number, b: number): number => (a << 16) | b;
/** Unpacks two 16-bit integers from one 32-bit integer */ /** Unpacks two 16-bit integers from one 32-bit integer */
export const unpack = (value: number, arr: [a: number, b: number] = Array.from(new Array(2).keys()) as any): [a: number, b: number] => { export function unpack(value: number, arr: [a: number, b: number] = Array.from(new Array(2).keys()) as any): [a: number, b: number] {
arr[0] = (value >> 16) & 0xFFFF; arr[0] = (value >> 16) & 0xFFFF;
arr[1] = value & 0xFFFF; arr[1] = value & 0xFFFF;
return arr; return arr;
}; }
export const unpackFirst = (value: number): number => (value >> 16) & 0xFFFF; export const unpackFirst = (value: number): number => (value >> 16) & 0xFFFF;
export const unpackSecond = (value: number): number => value & 0xFFFF; export const unpackSecond = (value: number): number => value & 0xFFFF;

View File

@ -1,6 +1,6 @@
import process from 'node:process'; import process from 'node:process';
export const createCache = (namespace?: string, printStats = false) => { export function createCache(namespace?: string, printStats = false) {
const cache = new Map(); const cache = new Map();
let hit = 0; let hit = 0;
@ -30,4 +30,4 @@ export const createCache = (namespace?: string, printStats = false) => {
return value; return value;
} }
}; };
}; }

View File

@ -410,7 +410,7 @@ export const serializeArray = (arr: string[]) => fastStringArrayJoin(arr, separa
export const deserializeArray = (str: string) => str.split(separator); export const deserializeArray = (str: string) => str.split(separator);
export const getFileContentHash = (filename: string) => stringHash(fs.readFileSync(filename, 'utf-8')); export const getFileContentHash = (filename: string) => stringHash(fs.readFileSync(filename, 'utf-8'));
export const createCacheKey = (filename: string) => { export function createCacheKey(filename: string) {
const fileHash = getFileContentHash(filename); const fileHash = getFileContentHash(filename);
return (key: string) => key + '$' + fileHash + '$'; return (key: string) => key + '$' + fileHash + '$';
}; }

View File

@ -22,7 +22,7 @@ const mihomoBinaryUrl: Partial<Record<NodeJS.Platform, Partial<Record<NodeJS.Arc
} }
}; };
const ensureMihomoBinary = async () => { async function ensureMihomoBinary() {
await mkdirp(mihomoBinaryDir); await mkdirp(mihomoBinaryDir);
if (!fs.existsSync(mihomoBinaryPath)) { if (!fs.existsSync(mihomoBinaryPath)) {
const writeStream = fs.createWriteStream(mihomoBinaryPath); const writeStream = fs.createWriteStream(mihomoBinaryPath);
@ -47,9 +47,9 @@ const ensureMihomoBinary = async () => {
); );
} }
await fsp.chmod(mihomoBinaryPath, 0o755); await fsp.chmod(mihomoBinaryPath, 0o755);
}; }
export const convertClashMetaMrs = async (type: 'domain', format: 'text', input: string, output: string) => { export async function convertClashMetaMrs(type: 'domain', format: 'text', input: string, output: string) {
await ensureMihomoBinary(); await ensureMihomoBinary();
const { stderr } = await ezspawn(mihomoBinaryPath, ['convert-ruleset', type, format, input, output]); const { stderr } = await ezspawn(mihomoBinaryPath, ['convert-ruleset', type, format, input, output]);
@ -57,4 +57,4 @@ export const convertClashMetaMrs = async (type: 'domain', format: 'text', input:
if (stderr) { if (stderr) {
throw new Error(stderr); throw new Error(stderr);
} }
}; }

View File

@ -8,9 +8,9 @@ const createSource = async function *(input: string[]) {
} }
}; };
const test = async (a: string[], b: string[], expected: boolean) => { async function test(a: string[], b: string[], expected: boolean) {
expect((await fileEqual(a, createSource(b)))).to.eq(expected); expect((await fileEqual(a, createSource(b)))).to.eq(expected);
}; }
describe('fileEqual', () => { describe('fileEqual', () => {
it('same', () => test( it('same', () => test(

View File

@ -26,19 +26,21 @@ export class CustomNoETagFallbackError extends Error {
} }
} }
export const sleepWithAbort = (ms: number, signal: AbortSignal) => new Promise<void>((resolve, reject) => { export function sleepWithAbort(ms: number, signal: AbortSignal) {
if (signal.aborted) { return new Promise<void>((resolve, reject) => {
reject(signal.reason as Error); if (signal.aborted) {
return; reject(signal.reason as Error);
} return;
}
signal.addEventListener('abort', stop, { once: true }); signal.addEventListener('abort', stop, { once: true });
// eslint-disable-next-line sukka/prefer-timer-id -- node:timers/promises // eslint-disable-next-line sukka/prefer-timer-id -- node:timers/promises
setTimeout(ms, undefined, { ref: false }).then(resolve).catch(reject).finally(() => signal.removeEventListener('abort', stop)); setTimeout(ms, undefined, { ref: false }).then(resolve).catch(reject).finally(() => signal.removeEventListener('abort', stop));
function stop(this: AbortSignal) { reject(this.reason as Error); } function stop(this: AbortSignal) { reject(this.reason as Error); }
}); });
}
export async function fetchAssets(url: string, fallbackUrls: string[] | readonly string[]) { export async function fetchAssets(url: string, fallbackUrls: string[] | readonly string[]) {
const controller = new AbortController(); const controller = new AbortController();

View File

@ -95,26 +95,6 @@ function createFetchRetry($fetch: typeof fetch): FetchWithRetry {
return res; return res;
} }
} catch (err: unknown) { } catch (err: unknown) {
const mayBailError = (err: unknown) => {
if (typeof err === 'object' && err !== null && 'name' in err) {
if ((
err.name === 'AbortError'
|| ('digest' in err && err.digest === 'AbortError')
) && !retryOpts.retryOnAborted) {
console.log(picocolors.gray('[fetch abort]'), url);
return true;
}
if (err.name === 'Custom304NotModifiedError') {
return true;
}
if (err.name === 'CustomNoETagFallbackError') {
return true;
}
}
return !!(isClientError(err));
};
if (mayBailError(err)) { if (mayBailError(err)) {
return bail(err) as never; return bail(err) as never;
}; };
@ -140,6 +120,26 @@ function createFetchRetry($fetch: typeof fetch): FetchWithRetry {
throw newErr; throw newErr;
} }
}, retryOpts); }, retryOpts);
function mayBailError(err: unknown) {
if (typeof err === 'object' && err !== null && 'name' in err) {
if ((
err.name === 'AbortError'
|| ('digest' in err && err.digest === 'AbortError')
) && !retryOpts.retryOnAborted) {
console.log(picocolors.gray('[fetch abort]'), url);
return true;
}
if (err.name === 'Custom304NotModifiedError') {
return true;
}
if (err.name === 'CustomNoETagFallbackError') {
return true;
}
}
return !!(isClientError(err));
};
} catch (err) { } catch (err) {
if (err instanceof ResponseError) { if (err instanceof ResponseError) {
return err.res; return err.res;

View File

@ -9,19 +9,19 @@ import { processLine } from './process-line';
import { $fetch } from './make-fetch-happen'; import { $fetch } from './make-fetch-happen';
import type { NodeFetchResponse } from './make-fetch-happen'; import type { NodeFetchResponse } from './make-fetch-happen';
const getReadableStream = (file: string | FileHandle): ReadableStream => { function getReadableStream(file: string | FileHandle): ReadableStream {
if (typeof file === 'string') { if (typeof file === 'string') {
// return fs.openAsBlob(file).then(blob => blob.stream()) // return fs.openAsBlob(file).then(blob => blob.stream())
return Readable.toWeb(fs.createReadStream(file/* , { encoding: 'utf-8' } */)); return Readable.toWeb(fs.createReadStream(file/* , { encoding: 'utf-8' } */));
} }
return file.readableWebStream(); return file.readableWebStream();
}; }
// TODO: use FileHandle.readLine() // TODO: use FileHandle.readLine()
export const readFileByLine: ((file: string | FileHandle) => AsyncIterable<string>) = (file: string | FileHandle) => getReadableStream(file) export const readFileByLine: ((file: string | FileHandle) => AsyncIterable<string>) = (file: string | FileHandle) => getReadableStream(file)
.pipeThrough(new TextDecoderStream()) .pipeThrough(new TextDecoderStream())
.pipeThrough(new TextLineStream()); .pipeThrough(new TextLineStream());
const ensureResponseBody = <T extends Response | NodeFetchResponse>(resp: T): NonNullable<T['body']> => { function ensureResponseBody<T extends Response | NodeFetchResponse>(resp: T): NonNullable<T['body']> {
if (!resp.body) { if (!resp.body) {
throw new Error('Failed to fetch remote text'); throw new Error('Failed to fetch remote text');
} }
@ -29,7 +29,7 @@ const ensureResponseBody = <T extends Response | NodeFetchResponse>(resp: T): No
throw new Error('Body has already been consumed.'); throw new Error('Body has already been consumed.');
} }
return resp.body; return resp.body;
}; }
export const createReadlineInterfaceFromResponse: ((resp: Response | NodeFetchResponse) => AsyncIterable<string>) = (resp) => { export const createReadlineInterfaceFromResponse: ((resp: Response | NodeFetchResponse) => AsyncIterable<string>) = (resp) => {
const stream = ensureResponseBody(resp); const stream = ensureResponseBody(resp);

View File

@ -101,25 +101,27 @@ const lowKeywords = createKeywordFilter([
const cacheKey = createCacheKey(__filename); const cacheKey = createCacheKey(__filename);
export const getPhishingDomains = (parentSpan: Span) => parentSpan.traceChild('get phishing domains').traceAsyncFn(async (span) => { export function getPhishingDomains(parentSpan: Span) {
const domainArr = await span.traceChildAsync('download/parse/merge phishing domains', async (curSpan) => { return parentSpan.traceChild('get phishing domains').traceAsyncFn(async (span) => {
const domainArr: string[] = []; const domainArr = await span.traceChildAsync('download/parse/merge phishing domains', async (curSpan) => {
const domainArr: string[] = [];
(await Promise.all(PHISHING_DOMAIN_LISTS_EXTRA.map(entry => processDomainLists(curSpan, ...entry, cacheKey)))) (await Promise.all(PHISHING_DOMAIN_LISTS_EXTRA.map(entry => processDomainLists(curSpan, ...entry, cacheKey))))
.forEach(appendArrayInPlaceCurried(domainArr)); .forEach(appendArrayInPlaceCurried(domainArr));
(await Promise.all(PHISHING_HOSTS_EXTRA.map(entry => processHosts(curSpan, ...entry, cacheKey)))) (await Promise.all(PHISHING_HOSTS_EXTRA.map(entry => processHosts(curSpan, ...entry, cacheKey))))
.forEach(appendArrayInPlaceCurried(domainArr)); .forEach(appendArrayInPlaceCurried(domainArr));
return domainArr; return domainArr;
});
const cacheHash = span.traceChildSync('get hash', () => stringHash(fastStringArrayJoin(domainArr, '|')));
return span.traceChildAsync(
'process phishing domain set',
() => processPhihsingDomains(domainArr, cacheHash)
);
}); });
}
const cacheHash = span.traceChildSync('get hash', () => stringHash(fastStringArrayJoin(domainArr, '|')));
return span.traceChildAsync(
'process phishing domain set',
() => processPhihsingDomains(domainArr, cacheHash)
);
});
async function processPhihsingDomains(domainArr: string[], cacheHash = '') { async function processPhihsingDomains(domainArr: string[], cacheHash = '') {
return fsFetchCache.apply( return fsFetchCache.apply(

View File

@ -1,6 +1,6 @@
const notError = Symbol('notError'); const notError = Symbol('notError');
export const createMemoizedPromise = <T>(fn: () => Promise<T>, preload = true): () => Promise<T> => { export function createMemoizedPromise<T>(fn: () => Promise<T>, preload = true): () => Promise<T> {
let error: Error | typeof notError = notError; let error: Error | typeof notError = notError;
let promise: Promise<T> | null = preload let promise: Promise<T> | null = preload
@ -19,4 +19,4 @@ export const createMemoizedPromise = <T>(fn: () => Promise<T>, preload = true):
promise ??= fn(); promise ??= fn();
return promise; return promise;
}; };
}; }

View File

@ -5,7 +5,7 @@ import { OUTPUT_CLASH_DIR, OUTPUT_SINGBOX_DIR, OUTPUT_SURGE_DIR } from '../const
export const isTruthy = <T>(i: T | 0 | '' | false | null | undefined): i is T => !!i; export const isTruthy = <T>(i: T | 0 | '' | false | null | undefined): i is T => !!i;
export const fastStringArrayJoin = (arr: string[], sep: string) => { export function fastStringArrayJoin(arr: string[], sep: string) {
let result = ''; let result = '';
for (let i = 0, len = arr.length; i < len; i++) { for (let i = 0, len = arr.length; i < len; i++) {
if (i !== 0) { if (i !== 0) {
@ -14,7 +14,7 @@ export const fastStringArrayJoin = (arr: string[], sep: string) => {
result += arr[i]; result += arr[i];
} }
return result; return result;
}; }
interface Write { interface Write {
( (
@ -23,12 +23,12 @@ interface Write {
): Promise<unknown> ): Promise<unknown>
} }
export const mkdirp = (dir: string) => { export function mkdirp(dir: string) {
if (fs.existsSync(dir)) { if (fs.existsSync(dir)) {
return; return;
} }
return fsp.mkdir(dir, { recursive: true }); return fsp.mkdir(dir, { recursive: true });
}; }
export const writeFile: Write = async (destination: string, input, dir = dirname(destination)) => { export const writeFile: Write = async (destination: string, input, dir = dirname(destination)) => {
const p = mkdirp(dir); const p = mkdirp(dir);
@ -40,7 +40,7 @@ export const writeFile: Write = async (destination: string, input, dir = dirname
export const removeFiles = async (files: string[]) => Promise.all(files.map((file) => fsp.rm(file, { force: true }))); export const removeFiles = async (files: string[]) => Promise.all(files.map((file) => fsp.rm(file, { force: true })));
export const domainWildCardToRegex = (domain: string) => { export function domainWildCardToRegex(domain: string) {
let result = '^'; let result = '^';
for (let i = 0, len = domain.length; i < len; i++) { for (let i = 0, len = domain.length; i < len; i++) {
switch (domain[i]) { switch (domain[i]) {
@ -59,11 +59,11 @@ export const domainWildCardToRegex = (domain: string) => {
} }
result += '$'; result += '$';
return result; return result;
}; }
export const identity = <T>(x: T): T => x; export const identity = <T>(x: T): T => x;
export const appendArrayFromSet = <T>(dest: T[], source: Set<T> | Array<Set<T>>, transformer: (item: T) => T = identity) => { export function appendArrayFromSet<T>(dest: T[], source: Set<T> | Array<Set<T>>, transformer: (item: T) => T = identity) {
const casted = Array.isArray(source) ? source : [source]; const casted = Array.isArray(source) ? source : [source];
for (let i = 0, len = casted.length; i < len; i++) { for (let i = 0, len = casted.length; i < len; i++) {
const iterator = casted[i].values(); const iterator = casted[i].values();
@ -75,13 +75,15 @@ export const appendArrayFromSet = <T>(dest: T[], source: Set<T> | Array<Set<T>>,
} }
return dest; return dest;
}; }
export const output = (id: string, type: 'non_ip' | 'ip' | 'domainset') => [ export function output(id: string, type: 'non_ip' | 'ip' | 'domainset') {
path.join(OUTPUT_SURGE_DIR, type, id + '.conf'), return [
path.join(OUTPUT_CLASH_DIR, type, id + '.txt'), path.join(OUTPUT_SURGE_DIR, type, id + '.conf'),
path.join(OUTPUT_SINGBOX_DIR, type, id + '.json') path.join(OUTPUT_CLASH_DIR, type, id + '.txt'),
] as const; path.join(OUTPUT_SINGBOX_DIR, type, id + '.json')
] as const;
}
export function withBannerArray(title: string, description: string[] | readonly string[], date: Date, content: string[]) { export function withBannerArray(title: string, description: string[] | readonly string[], date: Date, content: string[]) {
return [ return [
@ -96,7 +98,7 @@ export function withBannerArray(title: string, description: string[] | readonly
]; ];
}; };
export const mergeHeaders = (headersA: RequestInit['headers'] | undefined, headersB: RequestInit['headers']) => { export function mergeHeaders(headersA: RequestInit['headers'] | undefined, headersB: RequestInit['headers']) {
if (headersA == null) { if (headersA == null) {
return headersB; return headersB;
} }
@ -116,9 +118,9 @@ export const mergeHeaders = (headersA: RequestInit['headers'] | undefined, heade
for (const key in headersB) { for (const key in headersB) {
if (Object.hasOwn(headersB, key)) { if (Object.hasOwn(headersB, key)) {
result.set(key, (headersB as Record<string, string>)[key]); result.set(key, (headersB)[key]);
} }
} }
return result; return result;
}; }

View File

@ -5,7 +5,7 @@ import { normalizeTldtsOpt } from '../constants/loose-tldts-opt';
type TldTsParsed = ReturnType<typeof tldts.parse>; type TldTsParsed = ReturnType<typeof tldts.parse>;
export const normalizeDomain = (domain: string, parsed: TldTsParsed | null = null) => { export function normalizeDomain(domain: string, parsed: TldTsParsed | null = null) {
if (domain.length === 0) return null; if (domain.length === 0) return null;
parsed ??= tldts.parse(domain, normalizeTldtsOpt); parsed ??= tldts.parse(domain, normalizeTldtsOpt);
@ -29,4 +29,4 @@ export const normalizeDomain = (domain: string, parsed: TldTsParsed | null = nul
} }
return h.length > 0 ? h : null; return h.length > 0 ? h : null;
}; }

View File

@ -3,19 +3,19 @@ import { parse as tldtsParse } from 'tldts';
import { $fetch } from './make-fetch-happen'; import { $fetch } from './make-fetch-happen';
import type { NodeFetchResponse } from './make-fetch-happen'; import type { NodeFetchResponse } from './make-fetch-happen';
const isDomainLoose = (domain: string): boolean => { function isDomainLoose(domain: string): boolean {
const { isIcann, isPrivate, isIp } = tldtsParse(domain); const { isIcann, isPrivate, isIp } = tldtsParse(domain);
return !!(!isIp && (isIcann || isPrivate)); return !!(!isIp && (isIcann || isPrivate));
}; }
export const extractDomainsFromFelixDnsmasq = (line: string): string | null => { export function extractDomainsFromFelixDnsmasq(line: string): string | null {
if (line.startsWith('server=/') && line.endsWith('/114.114.114.114')) { if (line.startsWith('server=/') && line.endsWith('/114.114.114.114')) {
return line.slice(8, -16); return line.slice(8, -16);
} }
return null; return null;
}; }
export const parseFelixDnsmasqFromResp = async (resp: Response | NodeFetchResponse): Promise<string[]> => { export async function parseFelixDnsmasqFromResp(resp: Response | NodeFetchResponse): Promise<string[]> {
const results: string[] = []; const results: string[] = [];
for await (const line of createReadlineInterfaceFromResponse(resp)) { for await (const line of createReadlineInterfaceFromResponse(resp)) {
@ -26,9 +26,9 @@ export const parseFelixDnsmasqFromResp = async (resp: Response | NodeFetchRespon
} }
return results; return results;
}; }
export const parseFelixDnsmasq = async (url: string): Promise<string[]> => { export async function parseFelixDnsmasq(url: string): Promise<string[]> {
const resp = await $fetch(url); const resp = await $fetch(url);
return parseFelixDnsmasqFromResp(resp); return parseFelixDnsmasqFromResp(resp);
}; }

View File

@ -15,7 +15,7 @@ const DEBUG_DOMAIN_TO_FIND: string | null = null; // example.com | null
let foundDebugDomain = false; let foundDebugDomain = false;
const temporaryBypass = typeof DEBUG_DOMAIN_TO_FIND === 'string'; const temporaryBypass = typeof DEBUG_DOMAIN_TO_FIND === 'string';
const domainListLineCb = (l: string, set: string[], includeAllSubDomain: boolean, meta: string) => { function domainListLineCb(l: string, set: string[], includeAllSubDomain: boolean, meta: string) {
let line = processLine(l); let line = processLine(l);
if (!line) return; if (!line) return;
line = line.toLowerCase(); line = line.toLowerCase();
@ -39,7 +39,7 @@ const domainListLineCb = (l: string, set: string[], includeAllSubDomain: boolean
} }
set.push(includeAllSubDomain ? `.${line}` : line); set.push(includeAllSubDomain ? `.${line}` : line);
}; }
export function processDomainLists( export function processDomainLists(
span: Span, span: Span,
@ -71,7 +71,7 @@ export function processDomainLists(
)); ));
} }
const hostsLineCb = (l: string, set: string[], includeAllSubDomain: boolean, meta: string) => { function hostsLineCb(l: string, set: string[], includeAllSubDomain: boolean, meta: string) {
const line = processLine(l); const line = processLine(l);
if (!line) { if (!line) {
return; return;
@ -91,7 +91,7 @@ const hostsLineCb = (l: string, set: string[], includeAllSubDomain: boolean, met
} }
set.push(includeAllSubDomain ? `.${domain}` : domain); set.push(includeAllSubDomain ? `.${domain}` : domain);
}; }
export function processHosts( export function processHosts(
span: Span, span: Span,

View File

@ -1,4 +1,4 @@
export const processLine = (line: string): string | null => { export function processLine(line: string): string | null {
if (!line) { if (!line) {
return null; return null;
} }
@ -22,9 +22,9 @@ export const processLine = (line: string): string | null => {
} }
return trimmed; return trimmed;
}; }
export const processLineFromReadline = async (rl: AsyncIterable<string>): Promise<string[]> => { export async function processLineFromReadline(rl: AsyncIterable<string>): Promise<string[]> {
const res: string[] = []; const res: string[] = [];
for await (const line of rl) { for await (const line of rl) {
const l: string | null = processLine(line); const l: string | null = processLine(line);
@ -33,4 +33,4 @@ export const processLineFromReadline = async (rl: AsyncIterable<string>): Promis
} }
} }
return res; return res;
}; }

View File

@ -125,6 +125,7 @@ export abstract class RuleOutput<TPreprocessed = unknown> {
} }
private async addFromDomainsetPromise(source: AsyncIterable<string> | Iterable<string> | string[]) { private async addFromDomainsetPromise(source: AsyncIterable<string> | Iterable<string> | string[]) {
// eslint-disable-next-line @typescript-eslint/await-thenable -- https://github.com/typescript-eslint/typescript-eslint/issues/10080
for await (const line of source) { for await (const line of source) {
if (line[0] === '.') { if (line[0] === '.') {
this.addDomainSuffix(line); this.addDomainSuffix(line);
@ -140,6 +141,7 @@ export abstract class RuleOutput<TPreprocessed = unknown> {
} }
private async addFromRulesetPromise(source: AsyncIterable<string> | Iterable<string>) { private async addFromRulesetPromise(source: AsyncIterable<string> | Iterable<string>) {
// eslint-disable-next-line @typescript-eslint/await-thenable -- https://github.com/typescript-eslint/typescript-eslint/issues/10080
for await (const line of source) { for await (const line of source) {
const splitted = line.split(','); const splitted = line.split(',');
const type = splitted[0]; const type = splitted[0];
@ -314,7 +316,7 @@ export abstract class RuleOutput<TPreprocessed = unknown> {
abstract mitmSgmodule?(): string[] | null; abstract mitmSgmodule?(): string[] | null;
} }
export const fileEqual = async (linesA: string[], source: AsyncIterable<string>): Promise<boolean> => { export async function fileEqual(linesA: string[], source: AsyncIterable<string>): Promise<boolean> {
if (linesA.length === 0) { if (linesA.length === 0) {
return false; return false;
} }
@ -350,7 +352,7 @@ export const fileEqual = async (linesA: string[], source: AsyncIterable<string>)
// The file becomes larger // The file becomes larger
return !(index < linesA.length - 1); return !(index < linesA.length - 1);
}; }
export async function compareAndWriteFile(span: Span, linesA: string[], filePath: string) { export async function compareAndWriteFile(span: Span, linesA: string[], filePath: string) {
let isEqual = true; let isEqual = true;

View File

@ -2,10 +2,10 @@ import { isProbablyIpv4, isProbablyIpv6 } from './is-fast-ip';
const unsupported = Symbol('unsupported'); const unsupported = Symbol('unsupported');
const toNumberTuple = <T extends string>(key: T, value: string): [T, number] | null => { function toNumberTuple<T extends string>(key: T, value: string): [T, number] | null {
const tmp = Number(value); const tmp = Number(value);
return Number.isNaN(tmp) ? null : [key, tmp]; return Number.isNaN(tmp) ? null : [key, tmp];
}; }
// https://sing-box.sagernet.org/configuration/rule-set/source-format/ // https://sing-box.sagernet.org/configuration/rule-set/source-format/
export const PROCESSOR: Record<string, ((raw: string, type: string, value: string) => [key: keyof SingboxHeadlessRule, value: Required<SingboxHeadlessRule>[keyof SingboxHeadlessRule][number]] | null) | typeof unsupported> = { export const PROCESSOR: Record<string, ((raw: string, type: string, value: string) => [key: keyof SingboxHeadlessRule, value: Required<SingboxHeadlessRule>[keyof SingboxHeadlessRule][number]] | null) | typeof unsupported> = {

View File

@ -4,12 +4,12 @@
import * as tldts from 'tldts-experimental'; import * as tldts from 'tldts-experimental';
import { looseTldtsOpt } from '../constants/loose-tldts-opt'; import { looseTldtsOpt } from '../constants/loose-tldts-opt';
export const compare = (a: string, b: string) => { export function compare(a: string, b: string) {
if (a === b) return 0; if (a === b) return 0;
return (a.length - b.length) || a.localeCompare(b); return (a.length - b.length) || a.localeCompare(b);
}; }
export const buildParseDomainMap = (inputs: string[]) => { export function buildParseDomainMap(inputs: string[]) {
const domainMap = new Map<string, string>(); const domainMap = new Map<string, string>();
const subdomainMap = new Map<string, string>(); const subdomainMap = new Map<string, string>();
@ -24,13 +24,11 @@ export const buildParseDomainMap = (inputs: string[]) => {
} }
return { domainMap, subdomainMap }; return { domainMap, subdomainMap };
}; }
export const sortDomains = ( export function sortDomains(inputs: string[],
inputs: string[],
domainMap?: Map<string, string> | null, domainMap?: Map<string, string> | null,
subdomainMap?: Map<string, string> | null subdomainMap?: Map<string, string> | null) {
) => {
if (!domainMap || !subdomainMap) { if (!domainMap || !subdomainMap) {
const { domainMap: dm, subdomainMap: sm } = buildParseDomainMap(inputs); const { domainMap: dm, subdomainMap: sm } = buildParseDomainMap(inputs);
domainMap = dm; domainMap = dm;
@ -58,4 +56,4 @@ export const sortDomains = (
}; };
return inputs.sort(sorter); return inputs.sort(sorter);
}; }

View File

@ -7,7 +7,7 @@
* Simplified, optimized and add modified for 52 bit, which provides a larger hash space * Simplified, optimized and add modified for 52 bit, which provides a larger hash space
* and still making use of Javascript's 53-bit integer space. * and still making use of Javascript's 53-bit integer space.
*/ */
export const fnv1a52 = (str: string) => { export function fnv1a52(str: string) {
const len = str.length; const len = str.length;
let i = 0, let i = 0,
t0 = 0, t0 = 0,
@ -41,6 +41,6 @@ export const fnv1a52 = (str: string) => {
+ v1 * 65536 + v1 * 65536
+ (v0 ^ (v3 >> 4)) + (v0 ^ (v3 >> 4))
); );
}; }
export const stringHash = (payload: string) => fnv1a52(payload).toString(36) + payload.length.toString(36); export const stringHash = (payload: string) => fnv1a52(payload).toString(36) + payload.length.toString(36);

View File

@ -19,7 +19,7 @@ export type TreeTypeArray = TreeType[];
type VoidOrVoidArray = void | VoidOrVoidArray[]; type VoidOrVoidArray = void | VoidOrVoidArray[];
export const treeDir = async (rootPath: string): Promise<TreeTypeArray> => { export async function treeDir(rootPath: string): Promise<TreeTypeArray> {
const tree: TreeTypeArray = []; const tree: TreeTypeArray = [];
const walk = async (dir: string, node: TreeTypeArray, dirRelativeToRoot = ''): Promise<VoidOrVoidArray> => { const walk = async (dir: string, node: TreeTypeArray, dirRelativeToRoot = ''): Promise<VoidOrVoidArray> => {
@ -60,4 +60,4 @@ export const treeDir = async (rootPath: string): Promise<TreeTypeArray> => {
await walk(rootPath, tree); await walk(rootPath, tree);
return tree; return tree;
}; }

View File

@ -13,10 +13,8 @@ type TrieNode<Meta = any> = [
Meta /** meta */ Meta /** meta */
]; ];
const deepTrieNodeToJSON = ( function deepTrieNodeToJSON(node: TrieNode,
node: TrieNode, unpackMeta: ((meta?: any) => string) | undefined) {
unpackMeta: ((meta?: any) => string) | undefined
) => {
const obj: Record<string, any> = {}; const obj: Record<string, any> = {};
if (node[0]) { if (node[0]) {
obj['[start]'] = node[0]; obj['[start]'] = node[0];
@ -32,11 +30,11 @@ const deepTrieNodeToJSON = (
obj[key] = deepTrieNodeToJSON(value, unpackMeta); obj[key] = deepTrieNodeToJSON(value, unpackMeta);
}); });
return obj; return obj;
}; }
const createNode = <Meta = any>(parent: TrieNode | null = null): TrieNode => [false, parent, new Map<string, TrieNode>(), null] as TrieNode<Meta>; const createNode = <Meta = any>(parent: TrieNode | null = null): TrieNode => [false, parent, new Map<string, TrieNode>(), null] as TrieNode<Meta>;
export const hostnameToTokens = (hostname: string): string[] => { export function hostnameToTokens(hostname: string): string[] {
const tokens = hostname.split('.'); const tokens = hostname.split('.');
const results: string[] = []; const results: string[] = [];
let token = ''; let token = '';
@ -51,9 +49,9 @@ export const hostnameToTokens = (hostname: string): string[] => {
} }
} }
return results; return results;
}; }
const walkHostnameTokens = (hostname: string, onToken: (token: string) => boolean | null): boolean | null => { function walkHostnameTokens(hostname: string, onToken: (token: string) => boolean | null): boolean | null {
const tokens = hostname.split('.'); const tokens = hostname.split('.');
let token = ''; let token = '';
@ -78,7 +76,7 @@ const walkHostnameTokens = (hostname: string, onToken: (token: string) => boolea
} }
return false; return false;
}; }
interface FindSingleChildLeafResult<Meta> { interface FindSingleChildLeafResult<Meta> {
node: TrieNode<Meta>, node: TrieNode<Meta>,

View File

@ -34,7 +34,7 @@ export interface Span {
readonly traceResult: TraceResult readonly traceResult: TraceResult
} }
export const createSpan = (name: string, parentTraceResult?: TraceResult): Span => { export function createSpan(name: string, parentTraceResult?: TraceResult): Span {
const start = performance.now(); const start = performance.now();
let curTraceResult: TraceResult; let curTraceResult: TraceResult;
@ -93,27 +93,29 @@ export const createSpan = (name: string, parentTraceResult?: TraceResult): Span
// eslint-disable-next-line sukka/no-redundant-variable -- self reference // eslint-disable-next-line sukka/no-redundant-variable -- self reference
return span; return span;
}; }
export const dummySpan = createSpan(''); export const dummySpan = createSpan('');
export const task = (importMetaMain: boolean, importMetaPath: string) => <T>(fn: (span: Span) => Promise<T>, customName?: string) => { export function task(importMetaMain: boolean, importMetaPath: string) {
const taskName = customName ?? basename(importMetaPath, extname(importMetaPath)); return <T>(fn: (span: Span) => Promise<T>, customName?: string) => {
const taskName = customName ?? basename(importMetaPath, extname(importMetaPath));
const dummySpan = createSpan(taskName); const dummySpan = createSpan(taskName);
if (importMetaMain) { if (importMetaMain) {
fn(dummySpan).finally(() => { fn(dummySpan).finally(() => {
console.log(wtf.dump()); console.log(wtf.dump());
}); });
}
return async (span?: Span) => {
if (span) {
return span.traceChildAsync(taskName, fn);
} }
return fn(dummySpan);
return async (span?: Span) => {
if (span) {
return span.traceChildAsync(taskName, fn);
}
return fn(dummySpan);
};
}; };
}; }
// const isSpan = (obj: any): obj is Span => { // const isSpan = (obj: any): obj is Span => {
// return typeof obj === 'object' && obj && spanTag in obj; // return typeof obj === 'object' && obj && spanTag in obj;
@ -128,10 +130,10 @@ export const task = (importMetaMain: boolean, importMetaPath: string) => <T>(fn:
// }; // };
// }; // };
export const printTraceResult = (traceResult: TraceResult = rootTraceResult) => { export function printTraceResult(traceResult: TraceResult = rootTraceResult) {
printStats(traceResult.children); printStats(traceResult.children);
printTree(traceResult, node => `${node.name} ${picocolors.bold(`${(node.end - node.start).toFixed(3)}ms`)}`); printTree(traceResult, node => `${node.name} ${picocolors.bold(`${(node.end - node.start).toFixed(3)}ms`)}`);
}; }
function printTree(initialTree: TraceResult, printNode: (node: TraceResult, branch: string) => string) { function printTree(initialTree: TraceResult, printNode: (node: TraceResult, branch: string) => string) {
function printBranch(tree: TraceResult, branch: string, isGraphHead: boolean, isChildOfLastBranch: boolean) { function printBranch(tree: TraceResult, branch: string, isGraphHead: boolean, isChildOfLastBranch: boolean) {

View File

@ -7,7 +7,7 @@ import { parseFelixDnsmasq } from './lib/parse-dnsmasq';
import { SOURCE_DIR } from './constants/dir'; import { SOURCE_DIR } from './constants/dir';
import { $fetch } from './lib/make-fetch-happen'; import { $fetch } from './lib/make-fetch-happen';
export const parseDomesticList = async () => { export async function parseDomesticList() {
const trie = createTrie(await parseFelixDnsmasq('https://raw.githubusercontent.com/felixonmars/dnsmasq-china-list/master/accelerated-domains.china.conf')); const trie = createTrie(await parseFelixDnsmasq('https://raw.githubusercontent.com/felixonmars/dnsmasq-china-list/master/accelerated-domains.china.conf'));
const top5000 = new Set<string>(); const top5000 = new Set<string>();
@ -60,4 +60,4 @@ export const parseDomesticList = async () => {
// ]); // ]);
console.log(notIncludedDomestic.size, notIncludedDomestic); console.log(notIncludedDomestic.size, notIncludedDomestic);
}; }

View File

@ -8,7 +8,7 @@ import path from 'node:path';
import { SOURCE_DIR } from './constants/dir'; import { SOURCE_DIR } from './constants/dir';
import { $fetch } from './lib/make-fetch-happen'; import { $fetch } from './lib/make-fetch-happen';
export const parseGfwList = async () => { export async function parseGfwList() {
const whiteSet = new Set<string>(); const whiteSet = new Set<string>();
const blackSet = new Set<string>(); const blackSet = new Set<string>();
@ -120,4 +120,4 @@ export const parseGfwList = async () => {
trie, trie,
top500Gfwed top500Gfwed
] as const; ] as const;
}; }