Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6faea123c6 |
No files matched your search
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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).
|
||||
Reference in new issue
Block a user