design-share 0.2.0 → 0.2.2
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/package.json +1 -1
- package/public/app.js +8 -0
- package/public/style.css +12 -0
- package/src/github.js +24 -0
- package/src/server.js +9 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "design-share",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Live branch previews and pinned comments for design teams. Zero config, no server, git is the backend.",
|
|
5
5
|
"keywords": ["design", "preview", "branches", "collaboration", "cli"],
|
|
6
6
|
"license": "MIT",
|
package/public/app.js
CHANGED
|
@@ -188,12 +188,20 @@ function branchRow({ user, branch, time, dim, own }) {
|
|
|
188
188
|
else if (isOwnServing && prev && prev.status === 'ready') dotCls = 'live';
|
|
189
189
|
|
|
190
190
|
const unresolved = openCount(branch);
|
|
191
|
+
const pr = b.prs && b.prs[branch];
|
|
191
192
|
btn.innerHTML = `
|
|
192
193
|
<span class="dot ${dotCls}"></span>
|
|
193
194
|
<span class="branch-label">${escapeHtml(branch)}</span>
|
|
194
195
|
${unresolved ? `<span class="badge">${unresolved}</span>` : ''}
|
|
196
|
+
${pr ? `<span class="pr-link" title="${escapeHtml(`PR #${pr.number}: ${pr.title || ''}`)}">#${pr.number}</span>` : ''}
|
|
195
197
|
<span class="meta">${timeAgo(time)}</span>`;
|
|
196
198
|
btn.onclick = () => select(user, branch);
|
|
199
|
+
if (pr) {
|
|
200
|
+
btn.querySelector('.pr-link').onclick = (e) => {
|
|
201
|
+
e.stopPropagation();
|
|
202
|
+
window.open(pr.url, '_blank');
|
|
203
|
+
};
|
|
204
|
+
}
|
|
197
205
|
return btn;
|
|
198
206
|
}
|
|
199
207
|
|
package/public/style.css
CHANGED
|
@@ -123,6 +123,18 @@ button { font: inherit; cursor: pointer; }
|
|
|
123
123
|
.branch-row .meta { font-size: 11px; color: var(--text-3); flex: none; }
|
|
124
124
|
.branch-row.dim { color: var(--text-2); }
|
|
125
125
|
|
|
126
|
+
.pr-link {
|
|
127
|
+
font-size: 10.5px;
|
|
128
|
+
color: var(--text-3);
|
|
129
|
+
border: 1px solid var(--border);
|
|
130
|
+
border-radius: 99px;
|
|
131
|
+
padding: 0 6px;
|
|
132
|
+
line-height: 16px;
|
|
133
|
+
flex: none;
|
|
134
|
+
background: #fff;
|
|
135
|
+
}
|
|
136
|
+
.pr-link:hover { color: var(--accent); border-color: var(--accent); }
|
|
137
|
+
|
|
126
138
|
.dot { width: 7px; height: 7px; border-radius: 50%; flex: none; }
|
|
127
139
|
.dot.live { background: var(--green); }
|
|
128
140
|
.dot.idle { background: #d4d4d8; }
|
package/src/github.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
// PR links come from the gh CLI so no tokens are stored or handled: gh uses
|
|
4
|
+
// whoever is already logged in. Machines without gh (or without auth) simply
|
|
5
|
+
// return an empty map and the dashboard shows no PR links.
|
|
6
|
+
export function listOpenPRs(repoRoot) {
|
|
7
|
+
return new Promise((resolve) => {
|
|
8
|
+
execFile('gh', [
|
|
9
|
+
'pr', 'list', '--state', 'open', '--limit', '200',
|
|
10
|
+
'--json', 'number,url,headRefName,title',
|
|
11
|
+
], { cwd: repoRoot, timeout: 15_000 }, (err, stdout) => {
|
|
12
|
+
if (err) return resolve({});
|
|
13
|
+
try {
|
|
14
|
+
const byBranch = {};
|
|
15
|
+
for (const pr of JSON.parse(stdout)) {
|
|
16
|
+
byBranch[pr.headRefName] = { number: pr.number, url: pr.url, title: pr.title };
|
|
17
|
+
}
|
|
18
|
+
resolve(byBranch);
|
|
19
|
+
} catch {
|
|
20
|
+
resolve({});
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
}
|
package/src/server.js
CHANGED
|
@@ -5,6 +5,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
5
5
|
import { tryGit } from './git.js';
|
|
6
6
|
import { currentBranch } from './detect.js';
|
|
7
7
|
import { serveStatic } from './previews.js';
|
|
8
|
+
import { listOpenPRs } from './github.js';
|
|
8
9
|
|
|
9
10
|
const PUBLIC_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'public');
|
|
10
11
|
const SYNC_INTERVAL_MS = 15_000;
|
|
@@ -36,6 +37,11 @@ export class DesignShareServer {
|
|
|
36
37
|
this.port = port;
|
|
37
38
|
this.server = null;
|
|
38
39
|
this.timers = [];
|
|
40
|
+
this.prs = {};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async refreshPRs() {
|
|
44
|
+
this.prs = await listOpenPRs(this.repo.root);
|
|
39
45
|
}
|
|
40
46
|
|
|
41
47
|
async branchHeads() {
|
|
@@ -84,6 +90,7 @@ export class DesignShareServer {
|
|
|
84
90
|
otherBranches: others,
|
|
85
91
|
comments: Object.values(state.comments || {}),
|
|
86
92
|
previews: this.previews.statuses(),
|
|
93
|
+
prs: this.prs,
|
|
87
94
|
sync: {
|
|
88
95
|
hasRemote: this.store.hasRemote,
|
|
89
96
|
lastSync: this.store.lastSync,
|
|
@@ -202,6 +209,8 @@ export class DesignShareServer {
|
|
|
202
209
|
() => tryGit(this.repo.root, ['fetch', '--quiet', 'origin']),
|
|
203
210
|
FETCH_INTERVAL_MS,
|
|
204
211
|
));
|
|
212
|
+
this.refreshPRs();
|
|
213
|
+
this.timers.push(setInterval(() => this.refreshPRs(), 180_000));
|
|
205
214
|
resolve(port);
|
|
206
215
|
});
|
|
207
216
|
};
|