源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const scriptDirectory = dirname(fileURLToPath(import.meta.url));
|
||||
const workspaceRoot = join(scriptDirectory, "..", "..");
|
||||
const pnpmCommand = process.platform === "win32" ? "pnpm.cmd" : "pnpm";
|
||||
const cliArgs =
|
||||
process.argv[2] === "--" ? process.argv.slice(3) : process.argv.slice(2);
|
||||
const dryRun = cliArgs.includes("--dry-run");
|
||||
|
||||
if (cliArgs.some((arg) => arg !== "--dry-run")) {
|
||||
fail("usage: pnpm push:checked [-- --dry-run]");
|
||||
}
|
||||
|
||||
const branch = gitOutput(["branch", "--show-current"]);
|
||||
if (!branch) {
|
||||
fail("push:checked requires a named current branch");
|
||||
}
|
||||
|
||||
if (dryRun) {
|
||||
warnIfDirtyWorktree();
|
||||
} else {
|
||||
assertCleanWorktree();
|
||||
}
|
||||
|
||||
const remote = branchConfig("remote") ?? "origin";
|
||||
if (remote === ".") {
|
||||
fail("push:checked does not support local-dot branch remotes");
|
||||
}
|
||||
|
||||
const remoteBranch = normalizeMergeRef(branchConfig("merge")) ?? branch;
|
||||
const remoteRef = `refs/remotes/${remote}/${remoteBranch}`;
|
||||
const pushRef = `refs/heads/${remoteBranch}`;
|
||||
|
||||
console.log(`push:checked branch ${branch}`);
|
||||
console.log(`push:checked remote ${remote}/${remoteBranch}`);
|
||||
|
||||
const remoteHead = lsRemoteHead(remote, remoteBranch);
|
||||
if (remoteHead) {
|
||||
run("git", ["fetch", "--no-tags", remote, `+${pushRef}:${remoteRef}`]);
|
||||
assertRemoteHasNoNewCommits(remoteRef);
|
||||
} else {
|
||||
console.log("push:checked remote branch does not exist yet");
|
||||
}
|
||||
|
||||
if (dryRun) {
|
||||
console.log("push:checked dry run: would run pnpm check:full");
|
||||
console.log(
|
||||
`push:checked dry run: would push HEAD to ${remote}/${remoteBranch}`
|
||||
);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
run(pnpmCommand, ["run", "check:full"], { stdio: "inherit" });
|
||||
|
||||
const lease = `--force-with-lease=${pushRef}:${remoteHead ?? ""}`;
|
||||
run("git", ["push", lease, "--set-upstream", remote, `HEAD:${pushRef}`], {
|
||||
env: { ...process.env, HUSKY: "0" },
|
||||
stdio: "inherit"
|
||||
});
|
||||
|
||||
console.log("push:checked pushed");
|
||||
|
||||
function branchConfig(key) {
|
||||
const value = gitOutput(["config", `branch.${branch}.${key}`], {
|
||||
allowFailure: true
|
||||
});
|
||||
return value || null;
|
||||
}
|
||||
|
||||
function normalizeMergeRef(value) {
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
const prefix = "refs/heads/";
|
||||
return value.startsWith(prefix) ? value.slice(prefix.length) : value;
|
||||
}
|
||||
|
||||
function lsRemoteHead(remoteName, branchName) {
|
||||
const result = spawnSync(
|
||||
"git",
|
||||
["ls-remote", "--heads", remoteName, branchName],
|
||||
{
|
||||
cwd: workspaceRoot,
|
||||
encoding: "utf8"
|
||||
}
|
||||
);
|
||||
|
||||
if (result.status !== 0) {
|
||||
if (result.stderr.trim()) {
|
||||
process.stderr.write(result.stderr);
|
||||
}
|
||||
fail(`failed to inspect ${remoteName}/${branchName}`);
|
||||
}
|
||||
|
||||
const firstLine = result.stdout
|
||||
.split("\n")
|
||||
.map((line) => line.trim())
|
||||
.find(Boolean);
|
||||
return firstLine?.split(/\s+/u)[0] ?? null;
|
||||
}
|
||||
|
||||
function assertRemoteHasNoNewCommits(ref) {
|
||||
const counts = gitOutput([
|
||||
"rev-list",
|
||||
"--left-right",
|
||||
"--count",
|
||||
`HEAD...${ref}`
|
||||
])
|
||||
.split(/\s+/u)
|
||||
.map((value) => Number.parseInt(value, 10));
|
||||
const [ahead, behind] = counts;
|
||||
if (behind === 0) {
|
||||
console.log(`push:checked local ahead ${ahead}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const remoteCommits = gitOutput(["log", "--oneline", `HEAD..${ref}`], {
|
||||
allowFailure: true
|
||||
});
|
||||
if (remoteCommits) {
|
||||
process.stderr.write(`remote has new commit(s):\n${remoteCommits}\n`);
|
||||
}
|
||||
fail("fetch/rebase before running check:full");
|
||||
}
|
||||
|
||||
function assertCleanWorktree() {
|
||||
const status = gitOutput(["status", "--porcelain"]);
|
||||
if (!status) {
|
||||
return;
|
||||
}
|
||||
process.stderr.write(`${status}\n`);
|
||||
fail("commit or stash local changes before push:checked");
|
||||
}
|
||||
|
||||
function warnIfDirtyWorktree() {
|
||||
const status = gitOutput(["status", "--porcelain"]);
|
||||
if (status) {
|
||||
process.stderr.write(
|
||||
"push:checked dry run: real push requires clean worktree\n"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function gitOutput(args, options = {}) {
|
||||
const result = spawnSync("git", args, {
|
||||
cwd: workspaceRoot,
|
||||
encoding: "utf8"
|
||||
});
|
||||
if (result.status !== 0) {
|
||||
if (options.allowFailure) {
|
||||
return "";
|
||||
}
|
||||
if (result.stderr.trim()) {
|
||||
process.stderr.write(result.stderr);
|
||||
}
|
||||
fail(`git ${args.join(" ")} failed`);
|
||||
}
|
||||
return result.stdout.trim();
|
||||
}
|
||||
|
||||
function run(command, args, options = {}) {
|
||||
const result = spawnSync(command, args, {
|
||||
cwd: workspaceRoot,
|
||||
env: options.env ?? process.env,
|
||||
stdio: options.stdio ?? "inherit"
|
||||
});
|
||||
if (result.status !== 0) {
|
||||
fail(`${command} ${args.join(" ")} failed`);
|
||||
}
|
||||
}
|
||||
|
||||
function fail(message) {
|
||||
process.stderr.write(`push:checked: ${message}\n`);
|
||||
process.exit(1);
|
||||
}
|
||||
Reference in New Issue
Block a user