mirror of
https://github.com/SukkaW/Surge.git
synced 2026-04-30 01:46:57 +08:00
Refactor: use jest-worker
This commit is contained in:
@@ -1,12 +1,12 @@
|
|||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { readFileIntoProcessedArray } from './lib/fetch-text-by-line';
|
import { readFileIntoProcessedArray, fetchRemoteTextByLine } from './lib/fetch-text-by-line';
|
||||||
import { task } from './trace';
|
import { task } from './trace';
|
||||||
import { SHARED_DESCRIPTION } from './constants/description';
|
import { SHARED_DESCRIPTION } from './constants/description';
|
||||||
import { appendArrayInPlace } from 'foxts/append-array-in-place';
|
import { appendArrayInPlace } from 'foxts/append-array-in-place';
|
||||||
import { SOURCE_DIR } from './constants/dir';
|
import { SOURCE_DIR } from './constants/dir';
|
||||||
import { DomainsetOutput } from './lib/rules/domainset';
|
import { DomainsetOutput } from './lib/rules/domainset';
|
||||||
import { CRASHLYTICS_WHITELIST } from './constants/reject-data-source';
|
import { CRASHLYTICS_WHITELIST } from './constants/reject-data-source';
|
||||||
import Worktank from 'worktank';
|
import { HostnameTrie } from './lib/trie';
|
||||||
import { $$fetch } from './lib/fetch-retry';
|
import { $$fetch } from './lib/fetch-retry';
|
||||||
import { fastUri } from 'fast-uri';
|
import { fastUri } from 'fast-uri';
|
||||||
|
|
||||||
@@ -14,25 +14,17 @@ const cdnDomainSetPromise = readFileIntoProcessedArray(path.join(SOURCE_DIR, 'do
|
|||||||
const downloadDomainSetPromise = readFileIntoProcessedArray(path.join(SOURCE_DIR, 'domainset/download.conf'));
|
const downloadDomainSetPromise = readFileIntoProcessedArray(path.join(SOURCE_DIR, 'domainset/download.conf'));
|
||||||
const steamDomainSetPromise = readFileIntoProcessedArray(path.join(SOURCE_DIR, 'domainset/game-download.conf'));
|
const steamDomainSetPromise = readFileIntoProcessedArray(path.join(SOURCE_DIR, 'domainset/game-download.conf'));
|
||||||
|
|
||||||
const pool = new Worktank({
|
export const buildCdnDownloadConf = task(require.main === module, __filename)(async (span) => {
|
||||||
pool: {
|
const [
|
||||||
name: 'extract-s3-from-publicssuffix',
|
S3OSSDomains,
|
||||||
size: 1 // The number of workers to keep in the pool, if more workers are needed they will be spawned up to this limit
|
IPFSDomains,
|
||||||
},
|
cdnDomainsList,
|
||||||
worker: {
|
downloadDomainSet,
|
||||||
autoAbort: 10000,
|
steamDomainSet
|
||||||
autoTerminate: 20000, // The interval of milliseconds at which to check if the pool can be automatically terminated, to free up resources, workers will be spawned up again if needed
|
] = await Promise.all([
|
||||||
autoInstantiate: true,
|
span.traceChildAsync(
|
||||||
methods: {
|
'download public suffix list for s3',
|
||||||
// eslint-disable-next-line object-shorthand -- workertank
|
async () => {
|
||||||
getS3OSSDomains: async function (__filename: string): Promise<string[]> {
|
|
||||||
// TODO: createRequire is a temporary workaround for https://github.com/nodejs/node/issues/51956
|
|
||||||
const { default: module } = await import('node:module');
|
|
||||||
const __require = module.createRequire(__filename);
|
|
||||||
|
|
||||||
const { HostnameTrie } = __require('./lib/trie') as typeof import('./lib/trie');
|
|
||||||
const { fetchRemoteTextByLine } = __require('./lib/fetch-text-by-line') as typeof import('./lib/fetch-text-by-line');
|
|
||||||
|
|
||||||
const trie = new HostnameTrie();
|
const trie = new HostnameTrie();
|
||||||
|
|
||||||
for await (const line of await fetchRemoteTextByLine('https://publicsuffix.org/list/public_suffix_list.dat', true)) {
|
for await (const line of await fetchRemoteTextByLine('https://publicsuffix.org/list/public_suffix_list.dat', true)) {
|
||||||
@@ -70,24 +62,6 @@ const pool = new Worktank({
|
|||||||
|
|
||||||
return S3OSSDomains;
|
return S3OSSDomains;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export const buildCdnDownloadConf = task(require.main === module, __filename)(async (span) => {
|
|
||||||
const [
|
|
||||||
S3OSSDomains,
|
|
||||||
IPFSDomains,
|
|
||||||
cdnDomainsList,
|
|
||||||
downloadDomainSet,
|
|
||||||
steamDomainSet
|
|
||||||
] = await Promise.all([
|
|
||||||
span.traceChildAsync(
|
|
||||||
'download public suffix list for s3',
|
|
||||||
() => pool.exec(
|
|
||||||
'getS3OSSDomains',
|
|
||||||
[__filename]
|
|
||||||
).finally(() => pool.terminate())
|
|
||||||
),
|
),
|
||||||
span.traceChildAsync(
|
span.traceChildAsync(
|
||||||
'load public ipfs gateway list',
|
'load public ipfs gateway list',
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
import { task } from './trace';
|
|
||||||
import { SHARED_DESCRIPTION } from './constants/description';
|
|
||||||
import { RulesetOutput } from './lib/rules/ruleset';
|
|
||||||
import Worktank from 'worktank';
|
|
||||||
import { RULES } from './constants/microsoft-cdn';
|
|
||||||
|
|
||||||
const pool = new Worktank({
|
|
||||||
pool: {
|
|
||||||
name: 'get-microsoft-cdn',
|
|
||||||
size: 1 // The number of workers to keep in the pool, if more workers are needed they will be spawned up to this limit
|
|
||||||
},
|
|
||||||
worker: {
|
|
||||||
autoAbort: 10000,
|
|
||||||
autoTerminate: 30000, // The interval of milliseconds at which to check if the pool can be automatically terminated, to free up resources, workers will be spawned up again if needed
|
|
||||||
autoInstantiate: true,
|
|
||||||
methods: {
|
|
||||||
// eslint-disable-next-line object-shorthand -- workertank
|
|
||||||
getMicrosoftCdnRuleset: async function (__filename: string): Promise<[domains: string[], domainSuffixes: string[]]> {
|
|
||||||
// TODO: createRequire is a temporary workaround for https://github.com/nodejs/node/issues/51956
|
|
||||||
const { default: module } = await import('node:module');
|
|
||||||
const __require = module.createRequire(__filename);
|
|
||||||
|
|
||||||
const { HostnameSmolTrie } = __require('./lib/trie');
|
|
||||||
const { PROBE_DOMAINS, DOMAINS, DOMAIN_SUFFIXES, BLACKLIST } = __require('./constants/microsoft-cdn') as typeof import('./constants/microsoft-cdn');
|
|
||||||
const { fetchRemoteTextByLine } = __require('./lib/fetch-text-by-line') as typeof import('./lib/fetch-text-by-line');
|
|
||||||
const { appendArrayInPlace } = __require('foxts/append-array-in-place') as typeof import('foxts/append-array-in-place');
|
|
||||||
const { extractDomainsFromFelixDnsmasq } = __require('./lib/parse-dnsmasq') as typeof import('./lib/parse-dnsmasq');
|
|
||||||
|
|
||||||
const trie = new HostnameSmolTrie();
|
|
||||||
|
|
||||||
for await (const line of await fetchRemoteTextByLine('https://raw.githubusercontent.com/felixonmars/dnsmasq-china-list/master/accelerated-domains.china.conf')) {
|
|
||||||
const domain = extractDomainsFromFelixDnsmasq(line);
|
|
||||||
if (domain) {
|
|
||||||
trie.add(domain);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove blacklist domain from trie, to prevent them from being included in the later dump
|
|
||||||
BLACKLIST.forEach(black => trie.whitelist(black));
|
|
||||||
|
|
||||||
const domains: string[] = DOMAINS;
|
|
||||||
const domainSuffixes = appendArrayInPlace(PROBE_DOMAINS.flatMap(domain => trie.find(domain)), DOMAIN_SUFFIXES);
|
|
||||||
|
|
||||||
return [domains, domainSuffixes] as const;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const getMicrosoftCdnRulesetPromise = pool.exec(
|
|
||||||
'getMicrosoftCdnRuleset',
|
|
||||||
[__filename]
|
|
||||||
).finally(() => pool.terminate());
|
|
||||||
|
|
||||||
export const buildMicrosoftCdn = task(require.main === module, __filename)(async (span) => {
|
|
||||||
const [domains, domainSuffixes] = await span.traceChildPromise('get microsoft cdn domains', getMicrosoftCdnRulesetPromise);
|
|
||||||
|
|
||||||
return new RulesetOutput(span, 'microsoft_cdn', 'non_ip')
|
|
||||||
.withTitle('Sukka\'s Ruleset - Microsoft CDN')
|
|
||||||
.appendDescription(SHARED_DESCRIPTION)
|
|
||||||
.appendDescription(
|
|
||||||
'',
|
|
||||||
'This file contains Microsoft\'s domains using their China mainland CDN servers.'
|
|
||||||
)
|
|
||||||
.addFromRuleset(RULES)
|
|
||||||
.appendDataSource('https://github.com/felixonmars/dnsmasq-china-list')
|
|
||||||
.bulkAddDomain(domains)
|
|
||||||
.bulkAddDomainSuffix(domainSuffixes)
|
|
||||||
.write();
|
|
||||||
});
|
|
||||||
42
Build/build-microsoft-cdn.worker.ts
Normal file
42
Build/build-microsoft-cdn.worker.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { task } from './trace';
|
||||||
|
import { SHARED_DESCRIPTION } from './constants/description';
|
||||||
|
import { RulesetOutput } from './lib/rules/ruleset';
|
||||||
|
import { RULES, PROBE_DOMAINS, DOMAINS, DOMAIN_SUFFIXES, BLACKLIST } from './constants/microsoft-cdn';
|
||||||
|
import { HostnameSmolTrie } from './lib/trie';
|
||||||
|
import { fetchRemoteTextByLine } from './lib/fetch-text-by-line';
|
||||||
|
import { appendArrayInPlace } from 'foxts/append-array-in-place';
|
||||||
|
import { extractDomainsFromFelixDnsmasq } from './lib/parse-dnsmasq';
|
||||||
|
|
||||||
|
export const buildMicrosoftCdn = task(require.main === module, __filename)(async (span) => {
|
||||||
|
const [domains, domainSuffixes] = await span.traceChildAsync('get microsoft cdn domains', async () => {
|
||||||
|
const trie = new HostnameSmolTrie();
|
||||||
|
|
||||||
|
for await (const line of await fetchRemoteTextByLine('https://raw.githubusercontent.com/felixonmars/dnsmasq-china-list/master/accelerated-domains.china.conf')) {
|
||||||
|
const domain = extractDomainsFromFelixDnsmasq(line);
|
||||||
|
if (domain) {
|
||||||
|
trie.add(domain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove blacklist domain from trie, to prevent them from being included in the later dump
|
||||||
|
BLACKLIST.forEach(black => trie.whitelist(black));
|
||||||
|
|
||||||
|
const domains: string[] = DOMAINS;
|
||||||
|
const domainSuffixes = appendArrayInPlace(PROBE_DOMAINS.flatMap(domain => trie.find(domain)), DOMAIN_SUFFIXES);
|
||||||
|
|
||||||
|
return [domains, domainSuffixes] as [string[], string[]];
|
||||||
|
});
|
||||||
|
|
||||||
|
return new RulesetOutput(span, 'microsoft_cdn', 'non_ip')
|
||||||
|
.withTitle('Sukka\'s Ruleset - Microsoft CDN')
|
||||||
|
.appendDescription(SHARED_DESCRIPTION)
|
||||||
|
.appendDescription(
|
||||||
|
'',
|
||||||
|
'This file contains Microsoft\'s domains using their China mainland CDN servers.'
|
||||||
|
)
|
||||||
|
.addFromRuleset(RULES)
|
||||||
|
.appendDataSource('https://github.com/felixonmars/dnsmasq-china-list')
|
||||||
|
.bulkAddDomain(domains)
|
||||||
|
.bulkAddDomainSuffix(domainSuffixes)
|
||||||
|
.write();
|
||||||
|
});
|
||||||
@@ -18,7 +18,7 @@ import { addArrayElementsToSet } from 'foxts/add-array-elements-to-set';
|
|||||||
import { OUTPUT_INTERNAL_DIR, SOURCE_DIR } from './constants/dir';
|
import { OUTPUT_INTERNAL_DIR, SOURCE_DIR } from './constants/dir';
|
||||||
import { DomainsetOutput, AdGuardHomeOutput } from './lib/rules/domainset';
|
import { DomainsetOutput, AdGuardHomeOutput } from './lib/rules/domainset';
|
||||||
import { foundDebugDomain } from './lib/parse-filter/shared';
|
import { foundDebugDomain } from './lib/parse-filter/shared';
|
||||||
import { getPhishingDomains } from './lib/get-phishing-domains';
|
import { createWorker } from './lib/worker';
|
||||||
import type { MaybePromise } from './lib/misc';
|
import type { MaybePromise } from './lib/misc';
|
||||||
import { RulesetOutput } from './lib/rules/ruleset';
|
import { RulesetOutput } from './lib/rules/ruleset';
|
||||||
import { fetchAssets } from './lib/fetch-assets';
|
import { fetchAssets } from './lib/fetch-assets';
|
||||||
@@ -39,6 +39,9 @@ const adguardFiltersExtraDownloads = ADGUARD_FILTERS_EXTRA.map(entry => processF
|
|||||||
const adguardFiltersWhitelistsDownloads = ADGUARD_FILTERS_WHITELIST.map(entry => processFilterRulesWithPreload(...entry));
|
const adguardFiltersWhitelistsDownloads = ADGUARD_FILTERS_WHITELIST.map(entry => processFilterRulesWithPreload(...entry));
|
||||||
|
|
||||||
export const buildRejectDomainSet = task(require.main === module, __filename)(async (span) => {
|
export const buildRejectDomainSet = task(require.main === module, __filename)(async (span) => {
|
||||||
|
const phishingWorker = createWorker<typeof import('./lib/get-phishing-domains')>(
|
||||||
|
require.resolve('./lib/get-phishing-domains')
|
||||||
|
)(['getPhishingDomains']);
|
||||||
const rejectDomainsetOutput = new DomainsetOutput(span, 'reject')
|
const rejectDomainsetOutput = new DomainsetOutput(span, 'reject')
|
||||||
.withTitle('Sukka\'s Ruleset - Reject Base')
|
.withTitle('Sukka\'s Ruleset - Reject Base')
|
||||||
.appendDescription(
|
.appendDescription(
|
||||||
@@ -126,7 +129,9 @@ export const buildRejectDomainSet = task(require.main === module, __filename)(as
|
|||||||
arrayPushNonNullish(promises, domainListsDownloads.map(task => task(childSpan).then(appendArrayToRejectOutput)));
|
arrayPushNonNullish(promises, domainListsDownloads.map(task => task(childSpan).then(appendArrayToRejectOutput)));
|
||||||
arrayPushNonNullish(promises, domainListsExtraDownloads.map(task => task(childSpan).then(appendArrayToRejectExtraOutput)));
|
arrayPushNonNullish(promises, domainListsExtraDownloads.map(task => task(childSpan).then(appendArrayToRejectExtraOutput)));
|
||||||
|
|
||||||
rejectPhisingDomainsetOutput.addFromDomainset(getPhishingDomains(childSpan));
|
rejectPhisingDomainsetOutput.addFromDomainset(
|
||||||
|
span.traceChildPromise('get phishing domains', phishingWorker.getPhishingDomains())
|
||||||
|
);
|
||||||
|
|
||||||
arrayPushNonNullish(
|
arrayPushNonNullish(
|
||||||
promises,
|
promises,
|
||||||
@@ -253,6 +258,7 @@ export const buildRejectDomainSet = task(require.main === module, __filename)(as
|
|||||||
rejectNonIpRulesetOutput.whitelistKeyword(keyword);
|
rejectNonIpRulesetOutput.whitelistKeyword(keyword);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deduplicate reject_extra and reject_phishing from the base reject domainset
|
||||||
rejectDomainsetOutput.domainTrie.dump(arg => {
|
rejectDomainsetOutput.domainTrie.dump(arg => {
|
||||||
rejectExtraDomainsetOutput.whitelistDomain(arg);
|
rejectExtraDomainsetOutput.whitelistDomain(arg);
|
||||||
rejectPhisingDomainsetOutput.whitelistDomain(arg);
|
rejectPhisingDomainsetOutput.whitelistDomain(arg);
|
||||||
@@ -295,4 +301,6 @@ export const buildRejectDomainSet = task(require.main === module, __filename)(as
|
|||||||
await myRejectOutputAdGuardHome
|
await myRejectOutputAdGuardHome
|
||||||
.addFromRuleset(readFileIntoProcessedArray(path.join(SOURCE_DIR, 'non_ip/my_reject.conf')))
|
.addFromRuleset(readFileIntoProcessedArray(path.join(SOURCE_DIR, 'non_ip/my_reject.conf')))
|
||||||
.write();
|
.write();
|
||||||
|
|
||||||
|
await phishingWorker.end();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,10 +2,137 @@
|
|||||||
import { task } from './trace';
|
import { task } from './trace';
|
||||||
import { SHARED_DESCRIPTION } from './constants/description';
|
import { SHARED_DESCRIPTION } from './constants/description';
|
||||||
import { RulesetOutput } from './lib/rules/ruleset';
|
import { RulesetOutput } from './lib/rules/ruleset';
|
||||||
import { getTelegramCIDRPromise } from './lib/get-telegram-backup-ip';
|
import { getTelegramBackupIPFromBase64 } from './lib/get-telegram-backup-ip';
|
||||||
|
import picocolors from 'picocolors';
|
||||||
|
import { $$fetch } from './lib/fetch-retry';
|
||||||
|
import dns from 'node:dns/promises';
|
||||||
|
import { createReadlineInterfaceFromResponse } from './lib/fetch-text-by-line';
|
||||||
|
import { fastIpVersion } from 'foxts/fast-ip-version';
|
||||||
|
import { fastStringArrayJoin } from 'foxts/fast-string-array-join';
|
||||||
|
|
||||||
export const buildTelegramCIDR = task(require.main === module, __filename)(async (span) => {
|
export const buildTelegramCIDR = task(require.main === module, __filename)(async (span) => {
|
||||||
const { timestamp, ipcidr, ipcidr6 } = await span.traceChildAsync('get telegram cidr', getTelegramCIDRPromise);
|
const { timestamp, ipcidr, ipcidr6 } = await span.traceChildAsync('get telegram cidr', async () => {
|
||||||
|
const resp = await $$fetch('https://core.telegram.org/resources/cidr.txt');
|
||||||
|
const lastModified = resp.headers.get('last-modified');
|
||||||
|
const date = lastModified ? new Date(lastModified) : new Date();
|
||||||
|
|
||||||
|
const ipcidr: string[] = [
|
||||||
|
// Unused secret Telegram backup CIDR, announced by AS62041
|
||||||
|
'95.161.64.0/20'
|
||||||
|
];
|
||||||
|
const ipcidr6: string[] = [];
|
||||||
|
|
||||||
|
for await (const cidr of createReadlineInterfaceFromResponse(resp, true)) {
|
||||||
|
const v = fastIpVersion(cidr);
|
||||||
|
if (v === 4) {
|
||||||
|
ipcidr.push(cidr);
|
||||||
|
} else if (v === 6) {
|
||||||
|
ipcidr6.push(cidr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const backupIPs = new Set<string>();
|
||||||
|
|
||||||
|
// https://github.com/tdlib/td/blob/master/td/telegram/ConfigManager.cpp
|
||||||
|
|
||||||
|
const resolvers = ['8.8.8.8', '1.0.0.1'].map((ip) => {
|
||||||
|
const resolver = new dns.Resolver();
|
||||||
|
resolver.setServers([ip]);
|
||||||
|
return Object.assign(resolver, { server: ip });
|
||||||
|
});
|
||||||
|
|
||||||
|
// Backup IP Source 1 (DNS)
|
||||||
|
await Promise.all(resolvers.flatMap((resolver) => [
|
||||||
|
'apv3.stel.com', // prod
|
||||||
|
'tapv3.stel.com' // test
|
||||||
|
].map(async (domain) => {
|
||||||
|
try {
|
||||||
|
// tapv3.stel.com was for testing server
|
||||||
|
const resp = await resolver.resolveTxt(domain);
|
||||||
|
const strings = resp.map(r => fastStringArrayJoin(r, '')); // flatten
|
||||||
|
if (strings.length !== 2) {
|
||||||
|
throw new TypeError(`Unexpected TXT record count: ${strings.length}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const str = strings[0].length > strings[1].length
|
||||||
|
? strings[0] + strings[1]
|
||||||
|
: strings[1] + strings[0];
|
||||||
|
|
||||||
|
const ips = getTelegramBackupIPFromBase64(str);
|
||||||
|
ips.forEach(i => backupIPs.add(i.ip));
|
||||||
|
|
||||||
|
console.log('[telegram backup ip]', picocolors.green('DNS TXT'), { domain, ips, server: resolver.server });
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[telegram backup ip]', picocolors.red('DNS TXT error'), { domain }, e);
|
||||||
|
}
|
||||||
|
})));
|
||||||
|
|
||||||
|
// Backup IP Source 2: Firebase Realtime Database (test server not supported)
|
||||||
|
try {
|
||||||
|
const text = await (await $$fetch('https://reserve-5a846.firebaseio.com/ipconfigv3.json')).json();
|
||||||
|
if (typeof text === 'string' && text.length === 344) {
|
||||||
|
const ips = getTelegramBackupIPFromBase64(text);
|
||||||
|
ips.forEach(i => backupIPs.add(i.ip));
|
||||||
|
|
||||||
|
console.log('[telegram backup ip]', picocolors.green('Firebase Realtime DB'), { ips });
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[telegram backup ip]', picocolors.red('Firebase Realtime DB error'), e);
|
||||||
|
// ignore all errors
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backup IP Source 3: Firebase Value Store (test server not supported)
|
||||||
|
try {
|
||||||
|
const json = await (await $$fetch('https://firestore.googleapis.com/v1/projects/reserve-5a846/databases/(default)/documents/ipconfig/v3', {
|
||||||
|
headers: {
|
||||||
|
Accept: '*/*',
|
||||||
|
Origin: undefined // Without this line, Google API will return "Bad request: Origin doesn't match Host for XD3.". Probably have something to do with sqlite cache store
|
||||||
|
}
|
||||||
|
})).json();
|
||||||
|
|
||||||
|
if (
|
||||||
|
json && typeof json === 'object'
|
||||||
|
&& 'fields' in json && typeof json.fields === 'object' && json.fields
|
||||||
|
&& 'data' in json.fields && typeof json.fields.data === 'object' && json.fields.data
|
||||||
|
&& 'stringValue' in json.fields.data && typeof json.fields.data.stringValue === 'string' && json.fields.data.stringValue.length === 344
|
||||||
|
) {
|
||||||
|
const ips = getTelegramBackupIPFromBase64(json.fields.data.stringValue);
|
||||||
|
ips.forEach(i => backupIPs.add(i.ip));
|
||||||
|
|
||||||
|
console.log('[telegram backup ip]', picocolors.green('Firebase Value Store'), { ips });
|
||||||
|
} else {
|
||||||
|
console.error('[telegram backup ip]', picocolors.red('Firebase Value Store data format invalid'), { json });
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[telegram backup ip]', picocolors.red('Firebase Value Store error'), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backup IP Source 4: Google App Engine
|
||||||
|
await Promise.all([
|
||||||
|
'https://dns-telegram.appspot.com',
|
||||||
|
'https://dns-telegram.appspot.com/test'
|
||||||
|
].map(async (url) => {
|
||||||
|
try {
|
||||||
|
const text = await (await $$fetch(url)).text();
|
||||||
|
if (text.length === 344) {
|
||||||
|
const ips = getTelegramBackupIPFromBase64(text);
|
||||||
|
ips.forEach(i => backupIPs.add(i.ip));
|
||||||
|
|
||||||
|
console.log('[telegram backup ip]', picocolors.green('Google App Engine'), { url, ips });
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[telegram backup ip]', picocolors.red('Google App Engine error'), { url }, e);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
// tcdnb.azureedge.net no longer works
|
||||||
|
|
||||||
|
console.log('[telegram backup ip]', `Found ${backupIPs.size} backup IPs:`, backupIPs);
|
||||||
|
|
||||||
|
ipcidr.push(...Array.from(backupIPs).map(i => i + '/32'));
|
||||||
|
|
||||||
|
return { timestamp: date.getTime(), ipcidr, ipcidr6 };
|
||||||
|
});
|
||||||
|
|
||||||
if (ipcidr.length + ipcidr6.length === 0) {
|
if (ipcidr.length + ipcidr6.length === 0) {
|
||||||
throw new Error('Failed to fetch data!');
|
throw new Error('Failed to fetch data!');
|
||||||
|
|||||||
159
Build/build-telegram-cidr.worker.ts
Normal file
159
Build/build-telegram-cidr.worker.ts
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
// @ts-check
|
||||||
|
import { task } from './trace';
|
||||||
|
import { SHARED_DESCRIPTION } from './constants/description';
|
||||||
|
import { RulesetOutput } from './lib/rules/ruleset';
|
||||||
|
import { getTelegramBackupIPFromBase64 } from './lib/get-telegram-backup-ip';
|
||||||
|
import picocolors from 'picocolors';
|
||||||
|
import { $$fetch } from './lib/fetch-retry';
|
||||||
|
import dns from 'node:dns/promises';
|
||||||
|
import { createReadlineInterfaceFromResponse } from './lib/fetch-text-by-line';
|
||||||
|
import { fastIpVersion } from 'foxts/fast-ip-version';
|
||||||
|
import { fastStringArrayJoin } from 'foxts/fast-string-array-join';
|
||||||
|
|
||||||
|
export const buildTelegramCIDR = task(require.main === module, __filename)(async (span) => {
|
||||||
|
const { timestamp, ipcidr, ipcidr6 } = await span.traceChildAsync('get telegram cidr', async () => {
|
||||||
|
const resp = await $$fetch('https://core.telegram.org/resources/cidr.txt');
|
||||||
|
const lastModified = resp.headers.get('last-modified');
|
||||||
|
const date = lastModified ? new Date(lastModified) : new Date();
|
||||||
|
|
||||||
|
const ipcidr: string[] = [
|
||||||
|
// Unused secret Telegram backup CIDR, announced by AS62041
|
||||||
|
'95.161.64.0/20'
|
||||||
|
];
|
||||||
|
const ipcidr6: string[] = [];
|
||||||
|
|
||||||
|
for await (const cidr of createReadlineInterfaceFromResponse(resp, true)) {
|
||||||
|
const v = fastIpVersion(cidr);
|
||||||
|
if (v === 4) {
|
||||||
|
ipcidr.push(cidr);
|
||||||
|
} else if (v === 6) {
|
||||||
|
ipcidr6.push(cidr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const backupIPs = new Set<string>();
|
||||||
|
|
||||||
|
// https://github.com/tdlib/td/blob/master/td/telegram/ConfigManager.cpp
|
||||||
|
|
||||||
|
const resolvers = ['8.8.8.8', '1.0.0.1'].map((ip) => {
|
||||||
|
const resolver = new dns.Resolver();
|
||||||
|
resolver.setServers([ip]);
|
||||||
|
return Object.assign(resolver, { server: ip });
|
||||||
|
});
|
||||||
|
|
||||||
|
// Backup IP Source 1 (DNS)
|
||||||
|
await Promise.all(resolvers.flatMap((resolver) => [
|
||||||
|
'apv3.stel.com', // prod
|
||||||
|
'tapv3.stel.com' // test
|
||||||
|
].map(async (domain) => {
|
||||||
|
try {
|
||||||
|
// tapv3.stel.com was for testing server
|
||||||
|
const resp = await resolver.resolveTxt(domain);
|
||||||
|
const strings = resp.map(r => fastStringArrayJoin(r, '')); // flatten
|
||||||
|
if (strings.length !== 2) {
|
||||||
|
throw new TypeError(`Unexpected TXT record count: ${strings.length}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const str = strings[0].length > strings[1].length
|
||||||
|
? strings[0] + strings[1]
|
||||||
|
: strings[1] + strings[0];
|
||||||
|
|
||||||
|
const ips = getTelegramBackupIPFromBase64(str);
|
||||||
|
ips.forEach(i => backupIPs.add(i.ip));
|
||||||
|
|
||||||
|
console.log('[telegram backup ip]', picocolors.green('DNS TXT'), { domain, ips, server: resolver.server });
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[telegram backup ip]', picocolors.red('DNS TXT error'), { domain }, e);
|
||||||
|
}
|
||||||
|
})));
|
||||||
|
|
||||||
|
// Backup IP Source 2: Firebase Realtime Database (test server not supported)
|
||||||
|
try {
|
||||||
|
const text = await (await $$fetch('https://reserve-5a846.firebaseio.com/ipconfigv3.json')).json();
|
||||||
|
if (typeof text === 'string' && text.length === 344) {
|
||||||
|
const ips = getTelegramBackupIPFromBase64(text);
|
||||||
|
ips.forEach(i => backupIPs.add(i.ip));
|
||||||
|
|
||||||
|
console.log('[telegram backup ip]', picocolors.green('Firebase Realtime DB'), { ips });
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[telegram backup ip]', picocolors.red('Firebase Realtime DB error'), e);
|
||||||
|
// ignore all errors
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backup IP Source 3: Firebase Value Store (test server not supported)
|
||||||
|
try {
|
||||||
|
const json = await (await $$fetch('https://firestore.googleapis.com/v1/projects/reserve-5a846/databases/(default)/documents/ipconfig/v3', {
|
||||||
|
headers: {
|
||||||
|
Accept: '*/*',
|
||||||
|
Origin: undefined // Without this line, Google API will return "Bad request: Origin doesn't match Host for XD3.". Probably have something to do with sqlite cache store
|
||||||
|
}
|
||||||
|
})).json();
|
||||||
|
|
||||||
|
if (
|
||||||
|
json && typeof json === 'object'
|
||||||
|
&& 'fields' in json && typeof json.fields === 'object' && json.fields
|
||||||
|
&& 'data' in json.fields && typeof json.fields.data === 'object' && json.fields.data
|
||||||
|
&& 'stringValue' in json.fields.data && typeof json.fields.data.stringValue === 'string' && json.fields.data.stringValue.length === 344
|
||||||
|
) {
|
||||||
|
const ips = getTelegramBackupIPFromBase64(json.fields.data.stringValue);
|
||||||
|
ips.forEach(i => backupIPs.add(i.ip));
|
||||||
|
|
||||||
|
console.log('[telegram backup ip]', picocolors.green('Firebase Value Store'), { ips });
|
||||||
|
} else {
|
||||||
|
console.error('[telegram backup ip]', picocolors.red('Firebase Value Store data format invalid'), { json });
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[telegram backup ip]', picocolors.red('Firebase Value Store error'), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backup IP Source 4: Google App Engine
|
||||||
|
await Promise.all([
|
||||||
|
'https://dns-telegram.appspot.com',
|
||||||
|
'https://dns-telegram.appspot.com/test'
|
||||||
|
].map(async (url) => {
|
||||||
|
try {
|
||||||
|
const text = await (await $$fetch(url)).text();
|
||||||
|
if (text.length === 344) {
|
||||||
|
const ips = getTelegramBackupIPFromBase64(text);
|
||||||
|
ips.forEach(i => backupIPs.add(i.ip));
|
||||||
|
|
||||||
|
console.log('[telegram backup ip]', picocolors.green('Google App Engine'), { url, ips });
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[telegram backup ip]', picocolors.red('Google App Engine error'), { url }, e);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
// tcdnb.azureedge.net no longer works
|
||||||
|
|
||||||
|
console.log('[telegram backup ip]', `Found ${backupIPs.size} backup IPs:`, backupIPs);
|
||||||
|
|
||||||
|
ipcidr.push(...Array.from(backupIPs).map(i => i + '/32'));
|
||||||
|
|
||||||
|
return { timestamp: date.getTime(), ipcidr, ipcidr6 };
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ipcidr.length + ipcidr6.length === 0) {
|
||||||
|
throw new Error('Failed to fetch data!');
|
||||||
|
}
|
||||||
|
|
||||||
|
const description = [
|
||||||
|
...SHARED_DESCRIPTION,
|
||||||
|
'Data from:',
|
||||||
|
' - https://core.telegram.org/resources/cidr.txt'
|
||||||
|
];
|
||||||
|
|
||||||
|
return new RulesetOutput(span, 'telegram', 'ip')
|
||||||
|
.withTitle('Sukka\'s Ruleset - Telegram IP CIDR')
|
||||||
|
.withDescription(description)
|
||||||
|
// .withDate(date) // With extra data source, we no longer use last-modified for file date
|
||||||
|
.appendDataSource(
|
||||||
|
'https://core.telegram.org/resources/cidr.txt (last updated: ' + new Date(timestamp).toISOString() + ')'
|
||||||
|
)
|
||||||
|
.bulkAddCIDR4NoResolve(ipcidr)
|
||||||
|
.bulkAddCIDR6NoResolve(ipcidr6)
|
||||||
|
.write();
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ___ = '';
|
||||||
@@ -6,9 +6,7 @@ import { downloadPreviousBuild } from './download-previous-build';
|
|||||||
import { buildCommon } from './build-common';
|
import { buildCommon } from './build-common';
|
||||||
import { buildRejectIPList } from './build-reject-ip-list';
|
import { buildRejectIPList } from './build-reject-ip-list';
|
||||||
import { buildAppleCdn } from './build-apple-cdn';
|
import { buildAppleCdn } from './build-apple-cdn';
|
||||||
import { buildCdnDownloadConf } from './build-cdn-download-conf';
|
|
||||||
import { buildRejectDomainSet } from './build-reject-domainset';
|
import { buildRejectDomainSet } from './build-reject-domainset';
|
||||||
import { buildTelegramCIDR } from './build-telegram-cidr';
|
|
||||||
import { buildChnCidr } from './build-chn-cidr';
|
import { buildChnCidr } from './build-chn-cidr';
|
||||||
import { buildSpeedtestDomainSet } from './build-speedtest-domainset';
|
import { buildSpeedtestDomainSet } from './build-speedtest-domainset';
|
||||||
import { buildDomesticRuleset } from './build-domestic-direct-lan-ruleset-dns-mapping-module';
|
import { buildDomesticRuleset } from './build-domestic-direct-lan-ruleset-dns-mapping-module';
|
||||||
@@ -18,11 +16,9 @@ import { buildStreamService } from './build-stream-service';
|
|||||||
import { buildRedirectModule } from './build-sgmodule-redirect';
|
import { buildRedirectModule } from './build-sgmodule-redirect';
|
||||||
import { buildAlwaysRealIPModule } from './build-sgmodule-always-realip';
|
import { buildAlwaysRealIPModule } from './build-sgmodule-always-realip';
|
||||||
|
|
||||||
import { buildMicrosoftCdn } from './build-microsoft-cdn';
|
import { createWorker } from './lib/worker';
|
||||||
|
|
||||||
import { buildPublic } from './build-public';
|
import { buildPublic } from './build-public';
|
||||||
import { downloadMockAssets } from './download-mock-assets';
|
|
||||||
|
|
||||||
import { buildCloudMounterRules } from './build-cloudmounter-rules';
|
import { buildCloudMounterRules } from './build-cloudmounter-rules';
|
||||||
|
|
||||||
import { printStats, printTraceResult, whyIsNodeRunning } from './trace';
|
import { printStats, printTraceResult, whyIsNodeRunning } from './trace';
|
||||||
@@ -71,6 +67,22 @@ const buildFinishedLock = path.join(ROOT_DIR, '.BUILD_FINISHED');
|
|||||||
fs.unlinkSync(buildFinishedLock);
|
fs.unlinkSync(buildFinishedLock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const microsoftCdnWorker = createWorker<typeof import('./build-microsoft-cdn.worker')>(
|
||||||
|
require.resolve('./build-microsoft-cdn.worker')
|
||||||
|
)(['buildMicrosoftCdn']);
|
||||||
|
|
||||||
|
const cdnDownloadWorker = createWorker<typeof import('./build-cdn-download-conf.worker')>(
|
||||||
|
require.resolve('./build-cdn-download-conf.worker')
|
||||||
|
)(['buildCdnDownloadConf']);
|
||||||
|
|
||||||
|
const telegramCidrWorker = createWorker<typeof import('./build-telegram-cidr.worker')>(
|
||||||
|
require.resolve('./build-telegram-cidr.worker')
|
||||||
|
)(['buildTelegramCIDR']);
|
||||||
|
|
||||||
|
const mockAssetsWorker = createWorker<typeof import('./download-mock-assets.worker')>(
|
||||||
|
require.resolve('./download-mock-assets.worker')
|
||||||
|
)(['downloadMockAssets']);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// only enable why-is-node-running in GitHub Actions debug mode
|
// only enable why-is-node-running in GitHub Actions debug mode
|
||||||
if (isCI && process.env.RUNNER_DEBUG === '1') {
|
if (isCI && process.env.RUNNER_DEBUG === '1') {
|
||||||
@@ -79,14 +91,14 @@ const buildFinishedLock = path.join(ROOT_DIR, '.BUILD_FINISHED');
|
|||||||
|
|
||||||
const downloadPreviousBuildPromise = downloadPreviousBuild();
|
const downloadPreviousBuildPromise = downloadPreviousBuild();
|
||||||
|
|
||||||
await Promise.all([
|
const traces: TraceResult[] = await Promise.all([
|
||||||
downloadPreviousBuildPromise,
|
downloadPreviousBuildPromise,
|
||||||
downloadPreviousBuildPromise.then(() => buildCommon()),
|
downloadPreviousBuildPromise.then(() => buildCommon()),
|
||||||
downloadPreviousBuildPromise.then(() => buildRejectIPList()),
|
downloadPreviousBuildPromise.then(() => buildRejectIPList()),
|
||||||
downloadPreviousBuildPromise.then(() => buildAppleCdn()),
|
downloadPreviousBuildPromise.then(() => buildAppleCdn()),
|
||||||
downloadPreviousBuildPromise.then(() => buildCdnDownloadConf()),
|
downloadPreviousBuildPromise.then(() => cdnDownloadWorker.buildCdnDownloadConf()),
|
||||||
downloadPreviousBuildPromise.then(() => buildRejectDomainSet()),
|
downloadPreviousBuildPromise.then(() => buildRejectDomainSet()),
|
||||||
downloadPreviousBuildPromise.then(() => buildTelegramCIDR()),
|
downloadPreviousBuildPromise.then(() => telegramCidrWorker.buildTelegramCIDR()),
|
||||||
downloadPreviousBuildPromise.then(() => buildChnCidr()),
|
downloadPreviousBuildPromise.then(() => buildChnCidr()),
|
||||||
downloadPreviousBuildPromise.then(() => buildSpeedtestDomainSet()),
|
downloadPreviousBuildPromise.then(() => buildSpeedtestDomainSet()),
|
||||||
downloadPreviousBuildPromise.then(() => buildDomesticRuleset()),
|
downloadPreviousBuildPromise.then(() => buildDomesticRuleset()),
|
||||||
@@ -94,45 +106,29 @@ const buildFinishedLock = path.join(ROOT_DIR, '.BUILD_FINISHED');
|
|||||||
downloadPreviousBuildPromise.then(() => buildRedirectModule()),
|
downloadPreviousBuildPromise.then(() => buildRedirectModule()),
|
||||||
downloadPreviousBuildPromise.then(() => buildAlwaysRealIPModule()),
|
downloadPreviousBuildPromise.then(() => buildAlwaysRealIPModule()),
|
||||||
downloadPreviousBuildPromise.then(() => buildStreamService()),
|
downloadPreviousBuildPromise.then(() => buildStreamService()),
|
||||||
downloadPreviousBuildPromise.then(() => buildMicrosoftCdn()),
|
downloadPreviousBuildPromise.then(() => microsoftCdnWorker.buildMicrosoftCdn()),
|
||||||
downloadPreviousBuildPromise.then(() => buildCloudMounterRules()),
|
downloadPreviousBuildPromise.then(() => buildCloudMounterRules()),
|
||||||
downloadMockAssets()
|
mockAssetsWorker.downloadMockAssets()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await buildDeprecateFiles();
|
traces.push(
|
||||||
await buildPublic();
|
await buildDeprecateFiles(),
|
||||||
|
await buildPublic()
|
||||||
|
);
|
||||||
|
|
||||||
// write a file to demonstrate that the build is finished
|
// write a file to demonstrate that the build is finished
|
||||||
fs.writeFileSync(buildFinishedLock, 'BUILD_FINISHED\n');
|
fs.writeFileSync(buildFinishedLock, 'BUILD_FINISHED\n');
|
||||||
|
|
||||||
const traces: TraceResult[] = [];
|
traces.forEach((t) => {
|
||||||
[
|
printTraceResult(t);
|
||||||
downloadPreviousBuild,
|
|
||||||
downloadMockAssets,
|
|
||||||
buildCommon,
|
|
||||||
buildRejectIPList,
|
|
||||||
buildAppleCdn,
|
|
||||||
buildCdnDownloadConf,
|
|
||||||
buildRejectDomainSet,
|
|
||||||
buildTelegramCIDR,
|
|
||||||
buildChnCidr,
|
|
||||||
buildSpeedtestDomainSet,
|
|
||||||
buildDomesticRuleset,
|
|
||||||
buildGlobalRuleset,
|
|
||||||
buildRedirectModule,
|
|
||||||
buildAlwaysRealIPModule,
|
|
||||||
buildStreamService,
|
|
||||||
buildMicrosoftCdn,
|
|
||||||
buildCloudMounterRules,
|
|
||||||
buildPublic,
|
|
||||||
buildDeprecateFiles
|
|
||||||
].forEach((fn) => {
|
|
||||||
const trace = fn.getInternalTraceResult();
|
|
||||||
printTraceResult(trace);
|
|
||||||
traces.push(trace);
|
|
||||||
});
|
});
|
||||||
printStats(traces);
|
printStats(traces);
|
||||||
|
|
||||||
|
await microsoftCdnWorker.end();
|
||||||
|
await cdnDownloadWorker.end();
|
||||||
|
await telegramCidrWorker.end();
|
||||||
|
await mockAssetsWorker.end();
|
||||||
|
|
||||||
// Finish the build to avoid leaking timer/fetch ref
|
// Finish the build to avoid leaking timer/fetch ref
|
||||||
await whyIsNodeRunning();
|
await whyIsNodeRunning();
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
|
|||||||
@@ -1,43 +1,23 @@
|
|||||||
import Worktank from 'worktank';
|
import picocolors from 'picocolors';
|
||||||
|
import { parse } from 'tldts-experimental';
|
||||||
|
import { appendArrayInPlaceCurried } from 'foxts/append-array-in-place';
|
||||||
|
|
||||||
import { dummySpan, printTraceResult } from '../trace';
|
import { dummySpan } from '../trace';
|
||||||
import type { Span } from '../trace';
|
|
||||||
import type { TldTsParsed } from './normalize-domain';
|
import type { TldTsParsed } from './normalize-domain';
|
||||||
|
|
||||||
const pool = new Worktank({
|
import { loosTldOptWithPrivateDomains } from '../constants/loose-tldts-opt';
|
||||||
pool: {
|
import { BLACK_TLD, WHITELIST_MAIN_DOMAINS, leathalKeywords, lowKeywords, sensitiveKeywords } from '../constants/phishing-score-source';
|
||||||
name: 'process-phishing-domains',
|
import { PHISHING_DOMAIN_LISTS_EXTRA, PHISHING_HOSTS_EXTRA } from '../constants/reject-data-source';
|
||||||
size: 1
|
|
||||||
},
|
|
||||||
worker: {
|
|
||||||
autoAbort: 20000, // The maximum number of milliseconds to wait for the result from the worker, if exceeded the worker is terminated and the execution promise rejects
|
|
||||||
autoInstantiate: true,
|
|
||||||
autoTerminate: 30000, // The interval of milliseconds at which to check if the pool can be automatically terminated, to free up resources, workers will be spawned up again if needed
|
|
||||||
env: {},
|
|
||||||
methods: {
|
|
||||||
// eslint-disable-next-line object-shorthand -- workertank
|
|
||||||
getPhishingDomains: async function (
|
|
||||||
importMetaUrl: string,
|
|
||||||
/** require.main === module */ isDebug = false
|
|
||||||
): Promise<string[]> {
|
|
||||||
// TODO: createRequire is a temporary workaround for https://github.com/nodejs/node/issues/51956
|
|
||||||
const { default: module } = await import('node:module');
|
|
||||||
const __require = module.createRequire(importMetaUrl);
|
|
||||||
|
|
||||||
const picocolors = __require('picocolors') as typeof import('picocolors');
|
import { processHostsWithPreload } from './parse-filter/hosts';
|
||||||
const tldts = __require('tldts-experimental') as typeof import('tldts-experimental');
|
import { processDomainListsWithPreload } from './parse-filter/domainlists';
|
||||||
|
|
||||||
const { appendArrayInPlaceCurried } = __require('foxts/append-array-in-place') as typeof import('foxts/append-array-in-place');
|
import process from 'node:process';
|
||||||
|
|
||||||
const { loosTldOptWithPrivateDomains } = __require('../constants/loose-tldts-opt') as typeof import('../constants/loose-tldts-opt');
|
|
||||||
const { BLACK_TLD, WHITELIST_MAIN_DOMAINS, leathalKeywords, lowKeywords, sensitiveKeywords } = __require('../constants/phishing-score-source') as typeof import('../constants/phishing-score-source');
|
|
||||||
const { PHISHING_DOMAIN_LISTS_EXTRA, PHISHING_HOSTS_EXTRA } = __require('../constants/reject-data-source') as typeof import('../constants/reject-data-source');
|
|
||||||
const { dummySpan } = __require('../trace') as typeof import('../trace');
|
|
||||||
const NullPrototypeObject = __require('null-prototype-object') as typeof import('null-prototype-object');
|
|
||||||
|
|
||||||
const { processHostsWithPreload } = __require('./parse-filter/hosts') as typeof import('./parse-filter/hosts');
|
|
||||||
const { processDomainListsWithPreload } = __require('./parse-filter/domainlists') as typeof import('./parse-filter/domainlists');
|
|
||||||
|
|
||||||
|
export function getPhishingDomains(isDebug = false): Promise<string[]> {
|
||||||
|
return dummySpan.traceChild('get phishing domains').traceAsyncFn(async (span) => span.traceChildAsync(
|
||||||
|
'process phishing domain set',
|
||||||
|
async () => {
|
||||||
const downloads = [
|
const downloads = [
|
||||||
...PHISHING_DOMAIN_LISTS_EXTRA.map(entry => processDomainListsWithPreload(...entry)),
|
...PHISHING_DOMAIN_LISTS_EXTRA.map(entry => processDomainListsWithPreload(...entry)),
|
||||||
...PHISHING_HOSTS_EXTRA.map(entry => processHostsWithPreload(...entry))
|
...PHISHING_HOSTS_EXTRA.map(entry => processHostsWithPreload(...entry))
|
||||||
@@ -48,30 +28,19 @@ const pool = new Worktank({
|
|||||||
const domainGroups = await Promise.all(downloads.map(task => task(dummySpan)));
|
const domainGroups = await Promise.all(downloads.map(task => task(dummySpan)));
|
||||||
domainGroups.forEach(appendArrayInPlaceCurried(domainArr));
|
domainGroups.forEach(appendArrayInPlaceCurried(domainArr));
|
||||||
|
|
||||||
// return domainArr;
|
|
||||||
|
|
||||||
const domainCountMap = new Map<string, number>();
|
const domainCountMap = new Map<string, number>();
|
||||||
const domainScoreMap: Record<string, number> = new NullPrototypeObject();
|
const domainScoreMap: Record<string, number> = Object.create(null) as Record<string, number>;
|
||||||
|
|
||||||
let line = '';
|
let line: string;
|
||||||
let tld: string | null = '';
|
let tld: string | null;
|
||||||
let apexDomain: string | null = '';
|
let apexDomain: string | null;
|
||||||
let subdomain: string | null = '';
|
let subdomain: string | null;
|
||||||
let parsed: TldTsParsed;
|
let parsed: TldTsParsed;
|
||||||
|
|
||||||
// const set = new Set<string>();
|
|
||||||
// let duplicateCount = 0;
|
|
||||||
|
|
||||||
for (let i = 0, len = domainArr.length; i < len; i++) {
|
for (let i = 0, len = domainArr.length; i < len; i++) {
|
||||||
line = domainArr[i];
|
line = domainArr[i];
|
||||||
|
|
||||||
// if (set.has(line)) {
|
parsed = parse(line, loosTldOptWithPrivateDomains);
|
||||||
// duplicateCount++;
|
|
||||||
// } else {
|
|
||||||
// set.add(line);
|
|
||||||
// }
|
|
||||||
|
|
||||||
parsed = tldts.parse(line, loosTldOptWithPrivateDomains);
|
|
||||||
if (parsed.isPrivate) {
|
if (parsed.isPrivate) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -127,7 +96,6 @@ const pool = new Worktank({
|
|||||||
domainCountMap.forEach((count, apexDomain) => {
|
domainCountMap.forEach((count, apexDomain) => {
|
||||||
const score = domainScoreMap[apexDomain];
|
const score = domainScoreMap[apexDomain];
|
||||||
if (
|
if (
|
||||||
// !WHITELIST_MAIN_DOMAINS.has(apexDomain)
|
|
||||||
(score >= 24)
|
(score >= 24)
|
||||||
|| (score >= 16 && count >= 7)
|
|| (score >= 16 && count >= 7)
|
||||||
|| (score >= 13 && count >= 11)
|
|| (score >= 13 && count >= 11)
|
||||||
@@ -149,8 +117,11 @@ const pool = new Worktank({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return domainArr;
|
return domainArr;
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
function calcDomainAbuseScore(subdomain: string, fullDomain: string = subdomain) {
|
function calcDomainAbuseScore(subdomain: string, fullDomain: string = subdomain) {
|
||||||
if (leathalKeywords(fullDomain)) {
|
if (leathalKeywords(fullDomain)) {
|
||||||
return 100;
|
return 100;
|
||||||
}
|
}
|
||||||
@@ -193,27 +164,8 @@ const pool = new Worktank({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return weight;
|
return weight;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export function getPhishingDomains(parentSpan: Span) {
|
|
||||||
return parentSpan.traceChild('get phishing domains').traceAsyncFn(async (span) => span.traceChildAsync(
|
|
||||||
'process phishing domain set',
|
|
||||||
() => pool.exec(
|
|
||||||
'getPhishingDomains',
|
|
||||||
[__filename, require.main === module]
|
|
||||||
).finally(() => pool.terminate())
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (require.main === module) {
|
if (!process.env.JEST_WORKER_ID && require.main === module) {
|
||||||
getPhishingDomains(dummySpan)
|
getPhishingDomains(true).catch(console.error);
|
||||||
.catch(console.error)
|
|
||||||
.finally(() => {
|
|
||||||
dummySpan.stop();
|
|
||||||
printTraceResult(dummySpan.traceResult);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,10 +7,6 @@ import { bigint2ip } from 'fast-cidr-tools';
|
|||||||
|
|
||||||
import { base64ToUint8Array, concatUint8Arrays } from 'foxts/uint8array-utils';
|
import { base64ToUint8Array, concatUint8Arrays } from 'foxts/uint8array-utils';
|
||||||
|
|
||||||
import Worktank from 'worktank';
|
|
||||||
import { wait } from 'foxts/wait';
|
|
||||||
import { once } from 'foxts/once';
|
|
||||||
|
|
||||||
const mtptoto_public_rsa = `-----BEGIN RSA PUBLIC KEY-----
|
const mtptoto_public_rsa = `-----BEGIN RSA PUBLIC KEY-----
|
||||||
MIIBCgKCAQEAyr+18Rex2ohtVy8sroGP
|
MIIBCgKCAQEAyr+18Rex2ohtVy8sroGP
|
||||||
BwXD3DOoKCSpjDqYoXgCqB7ioln4eDCFfOBUlfXUEvM/fnKCpF46VkAftlb4VuPD
|
BwXD3DOoKCSpjDqYoXgCqB7ioln4eDCFfOBUlfXUEvM/fnKCpF46VkAftlb4VuPD
|
||||||
@@ -112,160 +108,3 @@ export function getTelegramBackupIPFromBase64(base64: string) {
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
const pool = new Worktank({
|
|
||||||
pool: {
|
|
||||||
name: 'get-telegram-backup-ips',
|
|
||||||
size: 1 // The number of workers to keep in the pool, if more workers are needed they will be spawned up to this limit
|
|
||||||
},
|
|
||||||
worker: {
|
|
||||||
autoAbort: 10000,
|
|
||||||
autoTerminate: 30000, // The interval of milliseconds at which to check if the pool can be automatically terminated, to free up resources, workers will be spawned up again if needed
|
|
||||||
autoInstantiate: true,
|
|
||||||
methods: {
|
|
||||||
// eslint-disable-next-line object-shorthand -- workertank
|
|
||||||
getTelegramBackupIPs: async function (__filename: string): Promise<{ timestamp: number, ipcidr: string[], ipcidr6: string[] }> {
|
|
||||||
// TODO: createRequire is a temporary workaround for https://github.com/nodejs/node/issues/51956
|
|
||||||
const { default: module } = await import('node:module');
|
|
||||||
const __require = module.createRequire(__filename);
|
|
||||||
|
|
||||||
const picocolors = __require('picocolors') as typeof import('picocolors');
|
|
||||||
const { '~fetch': fetch } = __require('./fetch-retry') as typeof import('./fetch-retry');
|
|
||||||
|
|
||||||
const dns = __require('node:dns/promises') as typeof import('node:dns/promises');
|
|
||||||
|
|
||||||
const { createReadlineInterfaceFromResponse } = __require('./fetch-text-by-line') as typeof import('./fetch-text-by-line');
|
|
||||||
const { getTelegramBackupIPFromBase64 } = __require('./get-telegram-backup-ip') as typeof import('./get-telegram-backup-ip');
|
|
||||||
const { fastIpVersion } = __require('foxts/fast-ip-version') as typeof import('foxts/fast-ip-version');
|
|
||||||
const { fastStringArrayJoin } = __require('foxts/fast-string-array-join') as typeof import('foxts/fast-string-array-join');
|
|
||||||
|
|
||||||
const resp = await fetch('https://core.telegram.org/resources/cidr.txt');
|
|
||||||
const lastModified = resp.headers.get('last-modified');
|
|
||||||
const date = lastModified ? new Date(lastModified) : new Date();
|
|
||||||
|
|
||||||
const ipcidr: string[] = [
|
|
||||||
// Unused secret Telegram backup CIDR, announced by AS62041
|
|
||||||
'95.161.64.0/20'
|
|
||||||
];
|
|
||||||
const ipcidr6: string[] = [];
|
|
||||||
|
|
||||||
for await (const cidr of createReadlineInterfaceFromResponse(resp, true)) {
|
|
||||||
const v = fastIpVersion(cidr);
|
|
||||||
if (v === 4) {
|
|
||||||
ipcidr.push(cidr);
|
|
||||||
} else if (v === 6) {
|
|
||||||
ipcidr6.push(cidr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const backupIPs = new Set<string>();
|
|
||||||
|
|
||||||
// https://github.com/tdlib/td/blob/master/td/telegram/ConfigManager.cpp
|
|
||||||
|
|
||||||
const resolvers = ['8.8.8.8', '1.0.0.1'].map((ip) => {
|
|
||||||
const resolver = new dns.Resolver();
|
|
||||||
resolver.setServers([ip]);
|
|
||||||
return Object.assign(resolver, { server: ip });
|
|
||||||
});
|
|
||||||
|
|
||||||
// Backup IP Source 1 (DNS)
|
|
||||||
await Promise.all(resolvers.flatMap((resolver) => [
|
|
||||||
'apv3.stel.com', // prod
|
|
||||||
'tapv3.stel.com' // test
|
|
||||||
].map(async (domain) => {
|
|
||||||
try {
|
|
||||||
// tapv3.stel.com was for testing server
|
|
||||||
const resp = await resolver.resolveTxt(domain);
|
|
||||||
const strings = resp.map(r => fastStringArrayJoin(r, '')); // flatten
|
|
||||||
if (strings.length !== 2) {
|
|
||||||
throw new TypeError(`Unexpected TXT record count: ${strings.length}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const str = strings[0].length > strings[1].length
|
|
||||||
? strings[0] + strings[1]
|
|
||||||
: strings[1] + strings[0];
|
|
||||||
|
|
||||||
const ips = getTelegramBackupIPFromBase64(str);
|
|
||||||
ips.forEach(i => backupIPs.add(i.ip));
|
|
||||||
|
|
||||||
console.log('[telegram backup ip]', picocolors.green('DNS TXT'), { domain, ips, server: resolver.server });
|
|
||||||
} catch (e) {
|
|
||||||
console.error('[telegram backup ip]', picocolors.red('DNS TXT error'), { domain }, e);
|
|
||||||
}
|
|
||||||
})));
|
|
||||||
|
|
||||||
// Backup IP Source 2: Firebase Realtime Database (test server not supported)
|
|
||||||
try {
|
|
||||||
const text = await (await fetch('https://reserve-5a846.firebaseio.com/ipconfigv3.json')).json();
|
|
||||||
if (typeof text === 'string' && text.length === 344) {
|
|
||||||
const ips = getTelegramBackupIPFromBase64(text);
|
|
||||||
ips.forEach(i => backupIPs.add(i.ip));
|
|
||||||
|
|
||||||
console.log('[telegram backup ip]', picocolors.green('Firebase Realtime DB'), { ips });
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('[telegram backup ip]', picocolors.red('Firebase Realtime DB error'), e);
|
|
||||||
// ignore all errors
|
|
||||||
}
|
|
||||||
|
|
||||||
// Backup IP Source 3: Firebase Value Store (test server not supported)
|
|
||||||
try {
|
|
||||||
const json = await (await fetch('https://firestore.googleapis.com/v1/projects/reserve-5a846/databases/(default)/documents/ipconfig/v3', {
|
|
||||||
headers: {
|
|
||||||
Accept: '*/*',
|
|
||||||
Origin: undefined // Without this line, Google API will return "Bad request: Origin doesn't match Host for XD3.". Probably have something to do with sqlite cache store
|
|
||||||
}
|
|
||||||
})).json();
|
|
||||||
|
|
||||||
// const json = await resp.json();
|
|
||||||
if (
|
|
||||||
json && typeof json === 'object'
|
|
||||||
&& 'fields' in json && typeof json.fields === 'object' && json.fields
|
|
||||||
&& 'data' in json.fields && typeof json.fields.data === 'object' && json.fields.data
|
|
||||||
&& 'stringValue' in json.fields.data && typeof json.fields.data.stringValue === 'string' && json.fields.data.stringValue.length === 344
|
|
||||||
) {
|
|
||||||
const ips = getTelegramBackupIPFromBase64(json.fields.data.stringValue);
|
|
||||||
ips.forEach(i => backupIPs.add(i.ip));
|
|
||||||
|
|
||||||
console.log('[telegram backup ip]', picocolors.green('Firebase Value Store'), { ips });
|
|
||||||
} else {
|
|
||||||
console.error('[telegram backup ip]', picocolors.red('Firebase Value Store data format invalid'), { json });
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('[telegram backup ip]', picocolors.red('Firebase Value Store error'), e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Backup IP Source 4: Google App Engine
|
|
||||||
await Promise.all([
|
|
||||||
'https://dns-telegram.appspot.com',
|
|
||||||
'https://dns-telegram.appspot.com/test'
|
|
||||||
].map(async (url) => {
|
|
||||||
try {
|
|
||||||
const text = await (await fetch(url)).text();
|
|
||||||
if (text.length === 344) {
|
|
||||||
const ips = getTelegramBackupIPFromBase64(text);
|
|
||||||
ips.forEach(i => backupIPs.add(i.ip));
|
|
||||||
|
|
||||||
console.log('[telegram backup ip]', picocolors.green('Google App Engine'), { url, ips });
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('[telegram backup ip]', picocolors.red('Google App Engine error'), { url }, e);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
// tcdnb.azureedge.net no longer works
|
|
||||||
|
|
||||||
console.log('[telegram backup ip]', `Found ${backupIPs.size} backup IPs:`, backupIPs);
|
|
||||||
|
|
||||||
ipcidr.push(...Array.from(backupIPs).map(i => i + '/32'));
|
|
||||||
|
|
||||||
return { timestamp: date.getTime(), ipcidr, ipcidr6 };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export const getTelegramCIDRPromise = once(() => wait(0).then(() => pool.exec(
|
|
||||||
'getTelegramBackupIPs',
|
|
||||||
[__filename]
|
|
||||||
)).finally(() => pool.terminate()), false);
|
|
||||||
|
|||||||
28
Build/lib/worker.ts
Normal file
28
Build/lib/worker.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import process from 'node:process';
|
||||||
|
import type { JestWorkerFarm } from 'jest-worker';
|
||||||
|
import { Worker as JestWorker } from 'jest-worker';
|
||||||
|
|
||||||
|
const sharedWorkerOptions = {
|
||||||
|
numWorkers: 1,
|
||||||
|
enableWorkerThreads: true,
|
||||||
|
forkOptions: {
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
NODE_OPTIONS: process.env.NODE_OPTIONS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} satisfies ConstructorParameters<typeof JestWorker>[1];
|
||||||
|
|
||||||
|
export function createWorker<T extends Record<string, unknown>>(workerPath: string) {
|
||||||
|
return <const K extends ReadonlyArray<keyof T & string>>(exposedMethods: K): JestWorkerFarm<Pick<T, K[number]>> => {
|
||||||
|
const worker = new JestWorker(workerPath, {
|
||||||
|
...sharedWorkerOptions,
|
||||||
|
exposedMethods
|
||||||
|
}) as JestWorkerFarm<Pick<T, K[number]>>;
|
||||||
|
|
||||||
|
worker.getStdout().pipe(process.stdout);
|
||||||
|
worker.getStderr().pipe(process.stderr);
|
||||||
|
|
||||||
|
return worker;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -43,7 +43,6 @@
|
|||||||
"undici": "^7.24.5",
|
"undici": "^7.24.5",
|
||||||
"undici-cache-store-better-sqlite3": "^1.0.1",
|
"undici-cache-store-better-sqlite3": "^1.0.1",
|
||||||
"why-is-node-running": "^3.2.2",
|
"why-is-node-running": "^3.2.2",
|
||||||
"worktank": "^3.0.2",
|
|
||||||
"xbits": "^0.2.0",
|
"xbits": "^0.2.0",
|
||||||
"yaml": "^2.8.3",
|
"yaml": "^2.8.3",
|
||||||
"yauzl-promise": "^4.0.0"
|
"yauzl-promise": "^4.0.0"
|
||||||
@@ -62,6 +61,7 @@
|
|||||||
"eslint": "^10.1.0",
|
"eslint": "^10.1.0",
|
||||||
"eslint-config-sukka": "^8.9.0",
|
"eslint-config-sukka": "^8.9.0",
|
||||||
"eslint-formatter-sukka": "^8.9.0",
|
"eslint-formatter-sukka": "^8.9.0",
|
||||||
|
"jest-worker": "^30.3.0",
|
||||||
"mitata": "^1.0.34",
|
"mitata": "^1.0.34",
|
||||||
"mocha": "^11.7.5",
|
"mocha": "^11.7.5",
|
||||||
"tinyexec": "^1.0.4",
|
"tinyexec": "^1.0.4",
|
||||||
|
|||||||
151
pnpm-lock.yaml
generated
151
pnpm-lock.yaml
generated
@@ -86,9 +86,6 @@ importers:
|
|||||||
why-is-node-running:
|
why-is-node-running:
|
||||||
specifier: ^3.2.2
|
specifier: ^3.2.2
|
||||||
version: 3.2.2
|
version: 3.2.2
|
||||||
worktank:
|
|
||||||
specifier: ^3.0.2
|
|
||||||
version: 3.0.2
|
|
||||||
xbits:
|
xbits:
|
||||||
specifier: ^0.2.0
|
specifier: ^0.2.0
|
||||||
version: 0.2.0
|
version: 0.2.0
|
||||||
@@ -138,6 +135,9 @@ importers:
|
|||||||
eslint-formatter-sukka:
|
eslint-formatter-sukka:
|
||||||
specifier: ^8.9.0
|
specifier: ^8.9.0
|
||||||
version: 8.9.0(eslint@10.1.0)
|
version: 8.9.0(eslint@10.1.0)
|
||||||
|
jest-worker:
|
||||||
|
specifier: ^30.3.0
|
||||||
|
version: 30.3.0
|
||||||
mitata:
|
mitata:
|
||||||
specifier: ^1.0.34
|
specifier: ^1.0.34
|
||||||
version: 1.0.34
|
version: 1.0.34
|
||||||
@@ -274,6 +274,18 @@ packages:
|
|||||||
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
|
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
'@jest/pattern@30.0.1':
|
||||||
|
resolution: {integrity: sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==}
|
||||||
|
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
|
||||||
|
|
||||||
|
'@jest/schemas@30.0.5':
|
||||||
|
resolution: {integrity: sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==}
|
||||||
|
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
|
||||||
|
|
||||||
|
'@jest/types@30.3.0':
|
||||||
|
resolution: {integrity: sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==}
|
||||||
|
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
|
||||||
|
|
||||||
'@mitata/counters@0.0.8':
|
'@mitata/counters@0.0.8':
|
||||||
resolution: {integrity: sha512-f11w0Y1ETFlarDP7CePj8Z+y8Gv5Ax4gMxWsEwrqh0kH/YIY030Ezx5SUJeQg0YPTZ2OHKGcLG1oGJbIqHzaJA==}
|
resolution: {integrity: sha512-f11w0Y1ETFlarDP7CePj8Z+y8Gv5Ax4gMxWsEwrqh0kH/YIY030Ezx5SUJeQg0YPTZ2OHKGcLG1oGJbIqHzaJA==}
|
||||||
|
|
||||||
@@ -529,6 +541,9 @@ packages:
|
|||||||
'@remusao/trie@2.1.0':
|
'@remusao/trie@2.1.0':
|
||||||
resolution: {integrity: sha512-Er3Q8q0/2OcCJPQYJOPLmCuqO0wu7cav3SPtpjlxSbjFi1x+A1pZkkLD6c9q2rGEkGW/tkrRzfrhNMt8VQjzXg==}
|
resolution: {integrity: sha512-Er3Q8q0/2OcCJPQYJOPLmCuqO0wu7cav3SPtpjlxSbjFi1x+A1pZkkLD6c9q2rGEkGW/tkrRzfrhNMt8VQjzXg==}
|
||||||
|
|
||||||
|
'@sinclair/typebox@0.34.49':
|
||||||
|
resolution: {integrity: sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==}
|
||||||
|
|
||||||
'@swc-node/core@1.14.1':
|
'@swc-node/core@1.14.1':
|
||||||
resolution: {integrity: sha512-jrt5GUaZUU6cmMS+WTJEvGvaB6j1YNKPHPzC2PUi2BjaFbtxURHj6641Az6xN7b665hNniAIdvjxWcRml5yCnw==}
|
resolution: {integrity: sha512-jrt5GUaZUU6cmMS+WTJEvGvaB6j1YNKPHPzC2PUi2BjaFbtxURHj6641Az6xN7b665hNniAIdvjxWcRml5yCnw==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
@@ -639,6 +654,15 @@ packages:
|
|||||||
'@types/estree@1.0.8':
|
'@types/estree@1.0.8':
|
||||||
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
||||||
|
|
||||||
|
'@types/istanbul-lib-coverage@2.0.6':
|
||||||
|
resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==}
|
||||||
|
|
||||||
|
'@types/istanbul-lib-report@3.0.3':
|
||||||
|
resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==}
|
||||||
|
|
||||||
|
'@types/istanbul-reports@3.0.4':
|
||||||
|
resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==}
|
||||||
|
|
||||||
'@types/json-schema@7.0.15':
|
'@types/json-schema@7.0.15':
|
||||||
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
||||||
|
|
||||||
@@ -654,6 +678,12 @@ packages:
|
|||||||
'@types/tar-stream@3.1.4':
|
'@types/tar-stream@3.1.4':
|
||||||
resolution: {integrity: sha512-921gW0+g29mCJX0fRvqeHzBlE/XclDaAG0Ousy1LCghsOhvaKacDeRGEVzQP9IPfKn8Vysy7FEXAIxycpc/CMg==}
|
resolution: {integrity: sha512-921gW0+g29mCJX0fRvqeHzBlE/XclDaAG0Ousy1LCghsOhvaKacDeRGEVzQP9IPfKn8Vysy7FEXAIxycpc/CMg==}
|
||||||
|
|
||||||
|
'@types/yargs-parser@21.0.3':
|
||||||
|
resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==}
|
||||||
|
|
||||||
|
'@types/yargs@17.0.35':
|
||||||
|
resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==}
|
||||||
|
|
||||||
'@types/yauzl-promise@4.0.1':
|
'@types/yauzl-promise@4.0.1':
|
||||||
resolution: {integrity: sha512-qYEC3rJwqiJpdQ9b+bPNeuSY0c3JUM8vIuDy08qfuVN7xHm3ZDsHn2kGphUIB0ruEXrPGNXZ64nMUcu4fDjViQ==}
|
resolution: {integrity: sha512-qYEC3rJwqiJpdQ9b+bPNeuSY0c3JUM8vIuDy08qfuVN7xHm3ZDsHn2kGphUIB0ruEXrPGNXZ64nMUcu4fDjViQ==}
|
||||||
|
|
||||||
@@ -716,6 +746,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw==}
|
resolution: {integrity: sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
||||||
|
'@ungap/structured-clone@1.3.0':
|
||||||
|
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
|
||||||
|
|
||||||
'@unrs/resolver-binding-android-arm-eabi@1.11.1':
|
'@unrs/resolver-binding-android-arm-eabi@1.11.1':
|
||||||
resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==}
|
resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
@@ -1404,9 +1437,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==}
|
resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==}
|
||||||
engines: {node: '>= 4'}
|
engines: {node: '>= 4'}
|
||||||
|
|
||||||
immediato@1.1.0:
|
|
||||||
resolution: {integrity: sha512-6DTWQWiM3SyxAbNRDmMvFgZVwVP6wT8ciQv7GivxXejtXZFIcemC0Wlzfd/jEouJ2JroCIp4qZVloKW4BviUpQ==}
|
|
||||||
|
|
||||||
imurmurhash@0.1.4:
|
imurmurhash@0.1.4:
|
||||||
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
|
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
|
||||||
engines: {node: '>=0.8.19'}
|
engines: {node: '>=0.8.19'}
|
||||||
@@ -1462,15 +1492,21 @@ packages:
|
|||||||
isexe@2.0.0:
|
isexe@2.0.0:
|
||||||
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
||||||
|
|
||||||
isoconcurrency@1.0.0:
|
|
||||||
resolution: {integrity: sha512-YhuPf5V6uOtQQHt9gIkOTbq75ceXqraDvxtZZeS/XbNsre6fmM+WpJgNTSkGX5jB3+gnbwoTVqW1c3qdfyVpOA==}
|
|
||||||
|
|
||||||
isotimer@1.0.0:
|
|
||||||
resolution: {integrity: sha512-1p1wborMl9fFbulXx9YBpIqFnfUn/2tN8Ne9g3GLMaiQAPmN/wLlpNOKCNT822div3Sq7LKkApZJ+6JipDUusQ==}
|
|
||||||
|
|
||||||
jackspeak@3.4.3:
|
jackspeak@3.4.3:
|
||||||
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
|
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
|
||||||
|
|
||||||
|
jest-regex-util@30.0.1:
|
||||||
|
resolution: {integrity: sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==}
|
||||||
|
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
|
||||||
|
|
||||||
|
jest-util@30.3.0:
|
||||||
|
resolution: {integrity: sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==}
|
||||||
|
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
|
||||||
|
|
||||||
|
jest-worker@30.3.0:
|
||||||
|
resolution: {integrity: sha512-DrCKkaQwHexjRUFTmPzs7sHQe0TSj9nvDALKGdwmK5mW9v7j90BudWirKAJHt3QQ9Dhrg1F7DogPzhChppkJpQ==}
|
||||||
|
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
|
||||||
|
|
||||||
js-yaml@4.1.0:
|
js-yaml@4.1.0:
|
||||||
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
|
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -1513,6 +1549,9 @@ packages:
|
|||||||
lru-cache@10.4.3:
|
lru-cache@10.4.3:
|
||||||
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
|
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
|
||||||
|
|
||||||
|
merge-stream@2.0.0:
|
||||||
|
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
|
||||||
|
|
||||||
mime@3.0.0:
|
mime@3.0.0:
|
||||||
resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==}
|
resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==}
|
||||||
engines: {node: '>=10.0.0'}
|
engines: {node: '>=10.0.0'}
|
||||||
@@ -1650,9 +1689,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
|
|
||||||
promise-make-naked@3.0.2:
|
|
||||||
resolution: {integrity: sha512-B+b+kQ1YrYS7zO7P7bQcoqqMUizP06BOyNSBEnB5VJKDSWo8fsVuDkfSmwdjF0JsRtaNh83so5MMFJ95soH5jg==}
|
|
||||||
|
|
||||||
pump@3.0.3:
|
pump@3.0.3:
|
||||||
resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==}
|
resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==}
|
||||||
|
|
||||||
@@ -1919,9 +1955,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-/REy6amwPZl44DDzvRCkaI1q1bIiQB0mEFQLUrhz3z2EK91cp3n72rAjUlrTP0zV22HJIUOVHQGPxhFRjxjt+Q==}
|
resolution: {integrity: sha512-/REy6amwPZl44DDzvRCkaI1q1bIiQB0mEFQLUrhz3z2EK91cp3n72rAjUlrTP0zV22HJIUOVHQGPxhFRjxjt+Q==}
|
||||||
engines: {node: '>=4.0.0'}
|
engines: {node: '>=4.0.0'}
|
||||||
|
|
||||||
webworker-shim@1.1.4:
|
|
||||||
resolution: {integrity: sha512-W/40L5W6ZQyGhYr3hJ7N/2SjdK5OdFtnYm94j6xlRyjckegXnIGwz0EdxdkQx6VGTglJjK8mqBhMz3fd3AY4bg==}
|
|
||||||
|
|
||||||
which@2.0.2:
|
which@2.0.2:
|
||||||
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
|
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
@@ -1939,9 +1972,6 @@ packages:
|
|||||||
workerpool@9.3.3:
|
workerpool@9.3.3:
|
||||||
resolution: {integrity: sha512-slxCaKbYjEdFT/o2rH9xS1hf4uRDch1w7Uo+apxhZ+sf/1d9e0ZVkn42kPNGP2dgjIx6YFvSevj0zHvbWe2jdw==}
|
resolution: {integrity: sha512-slxCaKbYjEdFT/o2rH9xS1hf4uRDch1w7Uo+apxhZ+sf/1d9e0ZVkn42kPNGP2dgjIx6YFvSevj0zHvbWe2jdw==}
|
||||||
|
|
||||||
worktank@3.0.2:
|
|
||||||
resolution: {integrity: sha512-ry5gPtWnakOnUBAAa2aiyWZwAFJuBtd/MwZH6o9DXnQHD4AZvidtl2uTLrb2d3Zjy9D04n84lHJNnIETQl7tuA==}
|
|
||||||
|
|
||||||
wrap-ansi@7.0.0:
|
wrap-ansi@7.0.0:
|
||||||
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
|
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@@ -2136,6 +2166,25 @@ snapshots:
|
|||||||
wrap-ansi: 8.1.0
|
wrap-ansi: 8.1.0
|
||||||
wrap-ansi-cjs: wrap-ansi@7.0.0
|
wrap-ansi-cjs: wrap-ansi@7.0.0
|
||||||
|
|
||||||
|
'@jest/pattern@30.0.1':
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 24.12.0
|
||||||
|
jest-regex-util: 30.0.1
|
||||||
|
|
||||||
|
'@jest/schemas@30.0.5':
|
||||||
|
dependencies:
|
||||||
|
'@sinclair/typebox': 0.34.49
|
||||||
|
|
||||||
|
'@jest/types@30.3.0':
|
||||||
|
dependencies:
|
||||||
|
'@jest/pattern': 30.0.1
|
||||||
|
'@jest/schemas': 30.0.5
|
||||||
|
'@types/istanbul-lib-coverage': 2.0.6
|
||||||
|
'@types/istanbul-reports': 3.0.4
|
||||||
|
'@types/node': 24.12.0
|
||||||
|
'@types/yargs': 17.0.35
|
||||||
|
chalk: 4.1.2
|
||||||
|
|
||||||
'@mitata/counters@0.0.8': {}
|
'@mitata/counters@0.0.8': {}
|
||||||
|
|
||||||
'@napi-rs/wasm-runtime@0.2.12':
|
'@napi-rs/wasm-runtime@0.2.12':
|
||||||
@@ -2311,6 +2360,8 @@ snapshots:
|
|||||||
|
|
||||||
'@remusao/trie@2.1.0': {}
|
'@remusao/trie@2.1.0': {}
|
||||||
|
|
||||||
|
'@sinclair/typebox@0.34.49': {}
|
||||||
|
|
||||||
'@swc-node/core@1.14.1(@swc/core@1.13.5)(@swc/types@0.1.25)':
|
'@swc-node/core@1.14.1(@swc/core@1.13.5)(@swc/types@0.1.25)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@swc/core': 1.13.5
|
'@swc/core': 1.13.5
|
||||||
@@ -2405,6 +2456,16 @@ snapshots:
|
|||||||
|
|
||||||
'@types/estree@1.0.8': {}
|
'@types/estree@1.0.8': {}
|
||||||
|
|
||||||
|
'@types/istanbul-lib-coverage@2.0.6': {}
|
||||||
|
|
||||||
|
'@types/istanbul-lib-report@3.0.3':
|
||||||
|
dependencies:
|
||||||
|
'@types/istanbul-lib-coverage': 2.0.6
|
||||||
|
|
||||||
|
'@types/istanbul-reports@3.0.4':
|
||||||
|
dependencies:
|
||||||
|
'@types/istanbul-lib-report': 3.0.3
|
||||||
|
|
||||||
'@types/json-schema@7.0.15': {}
|
'@types/json-schema@7.0.15': {}
|
||||||
|
|
||||||
'@types/mocha@10.0.10': {}
|
'@types/mocha@10.0.10': {}
|
||||||
@@ -2422,6 +2483,12 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 24.12.0
|
'@types/node': 24.12.0
|
||||||
|
|
||||||
|
'@types/yargs-parser@21.0.3': {}
|
||||||
|
|
||||||
|
'@types/yargs@17.0.35':
|
||||||
|
dependencies:
|
||||||
|
'@types/yargs-parser': 21.0.3
|
||||||
|
|
||||||
'@types/yauzl-promise@4.0.1':
|
'@types/yauzl-promise@4.0.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 24.12.0
|
'@types/node': 24.12.0
|
||||||
@@ -2517,6 +2584,8 @@ snapshots:
|
|||||||
'@typescript-eslint/types': 8.57.2
|
'@typescript-eslint/types': 8.57.2
|
||||||
eslint-visitor-keys: 5.0.1
|
eslint-visitor-keys: 5.0.1
|
||||||
|
|
||||||
|
'@ungap/structured-clone@1.3.0': {}
|
||||||
|
|
||||||
'@unrs/resolver-binding-android-arm-eabi@1.11.1':
|
'@unrs/resolver-binding-android-arm-eabi@1.11.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -3201,8 +3270,6 @@ snapshots:
|
|||||||
|
|
||||||
ignore@7.0.5: {}
|
ignore@7.0.5: {}
|
||||||
|
|
||||||
immediato@1.1.0: {}
|
|
||||||
|
|
||||||
imurmurhash@0.1.4: {}
|
imurmurhash@0.1.4: {}
|
||||||
|
|
||||||
inherits@2.0.4: {}
|
inherits@2.0.4: {}
|
||||||
@@ -3242,18 +3309,31 @@ snapshots:
|
|||||||
|
|
||||||
isexe@2.0.0: {}
|
isexe@2.0.0: {}
|
||||||
|
|
||||||
isoconcurrency@1.0.0: {}
|
|
||||||
|
|
||||||
isotimer@1.0.0:
|
|
||||||
dependencies:
|
|
||||||
immediato: 1.1.0
|
|
||||||
|
|
||||||
jackspeak@3.4.3:
|
jackspeak@3.4.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@isaacs/cliui': 8.0.2
|
'@isaacs/cliui': 8.0.2
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@pkgjs/parseargs': 0.11.0
|
'@pkgjs/parseargs': 0.11.0
|
||||||
|
|
||||||
|
jest-regex-util@30.0.1: {}
|
||||||
|
|
||||||
|
jest-util@30.3.0:
|
||||||
|
dependencies:
|
||||||
|
'@jest/types': 30.3.0
|
||||||
|
'@types/node': 24.12.0
|
||||||
|
chalk: 4.1.2
|
||||||
|
ci-info: 4.4.0
|
||||||
|
graceful-fs: 4.2.11
|
||||||
|
picomatch: 4.0.3
|
||||||
|
|
||||||
|
jest-worker@30.3.0:
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 24.12.0
|
||||||
|
'@ungap/structured-clone': 1.3.0
|
||||||
|
jest-util: 30.3.0
|
||||||
|
merge-stream: 2.0.0
|
||||||
|
supports-color: 8.1.1
|
||||||
|
|
||||||
js-yaml@4.1.0:
|
js-yaml@4.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
argparse: 2.0.1
|
argparse: 2.0.1
|
||||||
@@ -3294,6 +3374,8 @@ snapshots:
|
|||||||
|
|
||||||
lru-cache@10.4.3: {}
|
lru-cache@10.4.3: {}
|
||||||
|
|
||||||
|
merge-stream@2.0.0: {}
|
||||||
|
|
||||||
mime@3.0.0: {}
|
mime@3.0.0: {}
|
||||||
|
|
||||||
mimic-response@3.1.0: {}
|
mimic-response@3.1.0: {}
|
||||||
@@ -3449,8 +3531,6 @@ snapshots:
|
|||||||
|
|
||||||
prelude-ls@1.2.1: {}
|
prelude-ls@1.2.1: {}
|
||||||
|
|
||||||
promise-make-naked@3.0.2: {}
|
|
||||||
|
|
||||||
pump@3.0.3:
|
pump@3.0.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
end-of-stream: 1.4.5
|
end-of-stream: 1.4.5
|
||||||
@@ -3772,8 +3852,6 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
webworker-shim@1.1.4: {}
|
|
||||||
|
|
||||||
which@2.0.2:
|
which@2.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
isexe: 2.0.0
|
isexe: 2.0.0
|
||||||
@@ -3784,13 +3862,6 @@ snapshots:
|
|||||||
|
|
||||||
workerpool@9.3.3: {}
|
workerpool@9.3.3: {}
|
||||||
|
|
||||||
worktank@3.0.2:
|
|
||||||
dependencies:
|
|
||||||
isoconcurrency: 1.0.0
|
|
||||||
isotimer: 1.0.0
|
|
||||||
promise-make-naked: 3.0.2
|
|
||||||
webworker-shim: 1.1.4
|
|
||||||
|
|
||||||
wrap-ansi@7.0.0:
|
wrap-ansi@7.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
ansi-styles: 4.3.0
|
ansi-styles: 4.3.0
|
||||||
|
|||||||
Reference in New Issue
Block a user