linear-grab-bridge 0.18.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/linear-grab-bridge.mjs +58 -1
- package/package.json +1 -1
package/linear-grab-bridge.mjs
CHANGED
|
@@ -28,7 +28,7 @@ const flag = (name, fallback) => {
|
|
|
28
28
|
const PORT = Number(flag('--port', '4577'));
|
|
29
29
|
const DIR = flag('--dir', process.cwd());
|
|
30
30
|
const CLAUDE_BIN = flag('--claude', 'claude');
|
|
31
|
-
const VERSION = '0.
|
|
31
|
+
const VERSION = '0.20.0';
|
|
32
32
|
|
|
33
33
|
/** Best-effort command runner (git/gh introspection). Never throws. */
|
|
34
34
|
function run(cmd, args, cwd = DIR) {
|
|
@@ -620,6 +620,63 @@ createServer(async (req, res) => {
|
|
|
620
620
|
);
|
|
621
621
|
return json(res, 200, { statuses, previews });
|
|
622
622
|
}
|
|
623
|
+
// Live status of the staging deploy: Vercel mirrors every branch deploy
|
|
624
|
+
// into GitHub Deployments (state + environment_url) — pollable via gh.
|
|
625
|
+
if (req.method === 'POST' && url.pathname === '/branch/status') {
|
|
626
|
+
const body = await readBody(req);
|
|
627
|
+
const prUrl = String(body.url ?? '');
|
|
628
|
+
const base = String(body.base || 'staging').replace(/[^\w./-]/g, '');
|
|
629
|
+
const m = prUrl.match(/^https:\/\/github\.com\/([\w.-]+)\/([\w.-]+)\/pull\/\d+$/);
|
|
630
|
+
if (!m) return json(res, 400, { error: 'invalid PR url' });
|
|
631
|
+
const repo = `${m[1]}/${m[2]}`;
|
|
632
|
+
try {
|
|
633
|
+
const deps = JSON.parse(
|
|
634
|
+
(await run('gh', ['api', `repos/${repo}/deployments?ref=${base}&per_page=1`])) ?? '[]',
|
|
635
|
+
);
|
|
636
|
+
if (!deps.length) return json(res, 200, { state: 'none' });
|
|
637
|
+
const statuses = JSON.parse(
|
|
638
|
+
(await run('gh', ['api', `repos/${repo}/deployments/${deps[0].id}/statuses?per_page=1`])) ?? '[]',
|
|
639
|
+
);
|
|
640
|
+
const st = statuses[0] ?? null;
|
|
641
|
+
return json(res, 200, {
|
|
642
|
+
state: st?.state ?? 'pending', // pending | in_progress | success | failure | error
|
|
643
|
+
url: st?.environment_url ?? null,
|
|
644
|
+
at: st?.updated_at ?? deps[0].created_at,
|
|
645
|
+
sha: (deps[0].sha ?? '').slice(0, 7),
|
|
646
|
+
});
|
|
647
|
+
} catch {
|
|
648
|
+
return json(res, 200, { state: 'unknown' });
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
// Merge a PR's branch into a staging branch (creating it from the default
|
|
652
|
+
// branch if missing) — Vercel then deploys it to the staging domain.
|
|
653
|
+
if (req.method === 'POST' && url.pathname === '/pr/stage') {
|
|
654
|
+
const body = await readBody(req);
|
|
655
|
+
const prUrl = String(body.url ?? '');
|
|
656
|
+
const base = String(body.base || 'staging').replace(/[^\w./-]/g, '');
|
|
657
|
+
const m = prUrl.match(/^https:\/\/github\.com\/([\w.-]+)\/([\w.-]+)\/pull\/\d+$/);
|
|
658
|
+
if (!m) return json(res, 400, { error: 'invalid PR url' });
|
|
659
|
+
const repo = `${m[1]}/${m[2]}`;
|
|
660
|
+
let head = null;
|
|
661
|
+
try {
|
|
662
|
+
head = JSON.parse((await run('gh', ['pr', 'view', prUrl, '--json', 'headRefName'])) ?? '').headRefName;
|
|
663
|
+
} catch {
|
|
664
|
+
/* fallthrough */
|
|
665
|
+
}
|
|
666
|
+
if (!head) return json(res, 500, { error: 'could not resolve the PR head branch (is gh authenticated?)' });
|
|
667
|
+
const baseExists = await run('gh', ['api', `repos/${repo}/branches/${base}`]);
|
|
668
|
+
if (baseExists == null) {
|
|
669
|
+
const def = ((await run('gh', ['api', `repos/${repo}`, '-q', '.default_branch'])) ?? 'main').trim();
|
|
670
|
+
const sha = ((await run('gh', ['api', `repos/${repo}/git/ref/heads/${def}`, '-q', '.object.sha'])) ?? '').trim();
|
|
671
|
+
if (!sha) return json(res, 500, { error: `staging branch missing and could not read ${def}` });
|
|
672
|
+
const created = await run('gh', ['api', '-X', 'POST', `repos/${repo}/git/refs`, '-f', `ref=refs/heads/${base}`, '-f', `sha=${sha}`]);
|
|
673
|
+
if (created == null) return json(res, 500, { error: `could not create branch ${base}` });
|
|
674
|
+
}
|
|
675
|
+
const out = await run('gh', ['api', '-X', 'POST', `repos/${repo}/merges`, '-f', `base=${base}`, '-f', `head=${head}`]);
|
|
676
|
+
if (out == null)
|
|
677
|
+
return json(res, 409, { error: `merge of ${head} into ${base} failed — likely a conflict; resolve manually` });
|
|
678
|
+
return json(res, 200, { ok: true, base, head });
|
|
679
|
+
}
|
|
623
680
|
// Fast-merge: after reviewing the demo, one click merges the PR via gh.
|
|
624
681
|
if (req.method === 'POST' && url.pathname === '/pr/merge') {
|
|
625
682
|
const body = await readBody(req);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "linear-grab-bridge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"description": "Local bridge for Linear Grab — delegate issues from the browser panel to headless Claude Code sessions running in your repo, with live status and an upload relay.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|