New build infra: Build for Clash (#11)

This commit is contained in:
Sukka 2023-09-11 00:51:35 +08:00 committed by GitHub
parent fa7e2f775c
commit df4a10e180
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 661 additions and 237 deletions

15
.gitignore vendored
View File

@ -4,18 +4,7 @@ node_modules
.wireit
public
List/domainset/reject.conf
List/domainset/cdn.conf
List/domainset/reject_phishing.conf
List/domainset/reject_sukka.conf
List/domainset/apple_cdn.conf
List/domainset/speedtest.conf
List/non_ip/cdn.conf
List/non_ip/domestic.conf
List/non_ip/apple_cdn.conf
List/ip/telegram.conf
List/ip/reject.conf
List/ip/china_ip.conf
List/internal/
List
Clash
Modules/sukka_local_dns_mapping.sgmodule

View File

@ -4,7 +4,7 @@ const { isIPv4, isIPv6 } = require('net');
const { compareAndWriteFile } = require('./lib/string-array-compare');
const { withBannerArray } = require('./lib/with-banner');
const { fetchRemoteTextAndCreateReadlineInterface, readFileByLine } = require('./lib/fetch-remote-text-by-line');
const { minifyRules } = require('./lib/minify-rules');
const { surgeRulesetToClashClassicalTextRuleset } = require('./lib/clash');
(async () => {
console.time('Total Time - build-anti-bogus-domain');
@ -21,7 +21,6 @@ const { minifyRules } = require('./lib/minify-rules');
console.timeEnd('* Download bogus-nxdomain-list');
const filePath = path.resolve(__dirname, '../Source/ip/reject.conf');
const resultPath = path.resolve(__dirname, '../List/ip/reject.conf');
/** @type {string[]} */
const result = [];
@ -39,24 +38,37 @@ const { minifyRules } = require('./lib/minify-rules');
}
}
await compareAndWriteFile(
withBannerArray(
'Sukka\'s Surge Rules - Anti Bogus Domain',
[
'License: AGPL 3.0',
'Homepage: https://ruleset.skk.moe',
'GitHub: https://github.com/SukkaW/Surge',
'',
'This file contains known addresses that are hijacking NXDOMAIN results returned by DNS servers.',
'',
'Data from:',
' - https://github.com/felixonmars/dnsmasq-china-list'
],
new Date(),
minifyRules(result)
const description = [
'License: AGPL 3.0',
'Homepage: https://ruleset.skk.moe',
'GitHub: https://github.com/SukkaW/Surge',
'',
'This file contains known addresses that are hijacking NXDOMAIN results returned by DNS servers.',
'',
'Data from:',
' - https://github.com/felixonmars/dnsmasq-china-list'
];
await Promise.all([
compareAndWriteFile(
withBannerArray(
'Sukka\'s Ruleset - Anti Bogus Domain',
description,
new Date(),
result
),
path.resolve(__dirname, '../List/ip/reject.conf')
),
resultPath
);
compareAndWriteFile(
withBannerArray(
'Sukka\'s Ruleset - Anti Bogus Domain',
description,
new Date(),
surgeRulesetToClashClassicalTextRuleset(result)
),
path.resolve(__dirname, '../Clash/ip/reject.txt')
)
]);
console.timeEnd('Total Time - build-anti-bogus-domain');
})();

View File

@ -4,48 +4,63 @@ const { compareAndWriteFile } = require('./lib/string-array-compare');
const { withBannerArray } = require('./lib/with-banner');
const { parseFelixDnsmasq } = require('./lib/parse-dnsmasq');
const { surgeRulesetToClashClassicalTextRuleset, surgeDomainsetToClashDomainset } = require('./lib/clash');
(async () => {
console.time('Total Time - build-apple-cdn-conf');
const res = await parseFelixDnsmasq('https://raw.githubusercontent.com/felixonmars/dnsmasq-china-list/master/apple.china.conf');
const description = [
'License: AGPL 3.0',
'Homepage: https://ruleset.skk.moe',
'GitHub: https://github.com/SukkaW/Surge',
'',
'This file contains Apple\'s domains using their China mainland CDN servers.',
'',
'Data from:',
' - https://github.com/felixonmars/dnsmasq-china-list'
];
const ruleset = res.map(domain => `DOMAIN-SUFFIX,${domain}`);
const domainset = res.map(i => `.${i}`);
await Promise.all([
compareAndWriteFile(
withBannerArray(
'Sukka\'s Surge Rules - Apple CDN',
[
'License: AGPL 3.0',
'Homepage: https://ruleset.skk.moe',
'GitHub: https://github.com/SukkaW/Surge',
'',
'This file contains Apple\'s domains using their China mainland CDN servers.',
'',
'Data from:',
' - https://github.com/felixonmars/dnsmasq-china-list'
],
'Sukka\'s Ruleset - Apple CDN',
description,
new Date(),
res.map(domain => `DOMAIN-SUFFIX,${domain}`)
ruleset
),
path.resolve(__dirname, '../List/non_ip/apple_cdn.conf')
),
compareAndWriteFile(
withBannerArray(
'Sukka\'s Surge Rules - Apple CDN',
[
'License: AGPL 3.0',
'Homepage: https://ruleset.skk.moe',
'GitHub: https://github.com/SukkaW/Surge',
'',
'This file contains Apple\'s domains using their China mainland CDN servers.',
'',
'Data from:',
' - https://github.com/felixonmars/dnsmasq-china-list'
],
'Sukka\'s Ruleset - Apple CDN',
description,
new Date(),
res.map(i => `.${i}`)
surgeRulesetToClashClassicalTextRuleset(ruleset)
),
path.resolve(__dirname, '../Clash/non_ip/apple_cdn.txt')
),
compareAndWriteFile(
withBannerArray(
'Sukka\'s Ruleset - Apple CDN',
description,
new Date(),
domainset
),
path.resolve(__dirname, '../List/domainset/apple_cdn.conf')
),
compareAndWriteFile(
withBannerArray(
'Sukka\'s Ruleset - Apple CDN',
description,
new Date(),
surgeDomainsetToClashDomainset(domainset)
),
path.resolve(__dirname, '../Clash/domainset/apple_cdn.txt')
)
]);

View File

@ -3,10 +3,9 @@ const path = require('path');
const { compareAndWriteFile } = require('./lib/string-array-compare');
const { withBannerArray } = require('./lib/with-banner');
const { minifyRules } = require('./lib/minify-rules');
const { domainDeduper } = require('./lib/domain-deduper');
const { processLine } = require('./lib/process-line');
const { fetchRemoteTextAndCreateReadlineInterface, readFileByLine } = require('./lib/fetch-remote-text-by-line');
const Trie = require('./lib/trie');
const { surgeRulesetToClashClassicalTextRuleset } = require('./lib/clash');
(async () => {
console.time('Total Time - build-cdn-conf');
@ -40,51 +39,33 @@ const Trie = require('./lib/trie');
}
}
/**
* Dedupe cdn.conf
*/
/** @type {Set<string>} */
const cdnDomains = new Set();
for await (const line of readFileByLine(
path.resolve(__dirname, '../Source/domainset/cdn.conf')
)) {
const l = processLine(line);
if (l) {
cdnDomains.add(l);
}
}
const description = [
'License: AGPL 3.0',
'Homepage: https://ruleset.skk.moe',
'GitHub: https://github.com/SukkaW/Surge',
'',
'This file contains object storage and static assets CDN domains.'
];
const ruleset = minifyRules(cdnDomainsList);
await Promise.all([
compareAndWriteFile(
withBannerArray(
'Sukka\'s Surge Rules - CDN Domains',
[
'License: AGPL 3.0',
'Homepage: https://ruleset.skk.moe',
'GitHub: https://github.com/SukkaW/Surge',
'',
'This file contains object storage and static assets CDN domains.'
],
'Sukka\'s Ruleset - CDN Domains',
description,
new Date(),
minifyRules(cdnDomainsList)
ruleset
),
path.resolve(__dirname, '../List/non_ip/cdn.conf')
),
compareAndWriteFile(
withBannerArray(
'Sukka\'s Surge Rules - CDN Domains',
[
'License: AGPL 3.0',
'Homepage: https://ruleset.skk.moe',
'GitHub: https://github.com/SukkaW/Surge',
'',
'This file contains object storage and static assets CDN domains.'
],
'Sukka\'s Ruleset - CDN Domains',
description,
new Date(),
minifyRules(domainDeduper(Array.from(cdnDomains)))
surgeRulesetToClashClassicalTextRuleset(ruleset)
),
path.resolve(__dirname, '../List/domainset/cdn.conf')
path.resolve(__dirname, '../Clash/non_ip/cdn.txt')
)
]);

View File

@ -28,21 +28,38 @@ const EXCLUDE_CIDRS = [
const filteredCidr = excludeCidrs(Array.from(cidr), EXCLUDE_CIDRS, true);
console.log('After Merge:', filteredCidr.length);
await compareAndWriteFile(
withBannerArray(
'Sukka\'s Surge Rules - Mainland China IPv4 CIDR',
[
'License: CC BY-SA 2.0',
'Homepage: https://ruleset.skk.moe',
'GitHub: https://github.com/SukkaW/Surge',
'',
'Data from https://misaka.io (misakaio @ GitHub)'
],
new Date(),
filteredCidr.map(i => `IP-CIDR,${i}`)
await Promise.all([
compareAndWriteFile(
withBannerArray(
'Sukka\'s Ruleset - Mainland China IPv4 CIDR',
[
'License: CC BY-SA 2.0',
'Homepage: https://ruleset.skk.moe',
'GitHub: https://github.com/SukkaW/Surge',
'',
'Data from https://misaka.io (misakaio @ GitHub)'
],
new Date(),
filteredCidr.map(i => `IP-CIDR,${i}`)
),
pathResolve(__dirname, '../List/ip/china_ip.conf')
),
pathResolve(__dirname, '../List/ip/china_ip.conf')
);
compareAndWriteFile(
withBannerArray(
'Sukka\'s Ruleset - Mainland China IPv4 CIDR',
[
'License: CC BY-SA 2.0',
'Homepage: https://ruleset.skk.moe',
'GitHub: https://github.com/SukkaW/Surge',
'',
'Data from https://misaka.io (misakaio @ GitHub)'
],
new Date(),
filteredCidr
),
pathResolve(__dirname, '../Clash/ip/china_ip.txt')
)
]);
console.timeEnd('Total Time - build-chnroutes-cidr');
})();

View File

@ -6,6 +6,7 @@ const { processLine } = require('./lib/process-line');
const { withBannerArray } = require('./lib/with-banner');
const { compareAndWriteFile } = require('./lib/string-array-compare');
const domainSorter = require('./lib/stable-sort-domain');
const { surgeRulesetToClashClassicalTextRuleset } = require('./lib/clash');
(async () => {
const rl = readFileByLine(path.resolve(__dirname, '../Source/non_ip/domestic.conf'));
@ -25,22 +26,33 @@ const domainSorter = require('./lib/stable-sort-domain');
.map((domain) => `DOMAIN-SUFFIX,${domain}`)
);
const rulesetDescription = [
'License: AGPL 3.0',
'Homepage: https://ruleset.skk.moe',
'GitHub: https://github.com/SukkaW/Surge',
'',
'This file contains known addresses that are avaliable in the Mainland China.'
];
await Promise.all([
compareAndWriteFile(
withBannerArray(
'Sukka\'s Surge Rules - Domestic Domain',
[
'License: AGPL 3.0',
'Homepage: https://ruleset.skk.moe',
'GitHub: https://github.com/SukkaW/Surge',
'',
'This file contains known addresses that are avaliable in the Mainland China.'
],
'Sukka\'s Ruleset - Domestic Domains',
rulesetDescription,
new Date(),
results
),
path.resolve(__dirname, '../List/non_ip/domestic.conf')
),
compareAndWriteFile(
withBannerArray(
'Sukka\'s Ruleset - Domestic Domains',
rulesetDescription,
new Date(),
surgeRulesetToClashClassicalTextRuleset(results)
),
path.resolve(__dirname, '../Clash/non_ip/domestic.txt')
),
compareAndWriteFile(
[
'#!name=[Sukka] Local DNS Mapping',

View File

@ -5,6 +5,7 @@ const { withBannerArray } = require('./lib/with-banner.js');
const { compareAndWriteFile } = require('./lib/string-array-compare');
const { processLine } = require('./lib/process-line.js');
const domainSorter = require('./lib/stable-sort-domain');
const { surgeDomainsetToClashDomainset } = require('./lib/clash.js');
const WHITELIST_DOMAIN = new Set([
'w3s.link',
@ -141,21 +142,34 @@ const BLACK_TLD = new Set([
results.sort(domainSorter);
await compareAndWriteFile(
withBannerArray(
'Sukka\'s Surge Rules - Reject Phishing',
[
'License: AGPL 3.0',
'Homepage: https://ruleset.skk.moe',
'GitHub: https://github.com/SukkaW/Surge',
'',
'The domainset supports enhanced phishing protection',
'Build from:',
' - https://gitlab.com/malware-filter/phishing-filter'
],
new Date(),
results
const description = [
'License: AGPL 3.0',
'Homepage: https://ruleset.skk.moe',
'GitHub: https://github.com/SukkaW/Surge',
'',
'The domainset supports enhanced phishing protection',
'Build from:',
' - https://gitlab.com/malware-filter/phishing-filter'
];
await Promise.all([
compareAndWriteFile(
withBannerArray(
'Sukka\'s Ruleset - Reject Phishing',
description,
new Date(),
results
),
path.resolve(__dirname, '../List/domainset/reject_phishing.conf')
),
path.resolve(__dirname, '../List/domainset/reject_phishing.conf')
);
compareAndWriteFile(
withBannerArray(
'Sukka\'s Ruleset - Reject Phishing',
description,
new Date(),
surgeDomainsetToClashDomainset(results)
),
path.resolve(__dirname, '../Clash/domainset/reject_phishing.txt')
)
]);
})();

View File

@ -16,6 +16,7 @@ const { domainDeduper } = require('./lib/domain-deduper');
const createKeywordFilter = require('./lib/aho-corasick');
const { readFileByLine } = require('./lib/fetch-remote-text-by-line');
const domainSorter = require('./lib/stable-sort-domain');
const { surgeDomainsetToClashDomainset } = require('./lib/clash');
/** Whitelists */
const filterRuleWhitelistDomainSets = new Set(PREDEFINED_WHITELIST);
@ -196,26 +197,38 @@ const domainSuffixSet = new Set();
return acc;
}, {});
const description = [
'License: AGPL 3.0',
'Homepage: https://ruleset.skk.moe',
'GitHub: https://github.com/SukkaW/Surge',
'',
'The domainset supports AD blocking, tracking protection, privacy protection, anti-phishing, anti-mining',
'',
'Build from:',
...HOSTS.map(host => ` - ${host[0]}`),
...ADGUARD_FILTERS.map(filter => ` - ${Array.isArray(filter) ? filter[0] : filter}`)
];
const domainset = dudupedDominArray.sort(domainSorter);
await Promise.all([
compareAndWriteFile(
withBannerArray(
'Sukka\'s Surge Rules - Reject Base',
[
'License: AGPL 3.0',
'Homepage: https://ruleset.skk.moe',
'GitHub: https://github.com/SukkaW/Surge',
'',
'The domainset supports AD blocking, tracking protection, privacy protection, anti-phishing, anti-mining',
'',
'Build from:',
...HOSTS.map(host => ` - ${host[0]}`),
...ADGUARD_FILTERS.map(filter => ` - ${Array.isArray(filter) ? filter[0] : filter}`)
],
'Sukka\'s Ruleset - Reject Base',
description,
new Date(),
dudupedDominArray.sort(domainSorter)
domainset
),
pathResolve(__dirname, '../List/domainset/reject.conf')
),
compareAndWriteFile(
withBannerArray(
'Sukka\'s Ruleset - Reject Base',
description,
new Date(),
surgeDomainsetToClashDomainset(domainset)
),
pathResolve(__dirname, '../Clash/domainset/reject.txt')
),
fs.promises.writeFile(
pathResolve(__dirname, '../List/internal/reject-stats.txt'),
Object.entries(rejectDomainsStats)

View File

@ -6,6 +6,7 @@ const { compareAndWriteFile } = require('./lib/string-array-compare');
const domainSorter = require('./lib/stable-sort-domain');
const { Sema } = require('async-sema');
const { surgeDomainsetToClashDomainset } = require('./lib/clash');
const s = new Sema(2);
/**
@ -107,19 +108,31 @@ const querySpeedtestApi = async (keyword) => {
}
}
const reduped = domainDeduper(Array.from(domains)).sort(domainSorter);
const deduped = domainDeduper(Array.from(domains)).sort(domainSorter);
const description = [
'License: AGPL 3.0',
'Homepage: https://ruleset.skk.moe',
'GitHub: https://github.com/SukkaW/Surge'
];
await compareAndWriteFile(
withBannerArray(
'Sukka\'s Surge Rules - Speedtest Domains',
[
'License: AGPL 3.0',
'Homepage: https://ruleset.skk.moe',
'GitHub: https://github.com/SukkaW/Surge'
],
new Date(),
reduped
await Promise.all([
compareAndWriteFile(
withBannerArray(
'Sukka\'s Ruleset - Speedtest Domains',
description,
new Date(),
deduped
),
path.resolve(__dirname, '../List/domainset/speedtest.conf')
),
path.resolve(__dirname, '../List/domainset/speedtest.conf')
);
compareAndWriteFile(
withBannerArray(
'Sukka\'s Ruleset - Speedtest Domains',
description,
new Date(),
surgeDomainsetToClashDomainset(deduped)
),
path.resolve(__dirname, '../Clash/domainset/speedtest.txt')
)
]);
})();

View File

@ -5,6 +5,7 @@ const { isIPv4, isIPv6 } = require('net');
const { withBannerArray } = require('./lib/with-banner');
const { processLine } = require('./lib/process-line');
const { compareAndWriteFile } = require('./lib/string-array-compare');
const { surgeRulesetToClashClassicalTextRuleset } = require('./lib/clash');
(async () => {
console.time('Total Time - build-telegram-cidr');
@ -34,21 +35,34 @@ const { compareAndWriteFile } = require('./lib/string-array-compare');
throw new Error('Failed to fetch data!');
}
await compareAndWriteFile(
withBannerArray(
'Sukka\'s Surge Rules - Telegram IP CIDR',
[
'License: AGPL 3.0',
'Homepage: https://ruleset.skk.moe',
'GitHub: https://github.com/SukkaW/Surge',
'Data from:',
' - https://core.telegram.org/resources/cidr.txt'
],
date,
results
const description = [
'License: AGPL 3.0',
'Homepage: https://ruleset.skk.moe',
'GitHub: https://github.com/SukkaW/Surge',
'Data from:',
' - https://core.telegram.org/resources/cidr.txt'
];
await Promise.all([
compareAndWriteFile(
withBannerArray(
'Sukka\'s Ruleset - Telegram IP CIDR',
description,
date,
results
),
path.resolve(__dirname, '../List/ip/telegram.conf')
),
path.resolve(__dirname, '../List/ip/telegram.conf')
);
compareAndWriteFile(
withBannerArray(
'Sukka\'s Ruleset - Telegram IP CIDR',
description,
date,
surgeRulesetToClashClassicalTextRuleset(results)
),
path.resolve(__dirname, '../Clash/ip/telegram.txt')
)
]);
console.timeEnd('Total Time - build-telegram-cidr');
})();

179
Build/build.js Normal file
View File

@ -0,0 +1,179 @@
// @ts-check
const path = require('path');
const { PathScurry } = require('path-scurry');
const { readFileByLine } = require('./lib/fetch-remote-text-by-line');
const { processLine } = require('./lib/process-line');
const { compareAndWriteFile } = require('./lib/string-array-compare');
const { withBannerArray } = require('./lib/with-banner');
const { domainDeduper } = require('./lib/domain-deduper');
const { surgeRulesetToClashClassicalTextRuleset, surgeDomainsetToClashDomainset } = require('./lib/clash');
const MAGIC_COMMAND_SKIP = '# $ custom_build_script';
const MAGIC_COMMAND_TITLE = '# $ meta_title ';
const MAGIC_COMMAND_DESCRIPTION = '# $ meta_description ';
const sourceDir = path.resolve(__dirname, '../Source');
const outputSurgeDir = path.resolve(__dirname, '../List');
const outputClashDir = path.resolve(__dirname, '../Clash');
(async () => {
/** @type {Promise<void>[]} */
const promises = [];
const pw = new PathScurry(sourceDir);
for await (const entry of pw) {
if (entry.isFile()) {
if (path.extname(entry.name) === '.js') {
continue;
}
const relativePath = entry.relative();
if (relativePath.startsWith('domainset/')) {
promises.push(transformDomainset(entry.fullpath(), relativePath));
continue;
}
if (
relativePath.startsWith('ip/')
|| relativePath.startsWith('non_ip/')
) {
promises.push(transformRuleset(entry.fullpath(), relativePath));
continue;
}
}
}
await Promise.all(promises);
})();
/**
* @param {string} sourcePath
*/
const processFile = async (sourcePath) => {
/** @type {Set<string>} */
const lines = new Set();
let title = '';
/** @type {string[]} */
const descriptions = [];
for await (const line of readFileByLine(sourcePath)) {
if (line === MAGIC_COMMAND_SKIP) {
return;
}
if (line.startsWith(MAGIC_COMMAND_TITLE)) {
title = line.slice(MAGIC_COMMAND_TITLE.length).trim();
continue;
}
if (line.startsWith(MAGIC_COMMAND_DESCRIPTION)) {
descriptions.push(line.slice(MAGIC_COMMAND_DESCRIPTION.length).trim());
continue;
}
const l = processLine(line);
if (l) {
lines.add(l);
}
}
return /** @type {const} */ ([title, descriptions, lines]);
};
/**
* @param {string} sourcePath
* @param {string} relativePath
*/
async function transformDomainset(sourcePath, relativePath) {
const res = await processFile(sourcePath);
if (!res) return;
const [title, descriptions, lines] = res;
const deduped = domainDeduper(Array.from(lines));
const description = [
'License: AGPL 3.0',
'Homepage: https://ruleset.skk.moe',
'GitHub: https://github.com/SukkaW/Surge',
...(
descriptions.length
? ['', ...descriptions]
: []
)
];
await Promise.all([
// Surge DOMAIN-SET
compareAndWriteFile(
withBannerArray(
title,
description,
new Date(),
deduped
),
path.resolve(outputSurgeDir, relativePath)
),
// Clash domain text
compareAndWriteFile(
withBannerArray(
title,
description,
new Date(),
surgeDomainsetToClashDomainset(deduped)
),
// change path extname to .txt
path.resolve(outputClashDir, `${relativePath.slice(0, -path.extname(relativePath).length)}.txt`)
)
]);
}
/**
* Output Surge RULE-SET and Clash classical text format
*
* @param {string} sourcePath
* @param {string} relativePath
*/
async function transformRuleset(sourcePath, relativePath) {
const res = await processFile(sourcePath);
if (!res) return;
const [title, descriptions, set] = res;
const description = [
'License: AGPL 3.0',
'Homepage: https://ruleset.skk.moe',
'GitHub: https://github.com/SukkaW/Surge',
...(
descriptions.length
? ['', ...descriptions]
: []
)
];
const lines = Array.from(set);
const clashSupported = surgeRulesetToClashClassicalTextRuleset(set);
await Promise.all([
// Surge RULE-SET
compareAndWriteFile(
withBannerArray(
title,
description,
new Date(),
lines
),
path.resolve(outputSurgeDir, relativePath)
),
// Clash domainset
compareAndWriteFile(
withBannerArray(
title,
description,
new Date(),
clashSupported
),
// change path extname to .txt
path.resolve(outputClashDir, `${relativePath.slice(0, -path.extname(relativePath).length)}.txt`)
)
]);
}

View File

@ -7,6 +7,7 @@ const { tmpdir } = require('os');
const { Readable } = require('stream');
const { pipeline } = require('stream/promises');
const { readFileByLine } = require('./lib/fetch-remote-text-by-line');
const { isCI } = require('ci-info');
const fileExists = (path) => {
return fs.promises.access(path, fs.constants.F_OK)
@ -19,18 +20,22 @@ const fileExists = (path) => {
let allFileExists = true;
for await (const line of readFileByLine(resolve(__dirname, '../.gitignore'))) {
if (
(
line.startsWith('List/')
|| line.startsWith('Modules/')
) && !line.endsWith('/')
) {
allFileExists = await fileExists(join(__dirname, '..', line));
filesList.push(line);
if (isCI) {
allFileExists = false;
} else {
for await (const line of readFileByLine(resolve(__dirname, '../.gitignore'))) {
if (
(
// line.startsWith('List/')
line.startsWith('Modules/')
) && !line.endsWith('/')
) {
allFileExists = await fileExists(join(__dirname, '..', line));
filesList.push(line);
if (!allFileExists) {
console.log(`File not exists: ${line}`);
if (!allFileExists) {
console.log(`File not exists: ${line}`);
}
}
}
}

34
Build/lib/clash.js Normal file
View File

@ -0,0 +1,34 @@
// @ts-check
const _Trie = require('mnemonist/trie');
const Trie = _Trie.default || _Trie;
const CLASH_SUPPORTED_RULE_TYPE = [
'DOMAIN-SUFFIX',
'DOMAIN-KEYWORD',
'DOMAIN',
'SRC-IP-CIDR',
'GEOIP',
'IP-CIDR',
'IP-CIDR6',
'DST-PORT',
'SRC-PORT'
];
/**
* @param {string[] | Set<string>} rules
*/
const surgeRulesetToClashClassicalTextRuleset = (rules) => {
const trie = Trie.from(rules);
return CLASH_SUPPORTED_RULE_TYPE.map(
type => trie.find(`${type},`)
).flat();
};
module.exports.surgeRulesetToClashClassicalTextRuleset = surgeRulesetToClashClassicalTextRuleset;
/**
* @param {string[]} domainset
*/
const surgeDomainsetToClashDomainset = (domainset) => {
return domainset.map(i => (i[0] === '.' ? `+${i}` : i));
};
module.exports.surgeDomainsetToClashDomainset = surgeDomainsetToClashDomainset;

View File

@ -1,6 +0,0 @@
mask.icloud.com
mask-h2.icloud.com
mask-api.icloud.com
mask.apple-dns.net
mask-api.fe.apple-dns.net
mask-t.apple-dns.net

View File

@ -1 +0,0 @@
# deprecated

View File

@ -1,2 +0,0 @@
# >> Tencent AIA
IP-CIDR,162.14.0.0/18,no-resolve

View File

@ -1 +0,0 @@
# Deprecated

View File

@ -1,6 +1,6 @@
# Surge
# Sukka Ruleset
由 [Sukka](https://skk.moe) 搜集、整理、维护的、个人自用的、适用于 [Surge](https://nssurge.com/) 的 Rule Snippet。
由 [Sukka](https://skk.moe) 搜集、整理、维护的、个人自用的、适用于 [Surge](https://nssurge.com/) 和 [Clash Premium](https://dreamacro.github.io/clash/) 的 Rule Snippet。
## 条款和协议
@ -18,6 +18,13 @@
#### 广告拦截 / 隐私保护 / Malware 拦截 / Phiishing 拦截
- 自动生成
- 数据来源、白名单域名列表和生成方式,请参考 [`build-reject-domainset.js`](Build/build-reject-domainset.js)
- 仅建议在 Surge for Mac 上使用,移动平台请使用专门的工具(如 ADGuard for Android/iOS以获得更好的性能
- 不能替代浏览器广告屏蔽扩展(如 uBlock Origin
**Surge**
```ini
RULE-SET,https://ruleset.skk.moe/List/non_ip/reject.conf,REJECT
DOMAIN-SET,https://ruleset.skk.moe/List/domainset/reject.conf,REJECT-TINYGIF
@ -25,80 +32,97 @@ DOMAIN-SET,https://ruleset.skk.moe/List/domainset/reject_phishing.conf,REJECT
RULE-SET,https://ruleset.skk.moe/List/ip/reject.conf,REJECT-DROP
```
- 自动生成
- 数据来源、白名单域名列表和生成方式,请参考 [`build-reject-domainset.js`](Build/build-reject-domainset.js)
- 仅建议在 Surge for Mac 上使用,移动平台请使用专门的工具(如 ADGuard for Android/iOS以获得更好的性能
- 不能替代浏览器广告屏蔽扩展(如 uBlock Origin
**Clash**
#### 搜狗输入法
```ini
RULE-SET,https://ruleset.skk.moe/List/non_ip/sogouinput.conf,
```
- 人工维护
- 该规则组用于避免搜狗输入法将你输入的每一个字符自动收集并通过 `get.sogou.com/q` 等域名回传
- 影响搜狗输入法账号同步、词库更新、问题反馈
**Surge**
```ini
RULE-SET,https://ruleset.skk.moe/List/non_ip/sogouinput.conf,
```
#### 常见静态 CDN
- 自动生成 + 人工维护
- 包含所有常见静态资源 CDN 域名、对象存储域名
- 如果你正在使用商业性质的公共代理服务、且你的服务商提供按低倍率结算流量消耗的节点,可使用上述规则组将流量分配给这部分节点
**Surge**
```ini
DOMAIN-SET,https://ruleset.skk.moe/List/domainset/cdn.conf,[Replace with your policy]
RULE-SET,https://ruleset.skk.moe/List/non_ip/cdn.conf,[Replace with your policy]
```
- 自动生成 + 人工维护
- 包含所有常见静态资源 CDN 域名、对象存储域名
- 如果你正在使用商业性质的公共代理服务、且你的服务商提供按低倍率结算流量消耗的节点,可使用上述规则组将流量分配给这部分节点
#### 流媒体
- 人工维护
- 包含 4gtv、AbemaTV、All4、Amazon Prime Video、Apple TV、Apple Music TV、Bahamut、BBC、Bilibili Intl、DAZN、Deezer、Disney+、Discovery+、DMM、encoreTVB、Fox Now、Fox+、HBO GO/Now/Max/Asia、Hulu、HWTV、JOOX、Jwplayer、KKBOX、KKTV、Line TV、Naver TV、myTV Super、Netflix、niconico、Now E、Paramount+、PBS、Peacock、Pandora、PBS、Pornhub、SoundCloud、PBS、Spotify、TaiwanGood、Tiktok Intl、Twitch、ViuTV、ShowTime、iQiYi Global、Himalaya Podcast、Overcast、WeTV 的规则组
**Surge**
```ini
RULE-SET,https://ruleset.skk.moe/List/non_ip/stream.conf,[Replace with your policy]
RULE-SET,https://ruleset.skk.moe/List/ip/stream.conf,[Replace with your policy]
```
- 人工维护
- 包含 4gtv、AbemaTV、All4、Amazon Prime Video、Apple TV、Apple Music TV、Bahamut、BBC、Bilibili Intl、DAZN、Deezer、Disney+、Discovery+、DMM、encoreTVB、Fox Now、Fox+、HBO GO/Now/Max/Asia、Hulu、HWTV、JOOX、Jwplayer、KKBOX、KKTV、Line TV、Naver TV、myTV Super、Netflix、niconico、Now E、Paramount+、PBS、Peacock、Pandora、PBS、Pornhub、SoundCloud、PBS、Spotify、TaiwanGood、Tiktok Intl、Twitch、ViuTV、ShowTime、iQiYi Global、Himalaya Podcast、Overcast、WeTV 的规则组
#### Telegram
- 域名规则 人工维护
- IP CIDR 规则 自动生成(数据来源:[`https://core.telegram.org/resources/cidr.txt`](https://core.telegram.org/resources/cidr.txt)
**Surge**
```ini
RULE-SET,https://ruleset.skk.moe/List/non_ip/telegram.conf,[Replace with your policy]
RULE-SET,https://ruleset.skk.moe/List/ip/telegram.conf,[Replace with your policy]
```
- 域名规则 人工维护
- IP CIDR 规则 自动生成(数据来源:[`https://core.telegram.org/resources/cidr.txt`](https://core.telegram.org/resources/cidr.txt)
#### Apple CDN
```ini
DOMAIN-SET,https://ruleset.skk.moe/List/domainset/apple_cdn.conf,[Replace with your policy]
```
- 自动生成
- 规则组包含 Apple, Inc. 在中华人民共和国完成工信部 ICP 备案和公安网备、且在中华人民共和国境内提供 HTTP 服务的域名,如果由于某些原因需要代理其中部分域名,请自行针对域名编写规则、并添加到当前规则组之前。
- 数据来源 [`felixonmars/dnsmasq-china-list`](https://github.com/felixonmars/dnsmasq-china-list/blob/master/apple.china.conf)
**Surge**
```ini
DOMAIN-SET,https://ruleset.skk.moe/List/domainset/apple_cdn.conf,[Replace with your policy]
```
#### Apple Service
- 人工维护
**Surge**
```ini
RULE-SET,https://ruleset.skk.moe/List/non_ip/apple_services.conf,[Replace with your policy]
```
#### 网易云音乐
- 人工维护
#### 网易云音乐
**Surge**
```ini
RULE-SET,https://ruleset.skk.moe/List/non_ip/neteasemusic.conf,[Replace with your policy]
RULE-SET,https://ruleset.skk.moe/List/ip/neteasemusic.conf,[Replace with your policy]
```
#### Misc
- 人工维护
#### Misc
**Surge**
```ini
RULE-SET,https://ruleset.skk.moe/List/non_ip/domestic.conf,[Replace with your policy]
@ -108,17 +132,17 @@ RULE-SET,https://ruleset.skk.moe/List/non_ip/global.conf,PROXY
RULE-SET,https://ruleset.skk.moe/List/ip/domestic.conf,[Replace with your policy]
```
- 人工维护
#### chnroute CIDR
- 自动生成
- [原始数据](https://github.com/misakaio/chnroutes2) 由 Misaka Network, Inc.、DMIT, Inc.、NEROCLOUD Ltd.、Rainbow network Ltd.、MOACK Co., Ltd. 提供,由 Misaka Network, Inc. 整理,以 [CC BY-SA 2.0](https://creativecommons.org/licenses/by-sa/2.0/) 协议发布
**Surge**
```ini
RULE-SET,https://ruleset.skk.moe/List/ip/china_ip.conf,[Replace with your policy]
```
- 自动生成
- [原始数据](https://github.com/misakaio/chnroutes2) 由 Misaka Network, Inc.、DMIT, Inc.、NEROCLOUD Ltd.、Rainbow network Ltd.、MOACK Co., Ltd. 提供,由 Misaka Network, Inc. 整理,以 [CC BY-SA 2.0](https://creativecommons.org/licenses/by-sa/2.0/) 协议发布
## Surge 模块列表
- Sukka URL Rewrite: `https://ruleset.skk.moe/Modules/sukka_url_rewrite.sgmodule`
@ -139,7 +163,9 @@ RULE-SET,https://ruleset.skk.moe/List/ip/china_ip.conf,[Replace with your policy
**有适用于 Clash 的规则组吗?**
没有。如果 [Clash Premium 提供了对 `DOMAIN-SET` 格式的支持](https://github.com/Dreamacro/clash/issues/1838),未来可能会有。
~~没有。如果 [Clash Premium 提供了对 `DOMAIN-SET` 格式的支持](https://github.com/Dreamacro/clash/issues/1838),未来可能会有。~~
有。
**有适用于 Shadowrocket、Quantumult X、Loon、V2RayNG 的规则组吗?**

View File

@ -1,3 +1,6 @@
# $ meta_title Sukka's Ruleset - CDN Domains
# $ meta_description This file contains object storage and static assets CDN domains.
# >> Sukka
ruleset.skk.moe
pic.skk.moe

View File

@ -1,3 +1,6 @@
# $ meta_title Sukka's Ruleset - Large Files Hosting Domains
# $ meta_description This file contains domains for software updating & large file hosting.
.1fichier.com
.1fichier.info
.nitro.download

View File

@ -0,0 +1,9 @@
# $ meta_title Sukka's Ruleset - iCloud Private Relay
# $ meta_description This file contains domains for iCloud Private Relay Endpoint.
mask.icloud.com
mask-h2.icloud.com
mask-api.icloud.com
mask.apple-dns.net
mask-api.fe.apple-dns.net
mask-t.apple-dns.net

View File

@ -1,3 +1,5 @@
# $ custom_build_script
# --- Blacklist ---
# >> General

View File

@ -1,3 +1,6 @@
# $ meta_title Sukka's Ruleset - Apple IP
# $ meta_description This file contains IPs owned/used by Apple, Inc.
IP-CIDR,17.0.0.0/8,no-resolve
IP-CIDR,63.92.224.0/19,no-resolve
IP-CIDR,65.199.22.0/23,no-resolve

4
Source/ip/domestic.conf Normal file
View File

@ -0,0 +1,4 @@
# $ meta_title Sukka's Ruleset - Mainland China Supplement CIDR
# $ meta_description This file contains IPs broadcast inside Mainland China.
IP-CIDR,162.14.0.0/18,no-resolve

View File

@ -1,3 +1,6 @@
# $ meta_title Sukka's Ruleset - Large Files Hosting IPs
# $ meta_description This file contains IPs for software updating & large file hosting.
# >> MEGA
IP-CIDR,185.206.24.0/22,no-resolve

View File

@ -1,4 +1,5 @@
# >> NeteaseMusic
# $ meta_title Sukka's Ruleset - Netease Music IPs
# $ meta_description This file contains IPs used by Netease Music.
IP-CIDR,39.105.63.80/32,no-resolve
IP-CIDR,45.254.48.1/32,no-resolve

View File

@ -1,3 +1,5 @@
# $ custom_build_script
# --- AD Block ---
# >> iQiyi

View File

@ -1,4 +1,5 @@
# --- Stream Service ---
# $ meta_title Sukka's Ruleset - Stream Services IPs
# $ meta_description This file contains IPs used by popular stream services.
# >> Netflix
IP-CIDR,23.246.18.0/23,no-resolve

View File

@ -1,3 +1,6 @@
# $ meta_title Sukka's Ruleset - Apple Domains
# $ meta_description This file contains domains of Apple, Inc that doesn't have PoP inside the Mainland China.
# >> Apple
DOMAIN-SUFFIX,aaplimg.com
DOMAIN-SUFFIX,apple-dns.net

View File

@ -1,3 +1,5 @@
# $ custom_build_script
# >> GitHub Pages
DOMAIN-SUFFIX,github.io
# >> GitHub

View File

@ -1,3 +1,6 @@
# $ meta_title Sukka's Ruleset - Direct Rules
# $ meta_description This file contains domains and process that should not be proxied.
# >> AdGuard
DOMAIN,injections.adguard.org
DOMAIN,local.adguard.org

View File

@ -1,3 +1,5 @@
# $ custom_build_script
# >> Akamai
DOMAIN-SUFFIX,akadns.net

View File

@ -1,4 +1,5 @@
# --- General Global Services ---
# $ meta_title Sukka's Ruleset - General Global Services
# $ meta_description This file contains rules for services that are not available inside the Mainland China.
# >> Apple
DOMAIN-SUFFIX,appsto.re
@ -90,4 +91,4 @@ DOMAIN-KEYWORD,github
USER-AGENT,Roam*
# --- End of General Global Services Section ---
# --- End of General Global Services Section ---

View File

@ -1,4 +1,5 @@
# --- Enhanced Global Services ---
# $ meta_title Sukka's Ruleset - Enhanced Global Services
# $ meta_description This file contains rules for services that are not available inside the Mainland China.
# >> Cloudflare
DOMAIN-SUFFIX,cloudflareresolve.com

View File

@ -1,3 +1,5 @@
# $ meta_title Sukka's Ruleset - Direct
DOMAIN-SUFFIX,torrentmac.net
DOMAIN-SUFFIX,download.555mac.com
DOMAIN-KEYWORD,mac-torrent-download

View File

@ -1,3 +1,5 @@
# $ meta_title Sukka's Ruleset - Proxy
DOMAIN-SUFFIX,mikuclub.xyz
DOMAIN-SUFFIX,mikuclub.cn
DOMAIN-SUFFIX,saucenao.com

View File

@ -1,3 +1,5 @@
# $ meta_title Sukka's Ruleset - Reject
DOMAIN,download.parallels.com
DOMAIN,update.parallels.com
DOMAIN,desktop.parallels.com

View File

@ -1,4 +1,5 @@
# >> NeteaseMusic
# $ meta_title Sukka's Ruleset - Netease Music
# $ meta_description This file contains rules for Netease Music.
USER-AGENT,%E7%BD%91%E6%98%93%E4%BA%91%E9%9F%B3%E4%B9%90
USER-AGENT,NeteaseMusic*

View File

@ -1,3 +1,6 @@
# $ meta_title Sukka's Ruleset - Reject No Drop
# $ meta_description This file This file contains rules for domain should be used with REJECT-NO-DROP policy.
# Block YouTube QUIC
AND,((PROTOCOL,UDP), (DOMAIN-SUFFIX,googlevideo.com))
# Block Bilibili/Douyu P2P CDN

View File

@ -1,3 +1,6 @@
# $ meta_title Sukka's Ruleset - Reject Domains
# $ meta_description The ruleset supports AD blocking, tracking protection, privacy protection, anti-phishing, anti-mining
# --- Blacklist ---
# >> Crypto Coin Hive

View File

@ -1,4 +1,6 @@
# >> Sogou Input
# $ meta_title Sukka's Ruleset - Sogou Input
# $ meta_description This file contains rules for Sogou Input.
PROCESS-NAME,SogouInput
PROCESS-NAME,SOgouTaskManager
PROCESS-NAME,SogouServices

View File

@ -1,4 +1,5 @@
# --- Stream Service ---
# $ meta_title Sukka's Ruleset - Stream Services
# $ meta_description This file contains rules for popular stream services.
# >> 4gtv
DOMAIN-SUFFIX,4gtv.tv

View File

@ -1,4 +1,6 @@
# > Telegram
# $ meta_title Sukka's Ruleset - Telegram Domains
# $ meta_description This file contains domains used by Telegram Messenger.
DOMAIN-SUFFIX,t.me
DOMAIN-SUFFIX,tx.me
DOMAIN-SUFFIX,tdesktop.com

View File

@ -5,6 +5,7 @@
"description": "",
"scripts": {
"build": "wireit",
"build:common": "wireit",
"download-previous-build": "wireit",
"build:anti-bogus-domain": "wireit",
"build:apple-cdn": "wireit",
@ -25,6 +26,12 @@
"download-previous-build": {
"command": "node ./Build/download-previous-build.js"
},
"build:common": {
"command": "node ./Build/build.js",
"dependencies": [
"download-previous-build"
]
},
"build:anti-bogus-domain": {
"command": "node ./Build/build-anti-bogus-domain.js",
"dependencies": [
@ -95,6 +102,7 @@
"build:public": {
"command": "node ./Build/build-public.js",
"dependencies": [
"build:common",
"build:anti-bogus-domain",
"build:apple-cdn",
"build:cdn-conf",
@ -118,6 +126,7 @@
},
"build": {
"dependencies": [
"build:common",
"build:anti-bogus-domain",
"build:apple-cdn",
"build:cdn-conf",
@ -145,8 +154,11 @@
"@sukka/listdir": "^0.2.0",
"@vercel/fetch-retry": "^5.1.3",
"async-sema": "^3.1.1",
"ci-info": "^3.8.0",
"cidr-tools-wasm": "^0.0.11",
"fs-extra": "^11.1.1",
"mnemonist": "^0.39.5",
"path-scurry": "^1.10.1",
"picocolors": "^1.0.0",
"table": "^6.8.1",
"tar": "^6.1.15",

49
pnpm-lock.yaml generated
View File

@ -25,12 +25,21 @@ dependencies:
async-sema:
specifier: ^3.1.1
version: 3.1.1
ci-info:
specifier: ^3.8.0
version: 3.8.0
cidr-tools-wasm:
specifier: ^0.0.11
version: 0.0.11
fs-extra:
specifier: ^11.1.1
version: 11.1.1
mnemonist:
specifier: ^0.39.5
version: 0.39.5
path-scurry:
specifier: ^1.10.1
version: 1.10.1
picocolors:
specifier: ^1.0.0
version: 1.0.0
@ -190,15 +199,15 @@ packages:
fastq: 1.13.0
dev: true
/@nolyfill/has@1.0.11:
resolution: {integrity: sha512-Q2QNYUzZxW4/FzI57EGuxQF6PVO+LJajvcsRGTj26FU4z5rmXouaTGMas/2OreYupieIGMeI5dNEJwXS/Wy2kQ==}
/@nolyfill/has@1.0.21:
resolution: {integrity: sha512-Sf8iFaegjGp29hQVQjIc+nDR0uWqGkHsFC3jsUigFwGjpafgMaBtL++DpTU9jYAKDJEvslR1szl8qJjNGlhgcw==}
engines: {node: '>=12.4.0'}
dependencies:
'@nolyfill/shared': 1.0.11
'@nolyfill/shared': 1.0.21
dev: true
/@nolyfill/shared@1.0.11:
resolution: {integrity: sha512-MVtVsoUIbg93Bs33Nc7JvOv/ePyUjUMXorrTtTdjPpLn2t8HmbklZvk2ur0np0thyI9OWC5mNQHjR2l0kwJUNQ==}
/@nolyfill/shared@1.0.21:
resolution: {integrity: sha512-qDc/NoaFU23E0hhiDPeUrvWzTXIPE+RbvRQtRWSeHHNmCIgYI9HS1jKzNYNJxv4jvZ/1VmM3L6rNVxbj+LBMNA==}
dev: true
/@remusao/guess-url-type@1.2.1:
@ -459,6 +468,11 @@ packages:
engines: {node: '>=10'}
dev: false
/ci-info@3.8.0:
resolution: {integrity: sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==}
engines: {node: '>=8'}
dev: false
/cidr-tools-wasm@0.0.11:
resolution: {integrity: sha512-WUnooVHC+0/uwG+/5QZT6auR2Gzga+BFkwyQiKki8uZnVHOCn3gEt+FVjHg/7pdXsCbzGsDSMGkZ31ZqIkUrrw==}
dev: false
@ -964,7 +978,7 @@ packages:
/is-core-module@2.12.1:
resolution: {integrity: sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==}
dependencies:
has: /@nolyfill/has@1.0.11
has: /@nolyfill/has@1.0.21
dev: true
/is-extglob@2.1.1:
@ -1075,6 +1089,11 @@ packages:
get-func-name: 2.0.0
dev: true
/lru-cache@10.0.1:
resolution: {integrity: sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==}
engines: {node: 14 || >=16.14}
dev: false
/lru-cache@6.0.0:
resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
engines: {node: '>=10'}
@ -1134,6 +1153,12 @@ packages:
hasBin: true
dev: false
/mnemonist@0.39.5:
resolution: {integrity: sha512-FPUtkhtJ0efmEFGpU14x7jGbTB+s18LrzRL2KgoWz9YvcY3cPomz8tih01GbHwnGk/OmkOKfqd/RAQoc8Lm7DQ==}
dependencies:
obliterator: 2.0.4
dev: false
/mocha@10.2.0:
resolution: {integrity: sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==}
engines: {node: '>= 14.0.0'}
@ -1196,6 +1221,10 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
/obliterator@2.0.4:
resolution: {integrity: sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==}
dev: false
/once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
dependencies:
@ -1254,6 +1283,14 @@ packages:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
dev: true
/path-scurry@1.10.1:
resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==}
engines: {node: '>=16 || 14 >=14.17'}
dependencies:
lru-cache: 10.0.1
minipass: 5.0.0
dev: false
/pathval@1.1.1:
resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
dev: true