mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-11-09 11:52:08 +08:00
chore(contract): check generated bindings before push
This commit is contained in:
@@ -6,6 +6,7 @@ on:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
env:
|
||||
HUSKY: 0
|
||||
@@ -99,20 +100,7 @@ jobs:
|
||||
id: check_inputs
|
||||
uses: dorny/paths-filter@v4
|
||||
with:
|
||||
filters: |
|
||||
contract:
|
||||
- 'src/services/contract.ts'
|
||||
- 'src/services/bindings.ts'
|
||||
- 'Cargo.lock'
|
||||
- 'Cargo.toml'
|
||||
- 'src-tauri/Cargo.toml'
|
||||
- 'crates/**'
|
||||
- 'src-tauri/src/**'
|
||||
- 'scripts/gen-contract.mjs'
|
||||
- 'src-tauri/src/core/notify/**'
|
||||
- 'src-tauri/src/bin/**'
|
||||
- 'src-tauri/src/lib.rs'
|
||||
- '.github/workflows/frontend-check.yml'
|
||||
filters: scripts/contract-inputs.json
|
||||
|
||||
- name: Skip if contract inputs unchanged
|
||||
if: steps.check_inputs.outputs.contract != 'true'
|
||||
@@ -165,8 +153,6 @@ jobs:
|
||||
touch "src-tauri/sidecar/${bin}-x86_64-unknown-linux-gnu"
|
||||
done
|
||||
|
||||
- name: Regenerate contract and require empty diff
|
||||
- name: Check generated contract without changing the checkout
|
||||
if: steps.check_inputs.outputs.contract == 'true'
|
||||
run: |
|
||||
pnpm gen:contract
|
||||
git diff --exit-code -- src/services/bindings.ts
|
||||
run: pnpm gen:contract --check
|
||||
|
||||
@@ -5,4 +5,5 @@ if ! command -v "cargo-make" >/dev/null 2>&1; then
|
||||
cargo install --force cargo-make
|
||||
fi
|
||||
|
||||
node scripts/check-contract-changes.mjs --push "$1" || exit $?
|
||||
cargo make pre-push
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://unpkg.com/knip@6/schema.json",
|
||||
"entry": ["src/polyfills/*.js!"],
|
||||
"entry": ["src/polyfills/*.js!", "scripts/check-contract-changes.mjs!"],
|
||||
"project": ["src/**/*.{js,jsx,ts,tsx,sass,scss}!", "scripts/**/*.{js,mjs}!"],
|
||||
"include": [
|
||||
"files",
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import { execFileSync } from 'node:child_process'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
|
||||
const ROOT = path.resolve(import.meta.dirname, '..')
|
||||
const git = (...args) =>
|
||||
execFileSync('git', args, { cwd: ROOT, encoding: 'utf8' }).trim()
|
||||
const { contract } = JSON.parse(
|
||||
fs.readFileSync(path.join(ROOT, 'scripts/contract-inputs.json'), 'utf8'),
|
||||
)
|
||||
const inputs = contract.map((pattern) => `:(glob)${pattern}`)
|
||||
const args = process.argv.slice(2)
|
||||
if (args.length !== 2 || args[0] !== '--push') {
|
||||
console.error('usage: check-contract-changes.mjs --push <remote>')
|
||||
process.exit(2)
|
||||
}
|
||||
const hasChanges = (...range) =>
|
||||
git('diff', '--name-only', ...range, '--', ...inputs) !== ''
|
||||
const assertCleanInputs = (...range) => {
|
||||
if (
|
||||
hasChanges(...range) ||
|
||||
git('ls-files', '--others', '--exclude-standard', '--', ...inputs)
|
||||
) {
|
||||
throw new Error(
|
||||
'Contract inputs differ from the pushed commit. Commit those changes before pushing.',
|
||||
)
|
||||
}
|
||||
}
|
||||
let relevant = false
|
||||
const head = git('rev-parse', 'HEAD')
|
||||
for (const line of fs
|
||||
.readFileSync(0, 'utf8')
|
||||
.trim()
|
||||
.split('\n')
|
||||
.filter(Boolean)) {
|
||||
const fields = line.trim().split(/\s+/)
|
||||
if (
|
||||
fields.length !== 4 ||
|
||||
![fields[1], fields[3]].every((sha) => /^[a-f0-9]{40,64}$/.test(sha))
|
||||
) {
|
||||
throw new Error('Invalid pre-push ref update')
|
||||
}
|
||||
const [, local, , remote] = fields
|
||||
if (/^0+$/.test(local) || local === remote) continue
|
||||
// A new remote branch has no upstream; use its fork point with the remote default branch.
|
||||
const base = /^0+$/.test(remote)
|
||||
? git('merge-base', local, `refs/remotes/${args[1]}/HEAD`)
|
||||
: remote
|
||||
if (!hasChanges(base, local)) continue
|
||||
const commit = git('rev-parse', `${local}^{commit}`)
|
||||
if (commit !== head)
|
||||
throw new Error(
|
||||
'Check out the pushed commit before validating its changed IPC contract.',
|
||||
)
|
||||
assertCleanInputs('HEAD')
|
||||
relevant = true
|
||||
}
|
||||
if (relevant) {
|
||||
execFileSync(
|
||||
process.execPath,
|
||||
[path.join(ROOT, 'scripts/gen-contract.mjs'), '--check'],
|
||||
{
|
||||
cwd: ROOT,
|
||||
stdio: 'inherit',
|
||||
},
|
||||
)
|
||||
} else {
|
||||
console.log('[contract] inputs unchanged; skipping Rust generation')
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"contract": [
|
||||
"src-tauri/**/*.rs",
|
||||
"crates/**/*.rs",
|
||||
"Cargo.toml",
|
||||
"Cargo.lock",
|
||||
"src-tauri/Cargo.toml",
|
||||
"crates/**/Cargo.toml",
|
||||
"rust-toolchain.toml",
|
||||
"src-tauri/tauri.conf*.json",
|
||||
"src/services/bindings.ts",
|
||||
"scripts/gen-contract.mjs",
|
||||
"scripts/check-contract-changes.mjs",
|
||||
"biome.json",
|
||||
"biome.jsonc",
|
||||
"package.json",
|
||||
"pnpm-lock.yaml",
|
||||
"pnpm-workspace.yaml",
|
||||
"scripts/contract-inputs.json",
|
||||
".github/workflows/frontend-check.yml",
|
||||
".husky/pre-push"
|
||||
]
|
||||
}
|
||||
+51
-18
@@ -1,22 +1,55 @@
|
||||
import { execFileSync } from 'node:child_process'
|
||||
import fs from 'node:fs'
|
||||
import os from 'node:os'
|
||||
import path from 'node:path'
|
||||
|
||||
const ROOT = path.resolve(import.meta.dirname, '..')
|
||||
execFileSync(
|
||||
'cargo',
|
||||
['run', '-p', 'clash-verge', '--bin', 'frontend-contract', '--locked', '-q'],
|
||||
{
|
||||
cwd: ROOT,
|
||||
stdio: 'inherit',
|
||||
},
|
||||
)
|
||||
execFileSync(
|
||||
process.execPath,
|
||||
[
|
||||
path.join(ROOT, 'node_modules/@biomejs/biome/bin/biome'),
|
||||
'format',
|
||||
'--write',
|
||||
'src/services/bindings.ts',
|
||||
],
|
||||
{ cwd: ROOT, stdio: 'inherit' },
|
||||
)
|
||||
const BINDINGS = path.join(ROOT, 'src/services/bindings.ts')
|
||||
const args = process.argv.slice(2)
|
||||
if (args.length > 1 || (args.length === 1 && args[0] !== '--check')) {
|
||||
console.error('usage: gen-contract.mjs [--check]')
|
||||
process.exit(2)
|
||||
}
|
||||
const check = args[0] === '--check'
|
||||
const temporary = check
|
||||
? fs.mkdtempSync(path.join(os.tmpdir(), 'verge-contract-'))
|
||||
: null
|
||||
const output = temporary ? path.join(temporary, 'bindings.ts') : BINDINGS
|
||||
try {
|
||||
execFileSync(
|
||||
'cargo',
|
||||
[
|
||||
'run',
|
||||
'-p',
|
||||
'clash-verge',
|
||||
'--bin',
|
||||
'frontend-contract',
|
||||
'--locked',
|
||||
'-q',
|
||||
...(check ? ['--', output] : []),
|
||||
],
|
||||
{ cwd: ROOT, stdio: 'inherit' },
|
||||
)
|
||||
const formatted = execFileSync(
|
||||
process.execPath,
|
||||
[
|
||||
path.join(ROOT, 'node_modules/@biomejs/biome/bin/biome'),
|
||||
'format',
|
||||
'--stdin-file-path',
|
||||
BINDINGS,
|
||||
],
|
||||
{ cwd: ROOT, input: fs.readFileSync(output) },
|
||||
)
|
||||
if (check) {
|
||||
if (!formatted.equals(fs.readFileSync(BINDINGS))) {
|
||||
console.error(
|
||||
'IPC bindings are out of date. Run pnpm gen:contract and commit the generated file.',
|
||||
)
|
||||
process.exitCode = 1
|
||||
}
|
||||
} else {
|
||||
fs.writeFileSync(BINDINGS, formatted)
|
||||
}
|
||||
} finally {
|
||||
if (temporary) fs.rmSync(temporary, { recursive: true, force: true })
|
||||
}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
app_lib::ipc::builder().export(
|
||||
specta_typescript::Typescript::default(),
|
||||
std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../src/services/bindings.ts"),
|
||||
)?;
|
||||
let mut args = std::env::args_os().skip(1);
|
||||
let output = args
|
||||
.next()
|
||||
.map(std::path::PathBuf::from)
|
||||
.unwrap_or_else(|| std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../src/services/bindings.ts"));
|
||||
if args.next().is_some() {
|
||||
return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "expected at most one output path").into());
|
||||
}
|
||||
app_lib::ipc::builder().export(specta_typescript::Typescript::default(), output)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user