feat(lanes): add POST /:id/sync-base route (E2)
This commit is contained in:
+53
-1
@@ -32,6 +32,7 @@ const {
|
|||||||
slugify,
|
slugify,
|
||||||
} = require("../lib/worktree");
|
} = require("../lib/worktree");
|
||||||
const { withLaneLock } = require("../lib/lane-lock");
|
const { withLaneLock } = require("../lib/lane-lock");
|
||||||
|
const { checkSync, mergeSync, continueSync } = require("../lib/lane-sync");
|
||||||
const { HOOKS, runHook, resolveProfile } = require("../lib/lane-profile");
|
const { HOOKS, runHook, resolveProfile } = require("../lib/lane-profile");
|
||||||
const { slotDirs } = require("../lib/lane-slots");
|
const { slotDirs } = require("../lib/lane-slots");
|
||||||
const {
|
const {
|
||||||
@@ -563,7 +564,15 @@ function sendLifecycleError(res, err) {
|
|||||||
|
|
||||||
/** Map a runtime error onto its status code. */
|
/** Map a runtime error onto its status code. */
|
||||||
function sendRuntimeError(res, err) {
|
function sendRuntimeError(res, err) {
|
||||||
const badRequest = ["ENOPROFILE", "ENOHOOK", "EBADLANEDIR", "EBADSVC"];
|
const badRequest = [
|
||||||
|
"ENOPROFILE",
|
||||||
|
"ENOHOOK",
|
||||||
|
"EBADLANEDIR",
|
||||||
|
"EBADSVC",
|
||||||
|
"EBADBRANCH",
|
||||||
|
"EUNRESOLVED",
|
||||||
|
"EMERGEUNCOMMITTED",
|
||||||
|
];
|
||||||
if (badRequest.includes(err.code)) {
|
if (badRequest.includes(err.code)) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
error: {
|
error: {
|
||||||
@@ -759,6 +768,49 @@ router.post("/:id/hook/:name", sameOriginGuard, (req, res) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ONE sanctioned merge in the ship-feature-lane pipeline: origin/development
|
||||||
|
* INTO a feature branch, gated by a migration-number collision preflight.
|
||||||
|
* Synchronous — a fetch + collision-check + merge is seconds of git work, not
|
||||||
|
* the minutes a build/test hook can take, so this follows GET /:id/git's
|
||||||
|
* pattern rather than the hook route's 202-and-broadcast.
|
||||||
|
*
|
||||||
|
* Returns 200 with {code: 0|4|5, ...} for every DOCUMENTED outcome — a
|
||||||
|
* migration collision or a left-in-place conflict is an expected result, not
|
||||||
|
* an HTTP error. A malformed request, a missing profile, or an out-of-order
|
||||||
|
* --continue is the only case that answers with an `error` body.
|
||||||
|
*
|
||||||
|
* Never writes stage/status/notes — same boundary the hook and runtime
|
||||||
|
* routes already keep; the caller decides what a collision or conflict means
|
||||||
|
* for the lane's declared stage.
|
||||||
|
*/
|
||||||
|
router.post("/:id/sync-base", sameOriginGuard, async (req, res) => {
|
||||||
|
const lane = laneOr404(req, res);
|
||||||
|
if (!lane) return;
|
||||||
|
let profile;
|
||||||
|
try {
|
||||||
|
profile = requireProfile(lane);
|
||||||
|
} catch (err) {
|
||||||
|
return sendRuntimeError(res, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mode = ["check", "merge", "continue"].includes(req.body?.mode) ? req.body.mode : "merge";
|
||||||
|
const branch =
|
||||||
|
typeof req.body?.branch === "string" && req.body.branch ? req.body.branch : undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await withLaneLock(lane.id, () => {
|
||||||
|
const current = lanesLib.getLane(lane.id);
|
||||||
|
if (mode === "check") return checkSync(current, profile, branch);
|
||||||
|
if (mode === "continue") return continueSync(current, profile, branch);
|
||||||
|
return mergeSync(current, profile, branch);
|
||||||
|
});
|
||||||
|
res.json(result);
|
||||||
|
} catch (err) {
|
||||||
|
sendRuntimeError(res, err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lane control. Deliberately thin: every action maps onto one existing
|
* Lane control. Deliberately thin: every action maps onto one existing
|
||||||
* run-spawner call. There is no queue, no chaining, no gate evaluation — the
|
* run-spawner call. There is no queue, no chaining, no gate evaluation — the
|
||||||
|
|||||||
Reference in New Issue
Block a user