dsh-git-badge 0.10.1 → 0.12.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/CHANGELOG.md +32 -0
- package/lib/client.js +1 -1
- package/lib/index.js +13 -0
- package/package.json +2 -1
- package/seam/apply.js +12 -9
- package/seam/postinstall.js +8 -9
- package/seam/store.js +71 -0
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,38 @@ npm. 0.6.0 is the first release recorded here.
|
|
|
10
10
|
|
|
11
11
|
## [Unreleased]
|
|
12
12
|
|
|
13
|
+
## [0.12.0] - 2026-09-15
|
|
14
|
+
|
|
15
|
+
### Removed
|
|
16
|
+
|
|
17
|
+
- **The restart notice and one-click Restart button are gone from the input
|
|
18
|
+
chip.** The inline text — *restart dsh web to activate the sidebar badges* —
|
|
19
|
+
and its Restart button (`POST /dsh-market/api/v1/restart`) could outlive the
|
|
20
|
+
restart it asked for when the marker survived a boot, and the nag outweighed
|
|
21
|
+
the convenience. The restart-pending marker pipeline is intact server-side
|
|
22
|
+
(`seam/store.js`, `seamRestartPending` on the status response) for a future
|
|
23
|
+
marketplace-surface notification; the chip simply no longer renders it.
|
|
24
|
+
|
|
25
|
+
## [0.11.0] - 2026-09-14
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
- **A one-click restart for the change the marketplace cannot see.** The
|
|
30
|
+
postinstall/apply patches DSH's own files, which is invisible to dshmarket's
|
|
31
|
+
restart bookkeeping — a market install could complete "restart-free" while
|
|
32
|
+
the running bundles no longer matched the disk. Now: the installer writes a
|
|
33
|
+
`restart-pending` marker (`seam/store.js`); the plugin's status response
|
|
34
|
+
carries it; and while it is pending the input chip shows a one-line
|
|
35
|
+
explanation — *the installer patched DSH, client bundles are composed at
|
|
36
|
+
boot* — plus a **Restart** button calling dshmarket's PUBLIC, documented v1
|
|
37
|
+
restart endpoint (`POST /dsh-market/api/v1/restart`, feature-detected via
|
|
38
|
+
`capabilities`; hosts that delegate restart — desktop/supervised — get the
|
|
39
|
+
manual instruction instead, never a hand-rolled process-control path). The
|
|
40
|
+
button polls until the host is serving again, then reloads into the
|
|
41
|
+
recomposed boot. The marker is cleared by the node half at the next boot, so
|
|
42
|
+
it self-expires exactly when the restart it asks for has happened. Applies
|
|
43
|
+
to `revert` too — removing the patch equally needs a restart to take effect.
|
|
44
|
+
|
|
13
45
|
## [0.10.1] - 2026-09-14
|
|
14
46
|
|
|
15
47
|
### Fixed
|
package/lib/client.js
CHANGED
package/lib/index.js
CHANGED
|
@@ -69,6 +69,7 @@
|
|
|
69
69
|
import { execFile } from "node:child_process";
|
|
70
70
|
import { watch, existsSync, readFileSync } from "node:fs";
|
|
71
71
|
import { basename, isAbsolute, join, resolve } from "node:path";
|
|
72
|
+
import { clearRestartMarker, readRestartMarker } from "../seam/store.js";
|
|
72
73
|
|
|
73
74
|
const inject = ["webServer", "workspaceRegistry"];
|
|
74
75
|
const name = "dsh-git-badge";
|
|
@@ -1218,6 +1219,11 @@ async function effectiveTarget(target, bySession, wantPr, notify) {
|
|
|
1218
1219
|
|
|
1219
1220
|
/** Host plugin body — register the status route and the SSE change feed. */
|
|
1220
1221
|
function apply(ctx) {
|
|
1222
|
+
// A fresh boot composed the files on disk into the new bundles — any
|
|
1223
|
+
// restart-pending marker was written by an install/apply against the PREVIOUS
|
|
1224
|
+
// boot and its change is now live. Clearing here is what makes the marker
|
|
1225
|
+
// self-expiring: it survives exactly until the restart it asks for happens.
|
|
1226
|
+
clearRestartMarker();
|
|
1221
1227
|
ctx.effect(() => ctx.webServer.register({
|
|
1222
1228
|
kind: "exact",
|
|
1223
1229
|
path: "/api/git-badge",
|
|
@@ -1230,6 +1236,7 @@ function apply(ctx) {
|
|
|
1230
1236
|
res.end(JSON.stringify({ git: false, error: target.error }));
|
|
1231
1237
|
return;
|
|
1232
1238
|
}
|
|
1239
|
+
const pending = readRestartMarker();
|
|
1233
1240
|
const detail = url.searchParams.get("detail") === "1";
|
|
1234
1241
|
const pr = url.searchParams.get("pr") === "1";
|
|
1235
1242
|
// WHO asked decides whether a linked worktree may stand in for the
|
|
@@ -1268,6 +1275,12 @@ function apply(ctx) {
|
|
|
1268
1275
|
// tell whether an event belongs to it — without this the input chip
|
|
1269
1276
|
// only ever refreshed on remount or the 60s poll.
|
|
1270
1277
|
res.writeHead(200, { "content-type": "application/json" });
|
|
1278
|
+
if (pending !== null) {
|
|
1279
|
+
// dshmarket cannot see postinstall/apply host-file changes; this
|
|
1280
|
+
// flag is how the running UI learns a restart is pending and
|
|
1281
|
+
// offers its own Restart button (dshmarket v1 restart endpoint).
|
|
1282
|
+
body.seamRestartPending = { by: pending.by, at: pending.at };
|
|
1283
|
+
}
|
|
1271
1284
|
res.end(JSON.stringify(body));
|
|
1272
1285
|
} catch (error) {
|
|
1273
1286
|
res.writeHead(400, { "content-type": "application/json" });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-git-badge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Git status badges for DeepSeek Harness: an input-row chip on every install, plus sidebar session-row badges (git status, PR/CI action tokens) after one manual step: `npx dsh-git-badge apply`, then restart dsh web. Event-driven freshness over SSE.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
"seam/apply.js",
|
|
53
53
|
"seam/anchors.js",
|
|
54
54
|
"seam/postinstall.js",
|
|
55
|
+
"seam/store.js",
|
|
55
56
|
"seam/stub-index.js",
|
|
56
57
|
"cordis.patch.yml",
|
|
57
58
|
"package.json",
|
package/seam/apply.js
CHANGED
|
@@ -55,18 +55,16 @@ import {
|
|
|
55
55
|
seamPresent,
|
|
56
56
|
} from "./anchors.js";
|
|
57
57
|
|
|
58
|
+
import { dataDir, writeRestartMarker } from "./store.js";
|
|
59
|
+
|
|
58
60
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
59
61
|
/**
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
* refuse ("patched but no backup exists") even though the bytes were on disk
|
|
65
|
-
* somewhere. Sharing one store across npx runs, the profile bin and the repo
|
|
66
|
-
* wrapper makes the apply→revert round trip reliable for everyone. Override
|
|
67
|
-
* with SEAM_DATA_DIR (the test suite does, to stay hermetic).
|
|
62
|
+
* The stable backup store — shared with the postinstall hook and any future
|
|
63
|
+
* invocation path, so an apply→revert round trip works across npx cache
|
|
64
|
+
* entries. SEAM_DATA_DIR overrides (the test suite does, to stay hermetic).
|
|
65
|
+
* See seam/store.js for the full rationale and the restart marker.
|
|
68
66
|
*/
|
|
69
|
-
const DATA_DIR =
|
|
67
|
+
const DATA_DIR = dataDir();
|
|
70
68
|
|
|
71
69
|
// sha256 of the upstream build the anchors were last verified against. ADVISORY:
|
|
72
70
|
// status prints it for orientation; apply does NOT require it.
|
|
@@ -156,6 +154,9 @@ function doPatch() {
|
|
|
156
154
|
writeText(CLIENT, patched);
|
|
157
155
|
copyFileSync(STUB_INDEX, INDEX);
|
|
158
156
|
console.log("patched lib/client.js in place (anchors applied); host half stubbed.");
|
|
157
|
+
// dshmarket cannot see this host-file change; the plugin surfaces the
|
|
158
|
+
// restart itself (marker → status response → chip button).
|
|
159
|
+
writeRestartMarker("apply");
|
|
159
160
|
}
|
|
160
161
|
|
|
161
162
|
function writeText(path, text) {
|
|
@@ -291,6 +292,8 @@ if (verb === "apply") {
|
|
|
291
292
|
writeText(CLIENT, readText(BACKUP_CLIENT));
|
|
292
293
|
copyFileSync(BACKUP_INDEX, INDEX);
|
|
293
294
|
console.log("reverted client.js/index.js to the bytes as found before patching.");
|
|
295
|
+
// A revert also changes bytes the running bundles were composed from.
|
|
296
|
+
writeRestartMarker("revert");
|
|
294
297
|
} else {
|
|
295
298
|
// Installed file is not ours => upstream replaced it since patching.
|
|
296
299
|
console.error("REFUSING to revert: the installed client.js is not this repo's");
|
package/seam/postinstall.js
CHANGED
|
@@ -26,15 +26,10 @@ import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from
|
|
|
26
26
|
import { dirname, join } from "node:path";
|
|
27
27
|
import { fileURLToPath } from "node:url";
|
|
28
28
|
import { applyPatch, firstFailure, isOurs, seamPresent } from "./anchors.js";
|
|
29
|
+
import { dataDir, writeRestartMarker } from "./store.js";
|
|
29
30
|
|
|
30
31
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
31
32
|
const HINT = "Sidebar session-row badges need one manual step: `npx dsh-git-badge apply`, then restart dsh web.";
|
|
32
|
-
/**
|
|
33
|
-
* Same stable backup store apply.js defaults to — the backup this hook takes
|
|
34
|
-
* must be findable by a LATER `npx dsh-git-badge revert` running from a
|
|
35
|
-
* different cache entry. SEAM_DATA_DIR overrides (test suite).
|
|
36
|
-
*/
|
|
37
|
-
const DATA_DIR = process.env.SEAM_DATA_DIR || join(process.env.DSH_HOME || join(process.env.HOME || "", ".dsh"), "git-badge-seam");
|
|
38
33
|
|
|
39
34
|
function dshClientPath() {
|
|
40
35
|
try {
|
|
@@ -74,12 +69,16 @@ function main() {
|
|
|
74
69
|
return;
|
|
75
70
|
}
|
|
76
71
|
// The one state we act on: pristine upstream, anchors resolve.
|
|
77
|
-
mkdirSync(
|
|
78
|
-
copyFileSync(client, join(
|
|
79
|
-
copyFileSync(index, join(
|
|
72
|
+
mkdirSync(dataDir(), { recursive: true });
|
|
73
|
+
copyFileSync(client, join(dataDir(), "backup-client.js"));
|
|
74
|
+
copyFileSync(index, join(dataDir(), "backup-index.js"));
|
|
80
75
|
writeFileSync(client, applyPatch(text));
|
|
81
76
|
copyFileSync(join(HERE, "stub-index.js"), index);
|
|
82
77
|
console.log("[dsh-git-badge] seam applied — RESTART dsh web to get sidebar session-row badges.");
|
|
78
|
+
// Tell the running UI: dshmarket cannot see this host-file change, so the
|
|
79
|
+
// plugin offers the Restart button itself (marker → status response → chip).
|
|
80
|
+
// Read/cleared by the plugin halves — see seam/store.js.
|
|
81
|
+
writeRestartMarker("postinstall");
|
|
83
82
|
}
|
|
84
83
|
|
|
85
84
|
try {
|
package/seam/store.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The restart-pending marker — the bridge from install-time (node side) to the
|
|
3
|
+
* running UI.
|
|
4
|
+
*
|
|
5
|
+
* WHY IT EXISTS: dshmarket's pending-restart banner tracks its own package
|
|
6
|
+
* bookkeeping. A postinstall that patches the DSH HOST package
|
|
7
|
+
* (dsh-client-ui-workspace) mutates files outside that bookkeeping, so the
|
|
8
|
+
* market can complete "restart-free" while the host's composed client bundles
|
|
9
|
+
* no longer match the files on disk. The marker is the out-of-band signal:
|
|
10
|
+
*
|
|
11
|
+
* postinstall / apply / revert → write the marker (seam changed on disk)
|
|
12
|
+
* plugin node half, at boot → clear the marker (fresh boot composed the
|
|
13
|
+
* new bytes; the change is live)
|
|
14
|
+
* plugin client half → while the marker is served on the status
|
|
15
|
+
* response, offer a Restart button calling
|
|
16
|
+
* dshmarket's public v1 restart endpoint
|
|
17
|
+
*
|
|
18
|
+
* The hot-mount path is what makes this work on a FIRST market install: the
|
|
19
|
+
* freshly installed plugin's client half mounts into the running shell, reads
|
|
20
|
+
* the marker through the status response, and can restart the host — even
|
|
21
|
+
* though the process that installed the patch is gone.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
25
|
+
import { join } from "node:path";
|
|
26
|
+
|
|
27
|
+
/** The stable backup store — same default apply.js uses; SEAM_DATA_DIR overrides. */
|
|
28
|
+
export function dataDir() {
|
|
29
|
+
if (process.env.SEAM_DATA_DIR) return process.env.SEAM_DATA_DIR;
|
|
30
|
+
return join(process.env.DSH_HOME || join(process.env.HOME || "", ".dsh"), "git-badge-seam");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function markerPath() {
|
|
34
|
+
return join(dataDir(), "restart-pending.json");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Record that DSH files were changed on disk and a restart is needed to
|
|
39
|
+
* compose them. `reason` is a one-word actor (postinstall | apply | revert).
|
|
40
|
+
*/
|
|
41
|
+
export function writeRestartMarker(reason) {
|
|
42
|
+
try {
|
|
43
|
+
mkdirSync(dataDir(), { recursive: true });
|
|
44
|
+
writeFileSync(
|
|
45
|
+
markerPath(),
|
|
46
|
+
JSON.stringify({ schema: "dsh-git-badge/restart-pending/v1", by: reason, at: new Date().toISOString() }),
|
|
47
|
+
"utf8",
|
|
48
|
+
);
|
|
49
|
+
} catch {
|
|
50
|
+
// The marker is a UX nicety; never fail the operation that earned it.
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The pending marker, or null. Never throws. */
|
|
55
|
+
export function readRestartMarker() {
|
|
56
|
+
try {
|
|
57
|
+
if (!existsSync(markerPath())) return null;
|
|
58
|
+
return JSON.parse(readFileSync(markerPath(), "utf8"));
|
|
59
|
+
} catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** The change is live — a new boot composed it. Clear the pending state. */
|
|
65
|
+
export function clearRestartMarker() {
|
|
66
|
+
try {
|
|
67
|
+
rmSync(markerPath(), { force: true });
|
|
68
|
+
} catch {
|
|
69
|
+
// Absent is the goal; any error leaves the marker to be cleared later.
|
|
70
|
+
}
|
|
71
|
+
}
|