Compare commits

..
222 Commits
Author SHA1 Message Date
xixu-me 60925ed8eb feat: add Arco service
The Workflow / ci (push) Waiting to run
2026-07-19 17:50:11 +08:00
xixu-me f15c7a93eb feat(META): add xAI service entry to the services list 2026-04-07 01:34:57 +08:00
xixu-me 3c0fb800ff fix(META): remove Sora service entry and ensure consistent formatting 2026-03-31 09:19:29 +08:00
xixu-me 1637856af3 chore(ci): upgrade checkout action runtime 2026-03-31 03:18:12 +08:00
xixu-me 6b3f554103 fix(ci): make bun checks path-safe across runners 2026-03-31 03:16:30 +08:00
xixu-me f72b0dae3c build(ci): gate config generation behind bun checks 2026-03-31 03:10:32 +08:00
xixu-me d3f67f9e8e fix(mihomo): harden generated META config 2026-03-31 02:46:55 +08:00
xixu-me 84eff4ed5f Remove Pi service entry from services list
Deleted the Pi service object from the services array in META.js. This may be due to deprecation or removal of support for this service.
2026-01-25 15:00:59 +08:00
xixu-me 6a19c1decd Remove NotebookLM from AI services list
Deleted the entry for NotebookLM (Google-NotebookLM) from the services array in META.js. This streamlines the list of AI/LLM services.
2026-01-25 14:59:31 +08:00
xixu-me c4b813d91e Add 'X' social media service to services list
Introduced a new entry for the 'X' social media platform in the services array within META.js. This update ensures support for the 'X' domain alongside other social media services.
2025-12-22 13:29:48 +08:00
xixu-me ca34c6cef3 Remove unused services from services list
Deleted Medium, Spotify, Perplexity, xAI, Mastodon, and X from the services array in META.js to clean up unused or deprecated entries.
2025-12-22 13:28:07 +08:00
xixu-me a21d0ad7da Fix formatting in META.js for nameserver policy 2025-12-16 01:56:47 +08:00
xixu-me 46f16c9fb8 Change fake-ip-range6 from fc00::/18 to fd00::/18 2025-12-16 01:54:49 +08:00
xixu-me 90e9c3e555 Add India to locations list in META.js
Introduces a new entry for India with appropriate name, icon, and filter to the locations array.
2025-12-15 13:31:22 +08:00
xixu-me 9079aaf15e Fix rule provider key and file naming logic
Updated generateServiceRuleProviders to use alias or name for fileName and to use a whitespace-stripped, lowercased name as the rule key. This ensures consistent mapping and correct file path/url generation.
2025-11-28 14:31:37 +08:00
xixu-me 3f3b097740 Remove spaces from service rule set names
Updated generateServiceRules to strip spaces from service names when generating rule set identifiers, ensuring consistent formatting and preventing issues with rule matching.
2025-11-28 14:21:26 +08:00
xixu-me 533a66002b Fix service rule provider file naming
Updated the generateServiceRuleProviders function to remove spaces from service names when generating file paths, ensuring consistency and preventing potential file access issues.
2025-11-28 14:18:04 +08:00
xixu-me e908f1cfcf Add alias for Truth Social service
Added the 'alias' property with value 'TruthSocial' to the Truth Social entry in the services array for consistency with other services.
2025-11-28 14:06:40 +08:00
xixu-me 1a3bc924ce Increase STATIC_PROXY_COUNT to 4
Updated the number of static proxy groups from 3 to 4 to support an additional IP-sensitive service.
2025-11-28 13:46:57 +08:00
xixu-me 340899681c Add Truth Social to services list
Added Truth Social with tld 'com' and sld 'truthsocial' to the services array in META.js.
2025-11-28 13:46:16 +08:00
xixu-me 4e349b057e Refactor Chinese Mainland proxy group handling
Removed 'Chinese Mainland 🇨🇳' from the locations array and added it as a dedicated proxy group with its own filter and icon. Updated proxy group definitions to explicitly include 'Chinese Mainland 🇨🇳' where appropriate.
2025-11-21 20:24:03 +08:00
xixu-me dc0e041463 Refactor Chinese Mainland location handling
Moved 'Chinese Mainland 🇨🇳' from a dedicated proxy group to the general locations array. Updated proxy group definitions to use the unified locations list, simplifying configuration and reducing redundancy.
2025-11-21 20:11:56 +08:00
xixu-me cdf537fb8a Remove AdGuard DNS from international nameservers
The 'quic://unfiltered.adguard-dns.com' entry was removed from the internationalNameservers array, possibly due to deprecation or connectivity issues.
2025-11-21 19:51:54 +08:00
xixu-me 58c3d7a6c3 Remove proxy rule from nameserver policy
Deleted the 'rule-set:proxy' entry from the nameserver-policy in META.js, so proxy rules no longer use internationalNameservers.
2025-11-21 19:39:24 +08:00
xixu-me da48ff0ee0 Update DNS config and comment out some nameservers
Commented out Mullvad, Wikimedia, and DNS.SB nameservers in the internationalNameservers array. Added 'fake-ip-range6' for IPv6 support and updated default-nameserver entries to use TLS protocol.
2025-11-21 18:10:48 +08:00
xixu-me e590b0f695 Refactor DNS configuration and update nameservers
Reordered and updated the Chinese and international nameserver lists, removed unused AdGuard nameservers, and simplified the default and main nameserver arrays. Also set the DNS cache algorithm to 'arc' and made minor configuration cleanups.
2025-11-21 17:17:45 +08:00
xixu-me bc48ced7ba Add mixed-port to generalConfig in META.js
Introduces the 'mixed-port' property with value 7890 to the generalConfig object and removes the 'external-controller' property. This updates the configuration to support mixed-port usage and cleans up unused settings.
2025-11-21 14:48:23 +08:00
xixu-me b302a6a35e Remove unused proxy field from workflow META.yaml
Deleted the 'proxy: PROXY' line from the generated META.yaml in the GitHub Actions workflow as it is no longer needed.
2025-11-21 14:03:29 +08:00
xixu-me b48ab05e58 Add 'Domestic' proxy group and update related rules
Introduces a new 'Domestic' proxy group with relevant proxies and icon. Updates rules to use 'Domestic' instead of 'Chinese Mainland 🇨🇳' for direct and cncider rules, improving clarity and maintainability.
2025-11-20 15:40:07 +08:00
xixu-me 8aee784d7f Rename 'Mainland China' to 'Chinese Mainland'
Updated all references of 'Mainland China 🇨🇳' to 'Chinese Mainland 🇨🇳' in proxy group names, rules, and related arrays for consistency and clarity.
2025-11-20 15:11:50 +08:00
xixu-me 4a65083c3f Reorder 'AUTO' and 'FALLBACK' in proxy group arrays
Moved 'AUTO' and 'FALLBACK' entries to appear after static proxy names and other load balancing options in proxy group definitions for improved logical grouping and consistency.
2025-11-10 19:34:34 +08:00
xixu-me 0d0cf419ab Update location filters for Iraq and Japan
Removed 'IRQ' from Iraq's filter and added 'JP' and 'jp' to Japan's filter to improve location matching accuracy.
2025-11-10 18:28:59 +08:00
xixu-me d5025262b4 Update Xiaohongshu service domain format
Replaces 'tld' and 'sld' fields with a single 'domain' field for the Xiaohongshu (rednote) service entry to improve clarity and consistency.
2025-11-02 21:15:26 +08:00
xixu-me 8ee1825ff1 Add win-phonelink rule set and provider
Introduces a new 'win-phonelink' rule set to the rules array and configures its rule provider with YAML format and classical behavior. This enables handling of Windows Phone Link related traffic.
2025-11-02 21:10:42 +08:00
xixu-me a97f61a3ad Move service rules before advertising rules
Reordered the call to generateServiceRules(services) to place service rules before advertising-related rules in the rules array. This may affect rule matching priority and processing.
2025-10-13 13:37:10 +08:00
xixu-me b604225b2b Add support for multiple STATIC proxy groups
Introduced STATIC_PROXY_COUNT to allow configuration of multiple STATIC proxy groups for IP-sensitive services. Updated proxy group generation and references to use dynamically generated STATIC proxy group names instead of a single 'STATIC' group.
2025-10-12 20:26:25 +08:00
xixu-me 2923a9b194 Add Mastodon and Pi to services list
Added Mastodon (social) and Pi (minepi.com) entries to the services array in META.js to expand coverage of social media and cryptocurrency platforms.
2025-10-12 17:22:05 +08:00
xixu-me 5cbaaf956c Add Bybit to financial services list
Included Bybit as a new entry in the financial services array in META.js.
2025-10-10 20:44:12 +08:00
xixu-me 7cefec8fc2 Reorder and update Media & Content services list
Moved Media & Content services to the top of the services array and updated the Sora entry to use the correct domain. This improves clarity and ensures correct routing for the Sora service.
2025-10-09 09:59:30 +08:00
xixu-me 830364e196 Add Sora service and update favicon size
Added the Sora service to the services list. Updated the favicon extraction function to use a 128px size instead of 256px. Also standardized the rednote service entry to use tld and sld fields.
2025-10-07 23:04:53 +08:00
xixu-me 82f5b64a3a Update Xget service domain in META.js
Changed the domain for the Xget service from xget.xi-xu.me to xuc.xi-xu.me to reflect the correct endpoint.
2025-10-06 16:03:25 +08:00
xixu-me 944aa9f109 Update service icon set URL to use CDN
Changed the SERVICE_ICON_SET_URL to use jsdelivr CDN for improved reliability and performance when loading service icons.
2025-08-31 19:12:29 +08:00
xixu-me d8b5995329 Remove Google Translate from services list
Deleted the entry for Google Translate under the tools and utilities section in META.js. This streamlines the services array by excluding the previously included translation service.
2025-08-29 20:20:37 +08:00
xixu-me 3252a79750 Add Apple to technology companies list
Included Apple with TLD 'com' in the services array under technology companies in META.js.
2025-08-23 15:18:10 +08:00
xixu-me 460706182f Remove DeepL service entry from META.js
The DeepL service object was removed from the services array, possibly due to deprecation or redundancy.
2025-07-29 09:47:25 +08:00
xixu-me 502fa4ef1f Add Translate and DeepL to services list
Added Google Translate and DeepL under the Tools & Utilities section in the services array. Also updated section header from 'Infrastructure & Tools' to 'Infrastructure' for clarity.
2025-07-19 15:47:58 +08:00
xixu-me e85b2cb977 Move LOCATION_ICON_SET_URL declaration
Relocated the LOCATION_ICON_SET_URL constant below the services array for improved code organization and readability.
2025-07-14 12:32:57 +08:00
xixu-me 9c9da3e5ad Update Xiaohongshu service domain format
Replaces the 'tld' and 'sld' fields with a single 'domain' field for the Xiaohongshu (rednote) service entry to standardize domain representation.
2025-07-09 09:50:50 +08:00
xixu-me 8d03ef5a0f Add Netflix and Spotify to services list
Netflix and Spotify have been added to the Media & Content section of the services array in META.js.
2025-07-06 17:25:53 +08:00
xixu-me 647e83ac5b Reorganize and expand services list in META.js
Grouped services by category for clarity and maintainability. Added new entries for Medium and OKX, and reordered existing services to improve logical structure.
2025-07-06 17:15:32 +08:00
xixu-me 768e76160c Update DNS fake-ip filter and add connectivity-check
Expanded the 'fake-ip-filter' list to include additional rule sets: direct, private, and connectivity-check. Updated the fake-ip-filter rule provider URL and added a new rule provider for connectivity-check.
2025-06-15 19:54:07 +08:00
xixu-me c434a152cb Refactor fake-ip-filter to use external rule provider
Replaces the inline fake-ip-filter domain list with a reference to a new rule-set provider. Adds the 'fake-ip-filter' rule provider configuration pointing to an external .mrs file for improved maintainability.
2025-06-15 18:45:56 +08:00
xixu-me 54db90a86b Remove Australia from locations list
The Australia entry has been removed from the locations array in META.js. This may be to restrict available locations or update the supported regions.
2025-06-12 16:27:57 +08:00
xixu-me f063a6881f Add Australia to locations list
Introduced a new entry for Australia in the locations array, including its name, icon, and filter pattern.
2025-06-12 15:18:59 +08:00
xixu-me a4dbc9ed5f Update Singapore location filter to include 'SG'
Added 'SG' to the Singapore location filter to improve matching for different case variations and abbreviations.
2025-06-12 13:44:25 +08:00
xixu-me 51f35dc544 Update Google service domain in META.js
Changed the Google service entry to use tld 'google' and sld 'about' instead of tld 'com' for more accurate domain representation.
2025-06-11 09:54:34 +08:00
xixu-me 268f75884d Update Speedtest TLD from com to net
Changed the TLD for the Speedtest service from 'com' to 'net' in the services array to reflect the correct domain.
2025-06-10 21:29:58 +08:00
xixu-me 8128a01bb7 Refactor service icon configuration
Reorganized the service definitions to use TLD and SLD properties instead of hardcoded icon URLs. Added a `getServiceIcon` function to dynamically generate service icons based on domain or TLD/SLD, improving maintainability and flexibility.
2025-06-10 16:35:56 +08:00
xixu-me f4d8bcd246 Update DNS configuration in META.js
Modified DNS settings to disable HTTP/3 preference, enable rule respect, and update fake IP filter and nameserver policies. Added new entries to the fake IP filter and introduced a proxy server nameserver configuration.
2025-06-10 12:12:48 +08:00
xixu-me 1ebc52e08a Reorder and update BASE_ICON_SET_URL declaration
Moved the BASE_ICON_SET_URL declaration to a later point in the file for better organization. Also adjusted string formatting in error messages for proxy and proxy provider validation to improve readability.
2025-06-10 11:36:39 +08:00
xixu-me 9e3b7dcd32 Update service icon URLs in META.js
Replaced `BASE_ICON_SET_URL` and `extractFavicon` references with a new `SERVICE_ICON_SET_URL` for consistency and to use updated icon paths. This change affects multiple service entries in the `services` array.
2025-06-10 11:31:57 +08:00
xixu-me 34aeabd3ca Update META.js services and proxy validation
Added 'rednote' service with alias 'Xiaohongshu' and updated the favicon extraction URL for 'Xget'. Improved the `extractFavicon` function to use a new Google favicon API. Fixed formatting issues in error messages for proxy validation.
2025-06-09 16:11:39 +08:00
xixu-me 058b301ee6 Update run.yml 2025-05-28 00:12:07 +08:00
xixu-me 831328db98 Update run.yml 2025-05-28 00:08:20 +08:00
xixu-me c1094723e5 Update META.js 2025-05-28 00:06:49 +08:00
xixu-me d77c86ba0f Update META.js 2025-04-26 13:21:39 +08:00
xixu-me 389328cf5a Delete .github/dependabot.yml 2025-04-04 18:22:41 +08:00
xixu-me 2db579df8a Create dependabot.yml 2025-04-04 18:18:12 +08:00
xixu-me 665fa2229d Update META.js 2025-03-29 19:39:53 +08:00
xixu-me 13e62404fe Update META.js 2025-03-13 12:06:32 +08:00
xixu-me 89753e40ff Update META.js 2025-01-21 17:43:21 +08:00
xixu-me 24400c4c45 Update META.js 2025-01-14 19:21:36 +08:00
xixu-me 89b03eb3ad Update META.js 2025-01-14 19:11:46 +08:00
xixu-me 93cfb60a8e Create run.yml 2025-01-14 19:02:21 +08:00
xixu-me 90bbe99388 Update META.js 2025-01-14 18:54:06 +08:00
xixu-me 9ae86618bd Delete check-and-purge-cdn-cache.yml 2025-01-14 18:37:19 +08:00
xixu-me 09fd6aa15b Delete generate-yaml-configuration.yml 2025-01-14 18:37:17 +08:00
xixu-me 0c073de0e5 Update META.js 2025-01-11 13:30:55 +08:00
xixu-me 934a5bb2f1 Update META.js 2025-01-11 13:27:19 +08:00
xixu-me 0f51f8b37f Update META.js 2025-01-04 16:20:57 +08:00
xixu-me 0cb0ce785e Update META.js 2025-01-04 16:18:55 +08:00
xixu-me b26f69702f Delete META_stash.js 2025-01-04 16:18:53 +08:00
xixu-me d02823f80f Update META.js 2024-12-12 17:23:14 +08:00
xixu-me 5ea70d5c61 Update generate-yaml-configuration.yml 2024-12-06 22:11:13 +08:00
xixu-me d663250d77 Update generate-yaml-configuration.yml 2024-12-06 22:09:13 +08:00
xixu-me aa8cc2769a Update META.js 2024-12-06 22:08:17 +08:00
xixu-me 699efffd40 Update META.js 2024-12-06 22:08:12 +08:00
xixu-me 955c5d5e4b Create generate-yaml-configuration.yml 2024-12-06 22:05:12 +08:00
xixu-me b27530a020 Update META.js 2024-12-06 21:29:26 +08:00
xixu-me 0c0912a3f2 Update META.js 2024-12-06 20:40:23 +08:00
xixu-me c5bea6e6ba Update META.js 2024-12-06 20:33:20 +08:00
xixu-me e41da7eb4f Update META.js 2024-12-06 20:32:11 +08:00
xixu-me 9f11961836 Update META.js 2024-12-06 20:30:33 +08:00
xixu-me f4e3aaeab1 Update META.js 2024-12-06 20:11:42 +08:00
xixu-me c09505e304 Update META.js 2024-12-06 19:43:11 +08:00
xixu-me 7230deb89c Update META.js 2024-12-06 18:34:56 +08:00
xixu-me 062de6a9cf Update META_stash.js 2024-12-06 16:07:38 +08:00
xixu-me 128842e1ca Update META.js 2024-11-30 19:40:20 +08:00
xixu-me cb5c48e4ac Update META.js 2024-11-14 21:50:19 +08:00
xixu-me e839494a5d Update META.js 2024-11-13 21:06:16 +08:00
xixu-me cd3c9204b4 Update META_stash.js 2024-11-04 19:51:33 +08:00
xixu-me e46f7e94a7 Update META_stash.js 2024-11-04 13:11:45 +08:00
xixu-me 76376f9874 Rename META-yaml.js to META_stash.js 2024-11-04 11:41:19 +08:00
xixu-me bf4008c26e Create META-yaml.js 2024-11-03 23:38:45 +08:00
xixu-me 24afe91239 Update META.js 2024-11-03 21:53:16 +08:00
xixu-me 1dfa449b71 Update META.js 2024-11-03 21:50:35 +08:00
xixu-me a926dbf152 Delete check.yml 2024-11-02 16:29:54 +08:00
xixu-me ec1c7d7f32 Create check-and-purge-cdn-cache.yml 2024-11-02 16:29:52 +08:00
xixu-me 7f18ef56bd Update META.js 2024-11-02 16:18:05 +08:00
xixu-me cfb12aa8e3 Update META.js 2024-11-02 16:16:14 +08:00
xixu-me 6a397d1330 Update META.js 2024-10-31 02:28:14 +08:00
xixu-me c9f2d71216 Update META.js 2024-10-30 23:49:08 +08:00
xixu-me c7680a99cc Delete README.md 2024-10-29 21:28:37 +08:00
xixu-me c58df86736 Update README.md 2024-10-29 21:20:08 +08:00
xixu-me b92ad83725 Update README.md 2024-10-29 21:18:03 +08:00
xixu-me f1ea409491 Update META.js 2024-10-28 23:09:43 +08:00
xixu-me 7bee9b51db Update META.js 2024-10-27 22:50:50 +08:00
xixu-me e93d6f65c6 Update META.js 2024-10-27 22:47:58 +08:00
xixu-me 3a340a4737 Update META.js 2024-10-27 22:47:20 +08:00
xixu-me c835aa65a8 Update META.js 2024-10-27 22:46:20 +08:00
xixu-me 868725fbf8 Update META.js 2024-10-26 21:10:39 +08:00
xixu-me 8290d424cc Update META.js 2024-10-26 21:09:35 +08:00
xixu-me 11de6b2bbd Revert "Update META.js"
This reverts commit 99c610d62b.
2024-10-26 21:02:23 +08:00
xixu-me 1f21e55df7 Update META.js 2024-10-26 20:14:39 +08:00
xixu-me 2c6a0754c6 Update META.js 2024-10-26 20:14:01 +08:00
xixu-me 99c610d62b Update META.js 2024-10-26 20:12:35 +08:00
xixu-me 602f715795 Update META.js 2024-10-26 19:57:53 +08:00
xixu-me de3f8c39a8 Update META.js 2024-10-26 19:42:44 +08:00
xixu-me 06bf3d4eec Update META.js 2024-10-26 18:54:46 +08:00
xixu-me 3ca154680b Update META.js 2024-10-26 18:01:20 +08:00
xixu-me 29f6cae182 Update META.js 2024-10-26 15:57:26 +08:00
xixu-me 6b011294a5 Update META.js 2024-10-26 15:52:37 +08:00
xixu-me df1442fce0 Update META.js 2024-10-26 15:32:31 +08:00
xixu-me ab0d0e5423 Update META.js 2024-10-26 15:30:59 +08:00
xixu-me c7d5904b4e Update META.js 2024-10-26 14:56:53 +08:00
xixu-me b25e7fcfb7 Update META.js 2024-10-24 23:52:38 +08:00
xixu-me bcd98604b1 Update META.js 2024-10-24 23:49:44 +08:00
xixu-me 2122de2912 Update META.js 2024-10-24 23:28:31 +08:00
xixu-me e2fe02353e Update META.js 2024-10-24 23:10:59 +08:00
xixu-me 903b57fac1 Update META.js 2024-10-24 23:09:03 +08:00
xixu-me b5194bb768 Update META.js 2024-10-24 23:06:40 +08:00
xixu-me 2e6d3f3fc9 Update META.js 2024-10-24 22:43:00 +08:00
xixu-me cdb4aeac4a Update META.js 2024-10-24 22:41:59 +08:00
xixu-me 609debcacb Update META.js 2024-10-24 21:53:16 +08:00
xixu-me 53d43f22b0 Update META.js 2024-10-24 21:52:58 +08:00
xixu-me fc76349933 Update META.js 2024-10-24 20:15:20 +08:00
xixu-me 6726efd9ee Update META.js 2024-10-24 20:12:12 +08:00
xixu-me 57c7f07ea5 Update META.js 2024-10-24 19:37:21 +08:00
xixu-me 58e764aef7 Update META.js 2024-10-24 19:24:22 +08:00
xixu-me 01c2eadbd1 Update META.js 2024-10-24 17:33:47 +08:00
xixu-me a4edd595c4 Update META.js 2024-10-24 17:14:44 +08:00
xixu-me a9d6138d36 Update META.js 2024-10-23 23:23:54 +08:00
xixu-me c8191da3c5 Update META.js 2024-10-23 12:39:45 +08:00
xixu-me 5de189cfc1 Update META.js 2024-10-23 12:28:59 +08:00
xixu-me fce8ef8a83 Update META.js 2024-10-23 00:27:53 +08:00
xixu-me fc1d30536b Update META.js 2024-10-22 22:43:59 +08:00
xixu-me dd6fad269b Update META.js 2024-10-22 21:55:31 +08:00
xixu-me 70498fcf88 Update META.js 2024-10-22 21:02:59 +08:00
xixu-me 0e472ffcf6 Update META.js 2024-10-22 20:58:28 +08:00
xixu-me fdfaf7e4c5 Update META.js 2024-10-22 20:42:33 +08:00
xixu-me 9093d30507 Update META.js 2024-10-22 20:40:14 +08:00
xixu-me 47bcdad9d8 Update META.js 2024-10-22 20:37:03 +08:00
xixu-me 7148769cf7 Update META.js 2024-10-22 20:30:31 +08:00
xixu-me 5f3f1de5b2 Update META.js 2024-10-22 20:23:45 +08:00
xixu-me b38825bb44 Update META.js 2024-10-22 20:11:09 +08:00
xixu-me 8b543e13f2 Update META.js 2024-10-22 19:39:53 +08:00
xixu-me dbda8e3c73 Update META.js 2024-10-22 18:56:41 +08:00
xixu-me 81b2ac5b3e Update META.js 2024-10-22 18:47:48 +08:00
xixu-me b009c3ef19 Update META.js 2024-10-22 18:41:07 +08:00
xixu-me 5cdb1a411c Update META.js 2024-10-22 18:36:44 +08:00
xixu-me c8127dde15 Update META.js 2024-10-22 17:15:39 +08:00
xixu-me 6ebcb21bd2 Update META.js 2024-10-22 17:11:29 +08:00
xixu-me 9bd39a17fd Update META.js 2024-10-22 13:47:12 +08:00
xixu-me 79534b9653 Update META.js 2024-10-22 13:40:44 +08:00
xixu-me 32688eb7fd Update META.js 2024-10-22 13:39:59 +08:00
xixu-me 6354152686 Update META.js 2024-10-22 13:36:24 +08:00
xixu-me 27b102a27f Update META.js 2024-10-21 19:55:15 +08:00
xixu-me dd5163be32 Update META.js 2024-10-19 19:31:13 +08:00
xixu-me 27b4fcf7e2 Update META.js 2024-10-16 18:16:07 +08:00
xixu-me 067053a226 Update META.js 2024-10-15 19:30:19 +08:00
xixu-me eab8bb3311 Update META.js 2024-10-13 23:34:12 +08:00
xixu-me 7281461add Update META.js 2024-10-13 23:34:07 +08:00
xixu-me 62fca2c69c Update README.md 2024-10-13 23:25:48 +08:00
xixu-me 1971c56c4c Update check.yml 2024-10-13 23:25:46 +08:00
xixu-me e660a839bd Create META.js 2024-10-13 23:25:44 +08:00
xixu-me 57638f9d2b Delete MCG.js 2024-10-13 23:25:39 +08:00
xixu-me 08524cf87b Update check.yml 2024-10-13 20:54:35 +08:00
xixu-me 978e4feaae Update MCG.js 2024-10-13 00:35:08 +08:00
xixu-me 0b3b856c39 Update MCG.js 2024-10-13 00:18:20 +08:00
xixu-me dfe0c82a75 Update MCG.js 2024-10-12 23:16:15 +08:00
xixu-me 92aea6592d Update MCG.js 2024-10-12 23:07:54 +08:00
xixu-me 14f8065fea Update MCG.js 2024-10-12 22:54:22 +08:00
xixu-me 06b647974d Update MCG.js 2024-10-12 21:05:46 +08:00
xixu-me 7889fb390f Update MCG.js 2024-10-12 20:26:14 +08:00
xixu-me 48ca436207 Update MCG.js 2024-10-12 20:09:29 +08:00
xixu-me 770a6167d9 Update MCG.js 2024-10-12 19:22:09 +08:00
xixu-me 6414580b58 Update MCG.js 2024-10-12 19:20:10 +08:00
xixu-me a411bd82fa Update MCG.js 2024-10-12 19:05:56 +08:00
xixu-me 35f5d7669f Update MCG.js 2024-10-12 18:59:38 +08:00
xixu-me f8038b38e5 Update MCG.js 2024-10-12 18:16:44 +08:00
xixu-me 9ba44b80d9 Update MCG.js 2024-10-12 18:09:14 +08:00
xixu-me c292629677 Update MCG.js 2024-10-12 18:07:28 +08:00
xixu-me c7b514590b Create README.md 2024-10-12 13:43:56 +08:00
xixu-me fad239874c Update MCG.js 2024-10-12 13:43:05 +08:00
xixu-me 5321b846a5 Update check.yml 2024-10-12 13:42:53 +08:00
xixu-me 319d67d7fe Update check.yml 2024-10-12 02:06:41 +08:00
xixu-me 7112144499 Rename script.js to MCG.js 2024-10-12 02:04:02 +08:00
xixu-me ba4f58bb7f Create check.yml 2024-10-11 21:02:32 +08:00
xixu-me ab5d241e90 Delete verify.yml 2024-10-11 21:02:29 +08:00
xixu-me ad706d9c11 Create verify.yml 2024-10-11 21:00:07 +08:00
xixu-me 78a587aeef Delete validate-commit.yml 2024-10-11 21:00:03 +08:00
xixu-me 838a2d88dd Create validate-commit.yml 2024-10-11 20:55:34 +08:00
xixu-me 635bcb095c Update script.js 2024-10-11 20:51:14 +08:00
xixu-me 47c716c112 Update script.js 2024-10-11 20:49:07 +08:00
xixu-me b519f37efc Update script.js 2024-10-11 20:41:43 +08:00
xixu-me 3e029afb71 Update script.js 2024-10-11 20:37:43 +08:00
xixu-me e1d02248c0 Update script.js 2024-10-11 17:10:52 +08:00
xixu-me cce6ab1f8f Update script.js 2024-10-11 17:05:19 +08:00
xixu-me cc3e087aaf Update script.js 2024-10-11 13:25:08 +08:00
xixu-me c70f1a6b08 Update script.js 2024-10-11 12:47:37 +08:00
xixu-me 94ccb171ea Delete README.md 2024-10-11 12:28:20 +08:00
xixu-me 6e09711ec1 Delete LICENSE 2024-10-11 12:28:18 +08:00
13 changed files with 1273 additions and 327 deletions

No files matched your search

-201
View File
@@ -1,201 +0,0 @@
name: Dependabot Auto Merge
on:
check_suite:
types: [completed]
workflow_run:
workflows:
- "CodeQL"
- "The Workflow"
types:
- completed
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.event.workflow_run.head_branch || github.run_id }}
cancel-in-progress: false
permissions:
contents: write
pull-requests: write
checks: read
statuses: read
jobs:
merge:
name: Auto-merge Dependabot PRs
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Merge Dependabot PRs when checks pass
uses: actions/github-script@v9
with:
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const successfulCheckConclusions = new Set(["success", "skipped", "neutral"]);
const successfulStatusStates = new Set(["success"]);
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const latestBy = (items, keyOf, timeOf) => {
const latest = new Map();
for (const item of items) {
const key = keyOf(item);
const itemTime = new Date(timeOf(item) || 0).getTime();
const existing = latest.get(key);
const existingTime = existing ? new Date(timeOf(existing) || 0).getTime() : -1;
if (!existing || itemTime >= existingTime) {
latest.set(key, item);
}
}
return [...latest.values()];
};
const findCandidatePulls = async () => {
const pulls = await github.paginate(github.rest.pulls.list, {
owner,
repo,
state: "open",
per_page: 100,
});
return pulls.filter((pr) => pr.user?.login === "dependabot[bot]");
};
const getMergeablePullRequest = async (pull_number) => {
for (let attempt = 1; attempt <= 6; attempt += 1) {
const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number });
if (pr.mergeable !== null) {
return pr;
}
core.info("PR #" + pull_number + " mergeability is still being computed; retry " + attempt + "/6.");
await wait(5000);
}
const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number });
return pr;
};
const getSignalState = async (sha) => {
const checkRuns = await github.paginate(github.rest.checks.listForRef, {
owner,
repo,
ref: sha,
per_page: 100,
});
const statuses = await github.paginate(github.rest.repos.listCommitStatusesForRef, {
owner,
repo,
ref: sha,
per_page: 100,
});
const latestChecks = latestBy(
checkRuns.filter((run) => run.name !== "Dependabot Auto Merge"),
(run) => (run.app?.slug || "unknown") + ":" + run.name,
(run) => run.completed_at || run.started_at || run.created_at,
);
const latestStatuses = latestBy(statuses, (status) => status.context, (status) => status.updated_at || status.created_at);
const pendingChecks = latestChecks.filter((run) => run.status !== "completed");
const failedChecks = latestChecks.filter(
(run) => run.status === "completed" && !successfulCheckConclusions.has(String(run.conclusion || "").toLowerCase()),
);
const failedStatuses = latestStatuses.filter((status) => !successfulStatusStates.has(String(status.state || "").toLowerCase()));
return {
totalSignals: latestChecks.length + latestStatuses.length,
pendingChecks,
failedChecks,
failedStatuses,
};
};
const { data: repository } = await github.rest.repos.get({ owner, repo });
const mergeMethods = [];
if (repository.allow_merge_commit) mergeMethods.push("merge");
if (repository.allow_squash_merge) mergeMethods.push("squash");
if (repository.allow_rebase_merge) mergeMethods.push("rebase");
if (mergeMethods.length === 0) {
core.info("This repository has no enabled pull request merge methods.");
return;
}
const pulls = await findCandidatePulls();
if (pulls.length === 0) {
core.info("No open Dependabot PRs to evaluate.");
return;
}
for (const candidate of pulls) {
const pull_number = candidate.number;
const pr = await getMergeablePullRequest(pull_number);
if (pr.user?.login !== "dependabot[bot]") {
core.info("PR #" + pull_number + " is no longer a Dependabot PR.");
continue;
}
if (pr.state !== "open" || pr.draft) {
core.info("PR #" + pull_number + " is not an open, ready PR.");
continue;
}
if (pr.mergeable !== true) {
core.info("PR #" + pull_number + " is not currently mergeable.");
continue;
}
const signalState = await getSignalState(pr.head.sha);
if (signalState.totalSignals === 0) {
core.info("PR #" + pull_number + " has no checks or statuses yet; skipping.");
continue;
}
if (signalState.pendingChecks.length > 0) {
core.info("PR #" + pull_number + " still has pending checks: " + signalState.pendingChecks.map((run) => run.name).join(", ") + ".");
continue;
}
if (signalState.failedChecks.length > 0 || signalState.failedStatuses.length > 0) {
const failedChecks = signalState.failedChecks.map((run) => run.name + "=" + run.conclusion).join(", ");
const failedStatuses = signalState.failedStatuses.map((status) => status.context + "=" + status.state).join(", ");
core.info("PR #" + pull_number + " is not green. Checks: " + (failedChecks || "none") + ". Statuses: " + (failedStatuses || "none") + ".");
continue;
}
let merged = false;
let lastError = null;
for (const merge_method of mergeMethods) {
try {
await github.rest.pulls.merge({ owner, repo, pull_number, merge_method });
core.info("Merged Dependabot PR #" + pull_number + " with " + merge_method + ".");
merged = true;
break;
} catch (error) {
lastError = error;
if (error.status === 403 || error.status === 405 || error.status === 409) {
core.info("Cannot merge PR #" + pull_number + " with " + merge_method + ": " + error.message);
continue;
}
throw error;
}
}
if (!merged) {
core.info("PR #" + pull_number + " could not be merged: " + (lastError?.message || "unknown error") + ".");
continue;
}
if (pr.head.repo?.full_name === owner + "/" + repo) {
try {
await github.rest.git.deleteRef({ owner, repo, ref: "heads/" + pr.head.ref });
core.info("Deleted branch " + pr.head.ref + ".");
} catch (error) {
core.info("Could not delete branch " + pr.head.ref + ": " + error.message);
}
}
}
+106
View File
@@ -0,0 +1,106 @@
name: The Workflow
on:
push:
branches:
- script
workflow_dispatch:
jobs:
ci:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Set up Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Run test suite
run: bun test META.test.js workflow.test.js
run:
runs-on: ubuntu-latest
needs: ci
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Set up Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Validate runnable scenarios
run: bun run run
purge-dns-cache:
runs-on: ubuntu-latest
needs: run
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Purge CDN cache
run: |
for file in $(ls); do
curl -i "https://purge.jsdelivr.net/gh/${{ github.repository }}@script/${file}"
done
generate-config:
runs-on: ubuntu-latest
needs: run
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Set up Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Generate YAML configuration
run: |
mkdir -p publish
bun run generate
cat > publish/META.yaml << 'EOL'
proxy-providers-defaults: &ppd
type: http
interval: 86400
health-check:
enable: true
url: "https://www.gstatic.com/generate_204"
interval: 300
filter: "^(?!.*(群|邀请|返利|循环|官网|客服|网站|网址|获取|订阅|流量|到期|机场|下次|版本|官址|备用|过期|已用|联系|邮箱|工单|贩卖|通知|倒卖|防止|国内|地址|频道|无法|说明|使用|提示|特别|访问|支持|教程|关注|更新|作者|加入|USE|USED|TOTAL|EXPIRE|EMAIL|Panel|Channel|Author))"
proxy-providers:
Provider A:
<<: *ppd
url: "https://the.url.of/A.yaml"
override:
additional-prefix: "[A] "
Provider B:
<<: *ppd
url: "https://the.url.of/B.yaml"
override:
additional-prefix: "[B] "
Provider C:
<<: *ppd
url: "https://the.url.of/C.yaml"
override:
additional-prefix: "[C] "
EOL
cat META.yaml >> publish/META.yaml
- name: Push to config branch
run: |
cd publish
git init
git config --local user.name "github-actions[bot]"
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
git checkout -b config
git add .
git commit -m "Update YAML configuration"
git remote add origin "https://github-actions[bot]:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}"
git push -f origin config
+2
View File
@@ -0,0 +1,2 @@
node_modules/
META.yaml
-21
View File
@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2026 Xi Xu
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+41
View File
@@ -0,0 +1,41 @@
const fs = require("node:fs");
const path = require("node:path");
const vm = require("node:vm");
const yaml = require("yaml");
function loadMain() {
const content = fs.readFileSync(path.join(__dirname, "META.js"), "utf8");
const wrapped = `${content}
module.exports = { main };`;
const context = {
module: { exports: {} },
console: {
log() {},
warn() {},
error() {},
},
};
vm.runInNewContext(wrapped, context, { filename: "META.js" });
return context.module.exports.main;
}
function main() {
const generateConfig = loadMain();
const config = generateConfig({}, { skipValidation: true });
if (config.error) {
throw new Error(config.error);
}
fs.writeFileSync(path.join(__dirname, "META.yaml"), yaml.stringify(config));
console.log("Generated META.yaml");
}
try {
main();
} catch (error) {
console.error(error.message);
process.exitCode = 1;
}
+791
View File
@@ -0,0 +1,791 @@
// from https://github.com/xixu-me/META
// ################################################### this section can be flexibly customized ###################################################
// Number of STATIC proxy groups (for IP-sensitive services)
const STATIC_PROXY_COUNT = 4;
// Routing rules are matched in order from top to bottom, with the rule at the top of the list taking precedence over the rules below it.
const services = [
// Media & Content
{ name: "bilibili", tld: "com" },
{ name: "Netflix", tld: "com" },
{ name: "YouTube", tld: "com" },
// AI/LLM Services
{ name: "Anthropic", tld: "com" },
{ name: "Arco", domain: "www.youware.com" },
{ name: "Gemini", tld: "google", alias: "Google-Gemini" },
{ name: "OpenAI", tld: "com" },
{ name: "xAI", tld: "ai", sld: "x" },
// Social Media & Communication
{ name: "Douyin", domain: "douyin.com", alias: "Douyin" },
{ name: "rednote", domain: "xiaohongshu.com", alias: "Xiaohongshu" },
{ name: "Telegram", tld: "org" },
{
name: "Truth Social",
tld: "com",
sld: "truthsocial",
alias: "TruthSocial",
},
{ name: "X", tld: "com" },
// Financial Services
{ name: "Binance", tld: "com" },
{ name: "Bybit", tld: "com" },
{ name: "OKX", tld: "com" },
// Technology Companies
{ name: "Apple", tld: "com" },
{ name: "Google", tld: "google", sld: "about" },
{ name: "Microsoft", tld: "com" },
// Infrastructure
{ name: "Speedtest", tld: "net" },
{ name: "Xget", domain: "xuc.xi-xu.me" },
{ name: "Cloudflare", tld: "com" },
];
const LOCATION_ICON_SET_URL = "https://img.icons8.com/color/144/";
const locations = [
{
name: "Argentina 🇦🇷",
icon: `${LOCATION_ICON_SET_URL}argentina`,
keywords: ["阿根廷", "Argentina"],
codes: ["ARG", "AR"],
emoji: ["🇦🇷"],
},
{
name: "Canada 🇨🇦",
icon: `${LOCATION_ICON_SET_URL}canada`,
keywords: ["加拿大", "Canada"],
codes: ["CAN", "CA"],
emoji: ["🇨🇦"],
},
{
name: "Finland 🇫🇮",
icon: `${LOCATION_ICON_SET_URL}finland`,
keywords: ["芬兰", "Finland"],
codes: ["FIN", "FI"],
emoji: ["🇫🇮"],
},
{
name: "France 🇫🇷",
icon: `${LOCATION_ICON_SET_URL}france`,
keywords: ["法国", "France"],
codes: ["FR"],
emoji: ["🇫🇷"],
},
{
name: "Germany 🇩🇪",
icon: `${LOCATION_ICON_SET_URL}germany`,
keywords: ["德国", "Germany"],
codes: ["GER", "DE"],
emoji: ["🇩🇪"],
},
{
name: "Hong Kong, China 🇭🇰",
icon: `${LOCATION_ICON_SET_URL}hongkong-circular`,
keywords: ["香港", "Hong Kong"],
codes: ["HK"],
emoji: ["🇭🇰"],
},
{
name: "India 🇮🇳",
icon: `${LOCATION_ICON_SET_URL}india`,
keywords: ["印度", "India"],
codes: ["IND", "IN"],
emoji: ["🇮🇳"],
},
{
name: "Iraq 🇮🇶",
icon: `${LOCATION_ICON_SET_URL}iraq`,
keywords: ["伊拉克", "Iraq"],
codes: ["IQ"],
emoji: ["🇮🇶"],
},
{
name: "Japan 🇯🇵",
icon: `${LOCATION_ICON_SET_URL}japan`,
keywords: ["日本", "Japan"],
codes: ["JP"],
emoji: ["🇯🇵"],
},
{
name: "Korea 🇰🇷",
icon: `${LOCATION_ICON_SET_URL}south-korea`,
keywords: ["韩国", "Korea"],
codes: ["KR"],
emoji: ["🇰🇷"],
},
{
name: "Russia 🇷🇺",
icon: `${LOCATION_ICON_SET_URL}russian-federation`,
keywords: ["俄罗斯", "Russia Federation", "Russia"],
codes: ["RU"],
emoji: ["🇷🇺"],
},
{
name: "Singapore 🇸🇬",
icon: `${LOCATION_ICON_SET_URL}singapore`,
keywords: ["新加坡", "Singapore"],
codes: ["SG"],
emoji: ["🇸🇬"],
},
{
name: "Taiwan, China 🇨🇳",
icon: `${LOCATION_ICON_SET_URL}china-circular`,
keywords: ["台湾", "Taiwan"],
codes: ["TW"],
emoji: ["🇹🇼"],
},
{
name: "Thailand 🇹🇭",
icon: `${LOCATION_ICON_SET_URL}thailand`,
keywords: ["泰国", "Thailand"],
codes: ["TH"],
emoji: ["🇹🇭"],
},
{
name: "Türkiye 🇹🇷",
icon: `${LOCATION_ICON_SET_URL}turkey`,
keywords: ["土耳其", "Türkiye", "Turkey"],
codes: ["TUR", "TR"],
emoji: ["🇹🇷"],
},
{
name: "Ukraine 🇺🇦",
icon: `${LOCATION_ICON_SET_URL}ukraine`,
keywords: ["乌克兰", "Ukraine"],
codes: ["UKR", "UA"],
emoji: ["🇺🇦"],
},
{
name: "United Kingdom 🇬🇧",
icon: `${LOCATION_ICON_SET_URL}great-britain`,
keywords: ["英国", "United Kingdom", "Great Britain"],
codes: ["UK", "GB"],
emoji: ["🇬🇧"],
},
{
name: "United States 🇺🇸",
icon: `${LOCATION_ICON_SET_URL}usa`,
keywords: ["美国", "United States of America", "United States"],
codes: ["USA", "US"],
emoji: ["🇺🇸"],
},
{
name: "Global 🌐",
icon: "https://img.icons8.com/?size=144&id=3685&format=png&color=7bbbe9",
keywords: ["Global", "Cloudflare"],
codes: ["GL", "CF"],
emoji: ["🌐"],
},
];
// ####################################### DO NOT MODIFY THE CODE BELOW UNLESS YOU KNOW WHAT YOU ARE DOING #######################################
function extractFavicon(domain) {
return `https://t1.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=https://${domain}&size=128`;
}
function escapeRegex(text) {
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function buildLocationFilter({ keywords = [], codes = [], emoji = [] }) {
const patterns = [
...keywords.map((keyword) => escapeRegex(keyword)),
...codes.map((code) => `\\b${escapeRegex(code)}\\b`),
...emoji.map((symbol) => escapeRegex(symbol)),
];
return `(?i)${patterns.join("|")}`;
}
locations.forEach((location) => {
location.filter = buildLocationFilter(location);
});
// General Configuration
const generalConfig = {
"mixed-port": 7890,
"allow-lan": false,
mode: "rule",
"log-level": "info",
ipv6: true,
"find-process-mode": "strict",
profile: {
"store-selected": true,
"store-fake-ip": true,
},
"unified-delay": true,
"tcp-concurrent": true,
"global-ua": "\u0063\u006c\u0061\u0073\u0068.\u006D\u0065\u0074\u0061",
"etag-support": true,
};
// DNS
const chineseNameservers = [
"https://dns.alidns.com/dns-query",
"https://doh.pub/dns-query",
];
const internationalNameservers = [
"https://cloudflare-dns.com/dns-query",
"https://dns.google/dns-query",
// "https://dns.mullvad.net/dns-query",
// "https://wikimedia-dns.org/dns-query",
// "https://doh.dns.sb/dns-query",
];
const dns = {
enable: true,
"cache-algorithm": "arc",
"prefer-h3": false,
"use-hosts": true,
"use-system-hosts": false,
"respect-rules": true,
ipv6: true,
"enhanced-mode": "fake-ip",
"fake-ip-range": "198.18.0.1/16",
"fake-ip-range6": "fd00::/18",
"fake-ip-filter-mode": "blacklist",
"fake-ip-filter": [
"rule-set:fake-ip-filter",
"rule-set:direct",
"rule-set:private",
"rule-set:connectivity-check",
],
"default-nameserver": [
"tls://223.5.5.5",
"tls://223.6.6.6",
"tls://119.29.29.29",
],
"proxy-server-nameserver": chineseNameservers,
"nameserver-policy": {
"rule-set:private": ["system://"],
"rule-set:direct,geolocation-cn": chineseNameservers,
},
nameserver: internationalNameservers,
};
// Hosts
const hosts = {};
// Domain Sniffing
const sniffer = {
enable: false,
};
// TUN
const tun = {
enable: true,
stack: "mixed",
"auto-route": true,
"auto-detect-interface": true,
"strict-route": true,
"dns-hijack": ["any:53", "tcp://any:53"],
};
// Proxy Groups
const proxyGroupDefaults = {
url: "https://www.gstatic.com/generate_204",
interval: 300,
lazy: true,
timeout: 5000,
"max-failed-times": 5,
};
// Generate STATIC proxy group names
function generateStaticProxyNames() {
if (STATIC_PROXY_COUNT === 1) {
return ["STATIC"];
}
return Array.from(
{ length: STATIC_PROXY_COUNT },
(_, i) => `STATIC-${i + 1}`,
);
}
const staticProxyNames = generateStaticProxyNames();
const serviceProxyGroupProxies = [
"PROXY",
...staticProxyNames,
"AUTO",
"DIRECT",
"Chinese Mainland 🇨🇳",
...locations.map(({ name }) => name),
];
const locationPolicyProxyGroupDefaults = {
...proxyGroupDefaults,
proxies: ["REJECT"],
"include-all": true,
};
const SERVICE_ICON_SET_URL =
"https://cdn.jsdelivr.net/gh/xixu-me/favicons@assets/favicons/";
function getServiceIcon(service) {
if (service.icon) return service.icon;
if (service.domain) return extractFavicon(service.domain);
if (service.tld) {
const secondLevelDomain = service.sld || service.name.toLowerCase();
return `${SERVICE_ICON_SET_URL}${secondLevelDomain}.${service.tld}.png`;
}
console.warn(`No icon configuration for service: ${service.name}`);
return null;
}
services.forEach((service) => {
service.icon = getServiceIcon(service);
});
function generateServiceProxyGroups(items, defaultConfig) {
return items.map(({ name, icon }) => ({
...defaultConfig,
name,
icon,
type: "select",
proxies: serviceProxyGroupProxies,
}));
}
function generateLocationPolicyProxyGroups(
items,
defaultConfig,
type,
extraProps = {},
) {
const getStrategyName = (type, strategy) => {
switch (type) {
case "url-test":
return "AUTO";
case "fallback":
return "FALLBACK";
case "load-balance":
switch (strategy) {
case "consistent-hashing":
return "LOAD BALANCING (consistent hashing)";
case "round-robin":
return "LOAD BALANCING (round-robin)";
case "sticky-sessions":
return "LOAD BALANCING (sticky sessions)";
default:
return "LOAD BALANCING";
}
default:
return type.toUpperCase();
}
};
return items.map(({ name, icon, filter }) => {
const strategyName = getStrategyName(type, extraProps.strategy);
const emoji = name.split(" ").pop();
const newName = `${strategyName} ${emoji}`;
return {
...defaultConfig,
name: newName,
type,
icon,
filter,
hidden: true,
...extraProps,
};
});
}
function generateLocationSelectProxyGroups() {
return locations.map(({ name, icon }) => ({
...proxyGroupDefaults,
name,
type: "select",
icon,
proxies: [
`LOAD BALANCING (consistent hashing) ${name.split(" ").pop()}`,
`LOAD BALANCING (round-robin) ${name.split(" ").pop()}`,
`LOAD BALANCING (sticky sessions) ${name.split(" ").pop()}`,
`FALLBACK ${name.split(" ").pop()}`,
`AUTO ${name.split(" ").pop()}`,
],
}));
}
const BASE_ICON_SET_URL =
"https://cdn.jsdelivr.net/gh/Koolson/Qure@master/IconSet/Color/";
// Generate STATIC proxy groups
function generateStaticProxyGroups(defaultConfig) {
return staticProxyNames.map((name, index) => ({
...defaultConfig,
name,
type: "select",
"include-all": true,
icon: `${BASE_ICON_SET_URL}Static.png`,
}));
}
const proxyGroups = [
{
...proxyGroupDefaults,
name: "PROXY",
type: "select",
proxies: [
...staticProxyNames,
"AUTO",
"DIRECT",
"Chinese Mainland 🇨🇳",
...locations.map(({ name }) => name),
],
icon: `${BASE_ICON_SET_URL}Proxy.png`,
},
{
...proxyGroupDefaults,
name: "AUTO",
type: "url-test",
tolerance: 50,
"include-all": true,
icon: `${BASE_ICON_SET_URL}Auto.png`,
hidden: true,
},
...generateStaticProxyGroups(proxyGroupDefaults),
...generateServiceProxyGroups(services, proxyGroupDefaults),
{
...proxyGroupDefaults,
name: "Domestic",
type: "select",
proxies: [
"DIRECT",
"PROXY",
...staticProxyNames,
"AUTO",
"Chinese Mainland 🇨🇳",
...locations.map(({ name }) => name),
],
icon: `${BASE_ICON_SET_URL}Domestic.png`,
},
{
...proxyGroupDefaults,
name: "Others",
type: "select",
proxies: [...serviceProxyGroupProxies],
icon: `${BASE_ICON_SET_URL}Final.png`,
},
{
...proxyGroupDefaults,
name: "Advertising",
type: "select",
proxies: ["REJECT", "DIRECT", ...staticProxyNames],
icon: `${BASE_ICON_SET_URL}Advertising.png`,
},
{
...locationPolicyProxyGroupDefaults,
name: "Chinese Mainland 🇨🇳",
type: "select",
icon: `${LOCATION_ICON_SET_URL}china`,
proxies: ["REJECT"],
"include-all": true,
filter:
"(?i)\u5927\u9646|\u4e2d\u56fd|\u7535\u4fe1|\u79fb\u52a8|\u8054\u901a|Mainland|China|CN|mainland|china|cn|🇨🇳",
},
...generateLocationPolicyProxyGroups(
locations,
locationPolicyProxyGroupDefaults,
"url-test",
{
tolerance: 50,
},
),
...generateLocationPolicyProxyGroups(
locations,
locationPolicyProxyGroupDefaults,
"fallback",
),
...generateLocationPolicyProxyGroups(
locations,
locationPolicyProxyGroupDefaults,
"load-balance",
{
strategy: "round-robin",
},
),
...generateLocationPolicyProxyGroups(
locations,
locationPolicyProxyGroupDefaults,
"load-balance",
{
strategy: "consistent-hashing",
},
),
...generateLocationPolicyProxyGroups(
locations,
locationPolicyProxyGroupDefaults,
"load-balance",
{
strategy: "sticky-sessions",
},
),
...generateLocationSelectProxyGroups(),
];
// Routing Rules
function generateServiceRules(services) {
return services.map(
({ name }) => `RULE-SET,${name.toLowerCase().replace(/\s+/g, "")},${name}`,
);
}
const rules = [
"RULE-SET,applications,DIRECT",
"RULE-SET,win-phonelink,DIRECT",
"RULE-SET,lancidr,DIRECT,no-resolve",
"RULE-SET,private,DIRECT",
...generateServiceRules(services),
"RULE-SET,reject,Advertising",
"RULE-SET,win-spy,Advertising",
"RULE-SET,direct,Domestic",
"RULE-SET,proxy,PROXY",
"RULE-SET,telegramcidr,Telegram",
"RULE-SET,xcidr,X",
"RULE-SET,googlecidr,Google",
"RULE-SET,cloudflarecidr,Cloudflare",
"RULE-SET,cncidr,Domestic",
"MATCH,Others",
];
// Rule Providers
const ruleProviderDefaults = {
type: "http",
interval: 14400,
};
function generateServiceRuleProviders(services, defaultConfig) {
return services.reduce((acc, { name, alias }) => {
const fileName = (alias || name).toLowerCase();
const ruleName = name.toLowerCase().replace(/\s+/g, "");
acc[ruleName] = {
...defaultConfig,
format: "mrs",
behavior: "domain",
url: `https://cdn.jsdelivr.net/gh/xixu-me/RFM@universal/${fileName}.mrs`,
path: `./rulesets/${ruleName}.mrs`,
};
return acc;
}, {});
}
const ruleProviders = {
"fake-ip-filter": {
...ruleProviderDefaults,
format: "mrs",
behavior: "domain",
url: "https://cdn.jsdelivr.net/gh/xixu-me/RFM@basic/fake-ip-filter.mrs",
path: "./rulesets/fake-ip-filter.mrs",
},
"connectivity-check": {
...ruleProviderDefaults,
format: "mrs",
behavior: "domain",
url: "https://cdn.jsdelivr.net/gh/xixu-me/RFM@universal/connectivity-check.mrs",
path: "./rulesets/connectivity-check.mrs",
},
applications: {
...ruleProviderDefaults,
format: "yaml",
behavior: "classical",
url: "https://cdn.jsdelivr.net/gh/xixu-me/RFM@basic/applications.yaml",
path: "./rulesets/applications.yaml",
},
"win-phonelink": {
...ruleProviderDefaults,
format: "yaml",
behavior: "classical",
url: "https://cdn.jsdelivr.net/gh/xixu-me/RFM@fixed/win-phonelink.yaml",
path: "./rulesets/win-phonelink.yaml",
},
lancidr: {
...ruleProviderDefaults,
format: "mrs",
behavior: "ipcidr",
url: "https://cdn.jsdelivr.net/gh/xixu-me/RFM@basic/lancidr.mrs",
path: "./rulesets/lancidr.mrs",
},
cncidr: {
...ruleProviderDefaults,
format: "mrs",
behavior: "ipcidr",
url: "https://cdn.jsdelivr.net/gh/xixu-me/RFM@basic/cncidr.mrs",
path: "./rulesets/cncidr.mrs",
},
cloudflarecidr: {
...ruleProviderDefaults,
format: "mrs",
behavior: "ipcidr",
url: "https://cdn.jsdelivr.net/gh/xixu-me/RFM@basic/cloudflarecidr.mrs",
path: "./rulesets/cloudflarecidr.mrs",
},
googlecidr: {
...ruleProviderDefaults,
format: "mrs",
behavior: "ipcidr",
url: "https://cdn.jsdelivr.net/gh/xixu-me/RFM@basic/googlecidr.mrs",
path: "./rulesets/googlecidr.mrs",
},
telegramcidr: {
...ruleProviderDefaults,
format: "mrs",
behavior: "ipcidr",
url: "https://cdn.jsdelivr.net/gh/xixu-me/RFM@basic/telegramcidr.mrs",
path: "./rulesets/telegramcidr.mrs",
},
xcidr: {
...ruleProviderDefaults,
format: "mrs",
behavior: "ipcidr",
url: "https://cdn.jsdelivr.net/gh/xixu-me/RFM@basic/xcidr.mrs",
path: "./rulesets/xcidr.mrs",
},
private: {
...ruleProviderDefaults,
format: "mrs",
behavior: "domain",
url: "https://cdn.jsdelivr.net/gh/xixu-me/RFM@basic/private.mrs",
path: "./rulesets/private.mrs",
},
direct: {
...ruleProviderDefaults,
format: "mrs",
behavior: "domain",
url: "https://cdn.jsdelivr.net/gh/xixu-me/RFM@basic/direct.mrs",
path: "./rulesets/direct.mrs",
},
proxy: {
...ruleProviderDefaults,
format: "mrs",
behavior: "domain",
url: "https://cdn.jsdelivr.net/gh/xixu-me/RFM@basic/proxy.mrs",
path: "./rulesets/proxy.mrs",
},
"geolocation-cn": {
...ruleProviderDefaults,
format: "mrs",
behavior: "domain",
url: "https://cdn.jsdelivr.net/gh/xixu-me/RFM@universal/geolocation-cn.mrs",
path: "./rulesets/geolocation-cn.mrs",
},
reject: {
...ruleProviderDefaults,
format: "mrs",
behavior: "domain",
url: "https://cdn.jsdelivr.net/gh/xixu-me/RFM@basic/reject.mrs",
path: "./rulesets/reject.mrs",
},
"win-spy": {
...ruleProviderDefaults,
format: "mrs",
behavior: "domain",
url: "https://cdn.jsdelivr.net/gh/xixu-me/RFM@universal/win-spy.mrs",
path: "./rulesets/win-spy.mrs",
},
...generateServiceRuleProviders(services, ruleProviderDefaults),
};
// generate configuration using the above settings
function validateOriginalConfig(config) {
if (!config)
throw new Error("Configuration object cannot be null or undefined");
const proxyCount = Array.isArray(config.proxies) ? config.proxies.length : 0;
const proxyProviderCount = Object.keys(
config["proxy-providers"] || {},
).length;
if (proxyCount === 0 && proxyProviderCount === 0)
throw new Error(
"The original configuration must contain a non-empty proxies array (see https://wiki.\u006D\u0065\u0074\u0061\u0063\u0075\u0062\u0065\u0078.one/en/config/proxies/) or a proxy-providers object with at least one property (see https://wiki.\u006D\u0065\u0074\u0061\u0063\u0075\u0062\u0065\u0078.one/en/config/proxy-providers/)",
);
if (proxyCount > 0) {
const proxiesWithoutRemoteEndpoint = new Set([
"direct",
"dns",
"pass",
"reject",
]);
config.proxies.forEach((proxy, index) => {
if (!proxy.name || !proxy.type) {
throw new Error(
`Invalid proxy number ${
index + 1
} configuration (see https://wiki.\u006D\u0065\u0074\u0061\u0063\u0075\u0062\u0065\u0078.one/en/config/proxies/)`,
);
}
if (
!proxiesWithoutRemoteEndpoint.has(String(proxy.type).toLowerCase()) &&
(!proxy.server || proxy.port == null)
) {
throw new Error(
`Invalid proxy number ${
index + 1
} configuration (see https://wiki.\u006D\u0065\u0074\u0061\u0063\u0075\u0062\u0065\u0078.one/en/config/proxies/)`,
);
}
});
console.log(
`The original configuration contains ${proxyCount} proxies, which will be preserved`,
);
}
if (proxyProviderCount > 0) {
Object.entries(config["proxy-providers"]).forEach(
([name, provider], index) => {
if (!provider.type || (provider.type === "http" && !provider.url)) {
throw new Error(
`Invalid proxy provider number ${
index + 1
} configuration (see https://wiki.\u006D\u0065\u0074\u0061\u0063\u0075\u0062\u0065\u0078.one/en/config/proxy-providers/)`,
);
}
},
);
console.log(
`The original configuration contains ${proxyProviderCount} proxy providers, which will be preserved`,
);
}
return true;
}
function main(config, options = {}) {
try {
if (!options.skipValidation) {
validateOriginalConfig(config);
}
Object.assign(config, generalConfig, {
dns,
hosts,
sniffer,
tun,
"proxy-groups": proxyGroups,
rules,
"rule-providers": ruleProviders,
});
console.log("The generated configuration is as follows");
console.log(config);
return config;
} catch (error) {
console.error(
`An error occurred during configuration generation: ${error.message}`,
);
return { error: error.message, originalConfig: config };
}
}
+89
View File
@@ -0,0 +1,89 @@
const fs = require("node:fs");
const path = require("node:path");
const vm = require("node:vm");
function loadMetaModule() {
const content = fs.readFileSync(path.join(__dirname, "META.js"), "utf8");
const wrapped = `${content}
module.exports = {
main,
validateOriginalConfig,
};`;
const context = {
module: { exports: {} },
console: {
log() {},
warn() {},
error() {},
},
};
vm.runInNewContext(wrapped, context, { filename: "META.js" });
return context.module.exports;
}
function assert(condition, message) {
if (!condition) {
throw new Error(message);
}
}
function runScenario(name, input, options) {
const { main } = loadMetaModule();
const config = main(structuredClone(input), options);
assert(!config.error, `${name}: ${config.error}`);
assert(Array.isArray(config.rules), `${name}: rules should be an array`);
assert(
Array.isArray(config["proxy-groups"]),
`${name}: proxy-groups should be an array`
);
assert(
config["rule-providers"] && typeof config["rule-providers"] === "object",
`${name}: rule-providers should be present`
);
}
function main() {
runScenario(
"proxy-based config",
{
proxies: [
{
name: "sample",
type: "socks5",
server: "127.0.0.1",
port: 1080,
},
],
},
{}
);
runScenario(
"provider-based config",
{
"proxy-providers": {
provider: {
type: "http",
url: "https://example.com/provider.yaml",
path: "./providers/provider.yaml",
},
},
},
{}
);
runScenario("publish generation", {}, { skipValidation: true });
console.log("Validated META.js against 3 scenarios.");
}
try {
main();
} catch (error) {
console.error(error.message);
process.exitCode = 1;
}
+117
View File
@@ -0,0 +1,117 @@
const test = require("node:test");
const assert = require("node:assert/strict");
const fs = require("node:fs");
const path = require("node:path");
const vm = require("node:vm");
const repoRoot = __dirname;
function loadMetaModule() {
const content = fs.readFileSync(path.join(repoRoot, "META.js"), "utf8");
const wrapped = `${content}
module.exports = {
locations,
generalConfig,
tun,
main,
validateOriginalConfig,
};`;
const context = {
module: { exports: {} },
console: {
log() {},
warn() {},
error() {},
},
};
vm.runInNewContext(wrapped, context, { filename: "META.js" });
return context.module.exports;
}
function mihomoRegex(source) {
return new RegExp(source.replace(/^\(\?i\)/, ""), "i");
}
test("validateOriginalConfig accepts mihomo dns outbound proxies", () => {
const { validateOriginalConfig } = loadMetaModule();
assert.doesNotThrow(() =>
validateOriginalConfig({
proxies: [{ name: "dns-out", type: "dns" }],
})
);
});
test("generated config does not force deprecated global fingerprinting", () => {
const { main } = loadMetaModule();
const config = main({
proxies: [
{
name: "sample",
type: "socks5",
server: "127.0.0.1",
port: 1080,
},
],
});
assert.equal(config["global-client-fingerprint"], undefined);
});
test("main can skip source validation for publish-time generation", () => {
const { main } = loadMetaModule();
const config = main({}, { skipValidation: true });
assert.equal(config.error, undefined);
assert.ok(Array.isArray(config.rules));
assert.ok(Array.isArray(config["proxy-groups"]));
});
test("generated tun config fully replaces the source tun settings", () => {
const { main } = loadMetaModule();
const config = main({
proxies: [
{
name: "sample",
type: "socks5",
server: "127.0.0.1",
port: 1080,
},
],
tun: {
enable: false,
"auto-route": false,
"auto-redirect": true,
mtu: 9000,
},
});
assert.deepEqual(JSON.parse(JSON.stringify(config.tun)), {
enable: true,
stack: "mixed",
"auto-route": true,
"auto-detect-interface": true,
"strict-route": true,
"dns-hijack": ["any:53", "tcp://any:53"],
});
});
test("location filters avoid matching unrelated country names", () => {
const { locations } = loadMetaModule();
const india = locations.find((location) => location.name === "India 🇮🇳");
const canada = locations.find((location) => location.name === "Canada 🇨🇦");
assert.ok(india, "India location should exist");
assert.ok(canada, "Canada location should exist");
assert.equal(mihomoRegex(india.filter).test("Argentina 01"), false);
assert.equal(mihomoRegex(india.filter).test("Finland-IEPL"), false);
assert.equal(mihomoRegex(canada.filter).test("Singapore-Anycast"), false);
});
-105
View File
@@ -1,105 +0,0 @@
# META
META stands for Modular Enhanced Traffic Architecture.
That idea shapes the project itself: the inputs stay modular, the generated output adds stronger routing structure on top, and the overall repository is organized as an architecture with clear roles for source, validation, and published artifacts.
The repository currently uses a split branch model:
- `main`: project documentation
- `script`: source code, tests, and GitHub Actions workflow
- `config`: generated `META.yaml`
> [!NOTE]
> If you want to use or customize META, the source of truth is the `script` branch. The `config` branch is generated from it after CI passes.
## What META Generates
META starts from your existing proxy configuration and builds a layered traffic profile instead of leaving you with a flat set of raw inputs:
- Service-oriented policy groups
- Location-based groups with `select`, `url-test`, `fallback`, and multiple load-balancing strategies
- DNS, TUN, rule providers, and routing rules for a broad range of network environments
- A published YAML artifact for users who prefer a static configuration
The generator preserves your original proxies or proxy providers, then injects META's standardized policy groups and rules on top.
## Import URLs
META currently documents both direct raw access and [Xget](https://github.com/xixu-me/xget) for remote imports.
### JavaScript
| Source | URL |
| --- | --- |
| GitHub Raw | <https://raw.githubusercontent.com/xixu-me/meta/refs/heads/script/META.js> |
| Xget | <https://xget.xi-xu.me/gh/xixu-me/meta/raw/refs/heads/script/META.js> |
### YAML
| Source | URL |
| --- | --- |
| GitHub Raw | <https://raw.githubusercontent.com/xixu-me/meta/refs/heads/config/META.yaml> |
| Xget | <https://xget.xi-xu.me/gh/xixu-me/meta/raw/refs/heads/config/META.yaml> |
## Customization
`META.js` is intentionally organized so the top section is the only part most users need to edit:
```javascript
// ################################################### this section can be flexibly customized ###################################################
```
The main customization points are:
- `STATIC_PROXY_COUNT`: number of static groups for IP-sensitive services
- `services`: service list and icon/domain metadata
- `locations`: country and region groups, including keywords, country codes, and emoji matchers
META derives the more error-prone parts, such as location filter regexes, from this top-level data so the editable surface stays small while the generated structure stays rich.
For a GUI-based workflow, use [META.js Customizer](https://github.com/xixu-me/META.js-Customizer) at [metajs.xi-xu.me](https://metajs.xi-xu.me).
## RFM Integration
META is built around [RFM](https://github.com/xixu-me/rfm), which provides the ruleset foundation for the generated configuration.
- META consumes RFM rulesets as its primary rule-provider source
- Domain, classical, and IP-based routing behavior are organized around RFM outputs
- Keeping RFM current directly improves the quality and coverage of META's generated configuration
Within the overall project structure, META acts as the assembly layer while RFM supplies the ruleset base that gives the generated output its breadth and consistency.
## Maintenance
The branches are intentionally separated:
- `script` is where generator changes, tests, and workflow updates happen
- `config` is force-updated by GitHub Actions with the latest generated YAML
- `main` is the stable documentation entrypoint for the project
The source branch uses Bun for local verification and publishes the generated YAML only after automated checks pass. This keeps the documentation branch, source branch, and generated branch aligned without turning the main README into a workflow manual.
## Design Notes
The project leans into the meaning behind its name: small editable building blocks, stronger generated defaults, and a structure that can scale without turning into a monolithic config blob.
- It treats service routing as the primary UX surface instead of exposing only raw proxy groups
- It generates several per-location strategies automatically so users can switch between stability and performance without editing rules
- It keeps source validation strict enough to catch malformed inputs while still supporting special outbound forms used by the underlying ecosystem
- It favors portable defaults so the generated configuration travels better across different clients and environments
## Related Projects
- [META.js Customizer](https://github.com/xixu-me/META.js-Customizer): browser-based META configuration builder
- [RFM](https://github.com/xixu-me/rfm): ruleset backbone used by META
## Disclaimer
- META is a configuration generator. You are responsible for how you deploy and use the generated configuration.
- Compatibility can change as clients, parsers, and third-party services evolve.
- Review generated output before using it in environments where routing, privacy, or availability is sensitive.
## License
Licensed under the MIT License. See [`LICENSE`](./LICENSE).
+15
View File
@@ -0,0 +1,15 @@
{
"lockfileVersion": 1,
"configVersion": 0,
"workspaces": {
"": {
"name": "meta",
"dependencies": {
"yaml": "^2.8.1",
},
},
},
"packages": {
"yaml": ["yaml@2.8.3", "", { "bin": "bin.mjs" }, "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg=="],
}
}
+2
View File
@@ -0,0 +1,2 @@
[install]
auto = "disable"
+12
View File
@@ -0,0 +1,12 @@
{
"name": "meta",
"private": true,
"scripts": {
"generate": "bun META.generate.js",
"test": "bun test META.test.js workflow.test.js",
"run": "bun META.run.js"
},
"dependencies": {
"yaml": "^2.8.1"
}
}
+98
View File
@@ -0,0 +1,98 @@
const test = require("node:test");
const assert = require("node:assert/strict");
const fs = require("node:fs");
const path = require("node:path");
const childProcess = require("node:child_process");
const repoRoot = __dirname;
const workflowPath = path.join(repoRoot, ".github", "workflows", "run.yml");
const runScriptPath = path.join(repoRoot, "META.run.js");
function runBun(args) {
return childProcess.spawnSync("bun", args, {
cwd: repoRoot,
encoding: "utf8",
});
}
function ensureDependenciesInstalled() {
const result = runBun(["install", "--frozen-lockfile"]);
assert.equal(result.status, 0, result.stderr || result.stdout);
}
test("workflow runs CI before run and publish jobs", () => {
const workflow = fs.readFileSync(workflowPath, "utf8");
assert.match(workflow, /^ ci:\s*$/m, "workflow should define a ci job");
assert.match(
workflow,
/uses:\s+oven-sh\/setup-bun@v2/,
"workflow should install Bun via the official setup action"
);
assert.match(
workflow,
/^ run:\s*$(?:\r?\n)(?:\s+.*\r?\n)*?\s+needs:\s+ci\s*$/m,
"run job should depend on ci"
);
assert.match(
workflow,
/^ purge-dns-cache:\s*$(?:\r?\n)(?:\s+.*\r?\n)*?\s+needs:\s+run\s*$/m,
"purge-dns-cache should depend on run"
);
assert.match(
workflow,
/^ generate-config:\s*$(?:\r?\n)(?:\s+.*\r?\n)*?\s+needs:\s+run\s*$/m,
"generate-config should depend on run"
);
});
test("workflow installs dependencies and runs tests in ci job", () => {
const workflow = fs.readFileSync(workflowPath, "utf8");
assert.match(
workflow,
/bun install --frozen-lockfile/,
"ci job should install dependencies with Bun"
);
assert.match(workflow, /bun test/, "ci job should run the test suite with Bun");
assert.match(workflow, /bun run run/, "run job should use Bun scripts");
assert.match(
workflow,
/bun run generate/,
"generate-config job should use Bun scripts"
);
assert.doesNotMatch(
workflow,
/sed -i .*validateOriginalConfig/,
"workflow should not mutate META.js to bypass validation"
);
});
test("META.run.js executes the generator against sample inputs", () => {
const result = runBun([runScriptPath]);
assert.equal(result.status, 0, result.stderr || result.stdout);
assert.match(
result.stdout,
/validated/i,
"run script should report successful validation"
);
});
test("META.generate.js creates YAML output without mutating source", () => {
const outputPath = path.join(repoRoot, "META.yaml");
fs.rmSync(outputPath, { force: true });
ensureDependenciesInstalled();
const result = runBun(["META.generate.js"]);
assert.equal(result.status, 0, result.stderr || result.stdout);
assert.ok(fs.existsSync(outputPath), "generate script should create META.yaml");
assert.match(
fs.readFileSync(outputPath, "utf8"),
/mixed-port:\s*7890/,
"generated YAML should include core config values"
);
fs.rmSync(outputPath, { force: true });
});