fix(jd): publish Guanghu router runtime source
This commit is contained in:
parent
e82a377b58
commit
e2bb573a58
14 changed files with 1492 additions and 73 deletions
|
|
@ -7,6 +7,8 @@
|
|||
const fs = require("node:fs");
|
||||
const os = require("node:os");
|
||||
const path = require("node:path");
|
||||
const crypto = require("node:crypto");
|
||||
const { fileURLToPath } = require("node:url");
|
||||
const { execFile } = require("node:child_process");
|
||||
|
||||
function loadRegistry(file) {
|
||||
|
|
@ -23,7 +25,10 @@ async function receiveBundle(request, options = {}) {
|
|||
const expectedHead = String(request.expected_head || "").toLowerCase();
|
||||
const entry = registry[repo];
|
||||
if (!entry) return blocked("repo_not_allowlisted");
|
||||
if (!/^[a-z0-9][a-z0-9._/-]{0,199}$/.test(branch) || branch !== String(entry.branch || "main")) return blocked("branch_not_allowlisted");
|
||||
const allowedBranches = Array.isArray(entry.branches)
|
||||
? entry.branches.map(String)
|
||||
: [String(entry.branch || "main")];
|
||||
if (!/^[a-z0-9][a-z0-9._/-]{0,199}$/.test(branch) || !allowedBranches.includes(branch)) return blocked("branch_not_allowlisted");
|
||||
if (!/^[0-9a-f]{40}$/.test(expectedHead)) return blocked("expected_head_required");
|
||||
const bundlePath = checkedBundlePath(request.bundle_path, options.uploadDir || process.env.LAKE_LAMP_REPO_UPLOAD_DIR || "/var/lib/guanghu/repo-push-uploads");
|
||||
if (!bundlePath) return blocked("bundle_path_invalid");
|
||||
|
|
@ -31,16 +36,53 @@ async function receiveBundle(request, options = {}) {
|
|||
const temporary = fs.mkdtempSync(path.join(options.tempDir || os.tmpdir(), "lake-lamp-receive-"));
|
||||
const bare = path.join(temporary, "quarantine.git");
|
||||
try {
|
||||
await run(["init", "--bare", bare]);
|
||||
// Clone the registered repository into quarantine instead of initializing an
|
||||
// empty repository and fetching refs. A registered source may itself be
|
||||
// shallow; cloning preserves its shallow boundary metadata, while a ref-only
|
||||
// fetch can leave the prerequisite commit present but its parents unreadable.
|
||||
await run(["clone", "--bare", "--no-local", entry.remote, bare]);
|
||||
await run(["-C", bare, "bundle", "verify", bundlePath]);
|
||||
await run(["-C", bare, "fetch", bundlePath, `refs/heads/${branch}:refs/heads/incoming`]);
|
||||
const incoming = (await run(["-C", bare, "rev-parse", "refs/heads/incoming"])).stdout.trim().toLowerCase();
|
||||
if (!/^[0-9a-f]{40}$/.test(incoming)) return blocked("bundle_branch_missing");
|
||||
const remoteHead = parseRemoteHead((await run(["ls-remote", "--exit-code", "--heads", entry.remote, `refs/heads/${branch}`])).stdout);
|
||||
if (remoteHead !== expectedHead) return blocked("expected_head_mismatch", { expected_head: expectedHead, actual_head: remoteHead || "" });
|
||||
const ancestry = await run(["-C", bare, "merge-base", "--is-ancestor", expectedHead, incoming], { allowFailure: true });
|
||||
if (!ancestry.ok) return blocked("non_fast_forward_bundle", { expected_head: expectedHead, incoming_commit: incoming });
|
||||
await run(["-C", bare, "push", entry.remote, `refs/heads/incoming:refs/heads/${branch}`]);
|
||||
const remoteResult = await run(
|
||||
["ls-remote", "--exit-code", "--heads", entry.remote, `refs/heads/${branch}`],
|
||||
{ allowFailure: true },
|
||||
);
|
||||
const remoteHead = parseRemoteHead(remoteResult.stdout);
|
||||
const expectedMissing = expectedHead === "0".repeat(40);
|
||||
if ((expectedMissing && remoteHead) || (!expectedMissing && remoteHead !== expectedHead)) {
|
||||
return blocked("expected_head_mismatch", { expected_head: expectedHead, actual_head: remoteHead || "" });
|
||||
}
|
||||
if (!expectedMissing) {
|
||||
const ancestry = await run(["-C", bare, "merge-base", "--is-ancestor", expectedHead, incoming], { allowFailure: true });
|
||||
if (!ancestry.ok) return blocked("non_fast_forward_bundle", { expected_head: expectedHead, incoming_commit: incoming });
|
||||
}
|
||||
const targetPath = localRepositoryPath(entry.remote);
|
||||
if (!targetPath) return blocked("repository_receive_path_invalid");
|
||||
const transferRef = `refs/guanghu-router/${crypto.randomUUID()}`;
|
||||
try {
|
||||
await run([
|
||||
"-C",
|
||||
targetPath,
|
||||
"fetch",
|
||||
"--no-tags",
|
||||
bare,
|
||||
`refs/heads/incoming:${transferRef}`,
|
||||
]);
|
||||
const transferred = (await run(["-C", targetPath, "rev-parse", transferRef])).stdout.trim().toLowerCase();
|
||||
if (transferred !== incoming) return blocked("repository_transfer_mismatch");
|
||||
await run([
|
||||
"-C",
|
||||
targetPath,
|
||||
"update-ref",
|
||||
`refs/heads/${branch}`,
|
||||
incoming,
|
||||
expectedMissing ? "0".repeat(40) : expectedHead,
|
||||
]);
|
||||
} finally {
|
||||
await run(["-C", targetPath, "update-ref", "-d", transferRef], { allowFailure: true });
|
||||
}
|
||||
return { ok: true, repo, branch, commit_sha: incoming, verification_url: String(entry.verification_url || ""), diagnostic_code: "repo_push_succeeded", deployment: { state: "not_requested", next_step: "仅当冰朔与语言层确认该提交需要部署时,再创建绑定此 SHA 的部署工单并显式派发。" } };
|
||||
} catch (error) {
|
||||
return blocked("repo_push_receiver_failed", { evidence: String(error && error.message || "receiver failure").slice(0, 600) });
|
||||
|
|
@ -49,6 +91,41 @@ async function receiveBundle(request, options = {}) {
|
|||
}
|
||||
}
|
||||
|
||||
function resolveRepository(remoteUrl, registry) {
|
||||
const requested = normalizedSourceUrl(remoteUrl);
|
||||
if (!requested) return blocked("local_repository_remote_missing");
|
||||
for (const [repo, entry] of Object.entries(registry || {})) {
|
||||
const sourceUrls = Array.isArray(entry && entry.source_urls)
|
||||
? entry.source_urls
|
||||
: [];
|
||||
if (sourceUrls.some(value => normalizedSourceUrl(value) === requested)) {
|
||||
return {
|
||||
ok: true,
|
||||
diagnostic_code: "repository_registered",
|
||||
repo,
|
||||
branches: Array.isArray(entry.branches)
|
||||
? entry.branches.map(String)
|
||||
: [String(entry.branch || "main")],
|
||||
};
|
||||
}
|
||||
}
|
||||
return blocked("repository_not_registered");
|
||||
}
|
||||
|
||||
function normalizedSourceUrl(value) {
|
||||
return String(value || "").trim().replace(/\/+$/, "").toLowerCase();
|
||||
}
|
||||
|
||||
function localRepositoryPath(remote) {
|
||||
try {
|
||||
const value = String(remote || "");
|
||||
const resolved = value.startsWith("file://") ? fileURLToPath(value) : value;
|
||||
return path.isAbsolute(resolved) ? resolved : "";
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
function checkedBundlePath(value, uploadDir) {
|
||||
try {
|
||||
const root = fs.realpathSync(uploadDir);
|
||||
|
|
@ -69,4 +146,11 @@ function runGit(args, options = {}) {
|
|||
}));
|
||||
}
|
||||
|
||||
module.exports = { receiveBundle, checkedBundlePath, parseRemoteHead };
|
||||
module.exports = {
|
||||
loadRegistry,
|
||||
receiveBundle,
|
||||
checkedBundlePath,
|
||||
parseRemoteHead,
|
||||
resolveRepository,
|
||||
localRepositoryPath,
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue