super-backlog 1.3.3 → 1.3.4
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.
|
@@ -8,6 +8,7 @@ import { startHubServer } from '../dashboard/hub.js';
|
|
|
8
8
|
import { renderDashboard } from '../dashboard/render.js';
|
|
9
9
|
import { DASHBOARD_PORT } from '../dashboard/server.js';
|
|
10
10
|
import { atomicWrite } from '../lib/atomic.js';
|
|
11
|
+
import { defaultBuildFingerprint } from '../lib/build-fingerprint.js';
|
|
11
12
|
import { clearHubState, isPidAlive, newHubToken, readHubState, writeHubState } from '../lib/hub-state.js';
|
|
12
13
|
import { projectSlug } from '../lib/slug.js';
|
|
13
14
|
import { KIT_VERSION } from '../lib/version.js';
|
|
@@ -157,6 +158,8 @@ export async function runDashboard(cwd, args, deps = {}) {
|
|
|
157
158
|
const isAlive = deps.isAlive ?? isPidAlive;
|
|
158
159
|
const killPid = deps.killPid ?? ((p) => { process.kill(p); });
|
|
159
160
|
const sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
161
|
+
const buildFingerprint = deps.buildFingerprint ?? defaultBuildFingerprint;
|
|
162
|
+
const myFingerprint = buildFingerprint();
|
|
160
163
|
let slugResult;
|
|
161
164
|
try {
|
|
162
165
|
slugResult = projectSlug(cwd);
|
|
@@ -180,7 +183,14 @@ export async function runDashboard(cwd, args, deps = {}) {
|
|
|
180
183
|
: undefined;
|
|
181
184
|
const liveVersion = typeof statusJson?.version === 'string' ? statusJson.version : undefined;
|
|
182
185
|
const effectiveVersion = liveVersion ?? state.version;
|
|
183
|
-
|
|
186
|
+
const liveFingerprint = typeof statusJson?.fingerprint === 'string' ? statusJson.fingerprint : undefined;
|
|
187
|
+
// Same version AND (proven identical build OR build cannot be
|
|
188
|
+
// assessed locally) -> attach. A missing live fingerprint from a
|
|
189
|
+
// same-version hub means it was started by an older CLI and cannot
|
|
190
|
+
// see the current build, so it is restarted exactly once.
|
|
191
|
+
const buildMatches = myFingerprint !== null && liveFingerprint === myFingerprint;
|
|
192
|
+
const buildUnassessable = myFingerprint === null;
|
|
193
|
+
if (effectiveVersion === KIT_VERSION && (buildMatches || buildUnassessable)) {
|
|
184
194
|
if (values['port'] !== undefined && port !== state.port) {
|
|
185
195
|
console.error(`error: a hub is already running on ${state.port}`);
|
|
186
196
|
return 1;
|
|
@@ -194,7 +204,12 @@ export async function runDashboard(cwd, args, deps = {}) {
|
|
|
194
204
|
noOpen,
|
|
195
205
|
});
|
|
196
206
|
}
|
|
197
|
-
|
|
207
|
+
if (effectiveVersion === KIT_VERSION) {
|
|
208
|
+
console.error(`hub build changed (fingerprint mismatch, same version v${KIT_VERSION}) — restarting it`);
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
console.error(`hub v${effectiveVersion ?? 'unknown'} does not match this CLI (v${KIT_VERSION}) — restarting it`);
|
|
212
|
+
}
|
|
198
213
|
killPid(state.pid);
|
|
199
214
|
let dead = !isAlive(state.pid);
|
|
200
215
|
for (let attempt = 0; !dead && attempt < STOP_POLL_MAX_ATTEMPTS; attempt++) {
|
|
@@ -234,7 +249,7 @@ export async function runDashboard(cwd, args, deps = {}) {
|
|
|
234
249
|
console.error(`error: dashboard serve failed (${err instanceof Error ? err.message : String(err)})`);
|
|
235
250
|
return 1;
|
|
236
251
|
}
|
|
237
|
-
writeHubState(home, { pid, port: hub.port, token, version: KIT_VERSION });
|
|
252
|
+
writeHubState(home, { pid, port: hub.port, token, version: KIT_VERSION, fingerprint: myFingerprint ?? undefined });
|
|
238
253
|
const result = hub.register({ cwd, file: outPath, regenerate });
|
|
239
254
|
if (!result.ok) {
|
|
240
255
|
await hub.close();
|
package/dist/dashboard/hub.js
CHANGED
|
@@ -9,6 +9,7 @@ import { collectDashboardData } from './data.js';
|
|
|
9
9
|
import { renderDashboard } from './render.js';
|
|
10
10
|
import { createDebouncedReloader, createReloadBroker, DASHBOARD_PORT, recursiveWatchSupported, } from './server.js';
|
|
11
11
|
import { atomicWrite } from '../lib/atomic.js';
|
|
12
|
+
import { defaultBuildFingerprint } from '../lib/build-fingerprint.js';
|
|
12
13
|
import { projectSlug, realpathKey } from '../lib/slug.js';
|
|
13
14
|
import { KIT_VERSION } from '../lib/version.js';
|
|
14
15
|
import { createModelApiHandler } from '../models/dashboard-api.js';
|
|
@@ -63,6 +64,7 @@ function generateDashboard(cwd, file) {
|
|
|
63
64
|
export async function startHubServer(opts) {
|
|
64
65
|
const projects = new Map();
|
|
65
66
|
const token = opts.token;
|
|
67
|
+
const fingerprint = defaultBuildFingerprint();
|
|
66
68
|
let port = 0;
|
|
67
69
|
let watchWarned = false;
|
|
68
70
|
function watchBacklog(cwd, reloader) {
|
|
@@ -166,7 +168,7 @@ export async function startHubServer(opts) {
|
|
|
166
168
|
sendText(res, 401, 'unauthorized');
|
|
167
169
|
return;
|
|
168
170
|
}
|
|
169
|
-
sendJson(res, 200, { pid: process.pid, port, version: KIT_VERSION });
|
|
171
|
+
sendJson(res, 200, { pid: process.pid, port, version: KIT_VERSION, fingerprint: fingerprint ?? undefined });
|
|
170
172
|
return;
|
|
171
173
|
}
|
|
172
174
|
if (pathname === '/api/hub/register' && method === 'POST') {
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// src/lib/build-fingerprint.ts
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
4
|
+
import { createRequire } from 'node:module';
|
|
5
|
+
import { dirname, join } from 'node:path';
|
|
6
|
+
const require = createRequire(import.meta.url);
|
|
7
|
+
function distRoot() {
|
|
8
|
+
// Resolves from src/lib (dev/tests) and dist/lib (runtime) alike: both sit
|
|
9
|
+
// two levels below the package root that owns dist/.
|
|
10
|
+
const pkgPath = require.resolve('../../package.json');
|
|
11
|
+
return join(dirname(pkgPath), 'dist');
|
|
12
|
+
}
|
|
13
|
+
function walk(dir, out) {
|
|
14
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
15
|
+
const full = join(dir, entry.name);
|
|
16
|
+
if (entry.isDirectory())
|
|
17
|
+
walk(full, out);
|
|
18
|
+
else
|
|
19
|
+
out.push(full);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Stable content hash over the built CLI. Two builds of the same source
|
|
24
|
+
* produce the same fingerprint; any changed or added file changes it.
|
|
25
|
+
* Returns null when the build directory is missing.
|
|
26
|
+
*/
|
|
27
|
+
export function computeBuildFingerprint(root = distRoot()) {
|
|
28
|
+
if (!existsSync(root))
|
|
29
|
+
return null;
|
|
30
|
+
const files = [];
|
|
31
|
+
walk(root, files);
|
|
32
|
+
if (files.length === 0)
|
|
33
|
+
return null;
|
|
34
|
+
files.sort();
|
|
35
|
+
const hash = createHash('sha256');
|
|
36
|
+
for (const file of files) {
|
|
37
|
+
hash.update(file.slice(root.length + 1).replaceAll('\\', '/'));
|
|
38
|
+
hash.update('\0');
|
|
39
|
+
hash.update(readFileSync(file));
|
|
40
|
+
hash.update('\0');
|
|
41
|
+
}
|
|
42
|
+
return hash.digest('hex').slice(0, 16);
|
|
43
|
+
}
|
|
44
|
+
let cachedDefault;
|
|
45
|
+
/** Process-lifetime cached fingerprint of the running build. */
|
|
46
|
+
export function defaultBuildFingerprint() {
|
|
47
|
+
if (cachedDefault === undefined)
|
|
48
|
+
cachedDefault = computeBuildFingerprint();
|
|
49
|
+
return cachedDefault;
|
|
50
|
+
}
|
package/dist/lib/hub-state.js
CHANGED
|
@@ -16,8 +16,14 @@ export function readHubState(home) {
|
|
|
16
16
|
typeof parsed.token !== 'string') {
|
|
17
17
|
return null;
|
|
18
18
|
}
|
|
19
|
-
const { pid, port, token, version } = parsed;
|
|
20
|
-
return
|
|
19
|
+
const { pid, port, token, version, fingerprint } = parsed;
|
|
20
|
+
return {
|
|
21
|
+
pid,
|
|
22
|
+
port,
|
|
23
|
+
token,
|
|
24
|
+
...(typeof version === 'string' ? { version } : {}),
|
|
25
|
+
...(typeof fingerprint === 'string' ? { fingerprint } : {}),
|
|
26
|
+
};
|
|
21
27
|
}
|
|
22
28
|
catch {
|
|
23
29
|
return null;
|