dsh-plugin-upgrader 1.0.2 → 1.1.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 +124 -0
- package/README.en.md +29 -3
- package/README.md +24 -3
- package/lib/client.js +171 -25
- package/lib/host/install.d.ts +11 -1
- package/lib/host/install.d.ts.map +1 -1
- package/lib/host/install.js +189 -6
- package/lib/host/install.js.map +1 -1
- package/lib/host/manager.d.ts +35 -0
- package/lib/host/manager.d.ts.map +1 -1
- package/lib/host/manager.js.map +1 -1
- package/lib/host/overview.d.ts.map +1 -1
- package/lib/host/overview.js +62 -5
- package/lib/host/overview.js.map +1 -1
- package/lib/host/progress.d.ts +28 -0
- package/lib/host/progress.d.ts.map +1 -0
- package/lib/host/progress.js +21 -0
- package/lib/host/progress.js.map +1 -0
- package/lib/host/registry.d.ts +48 -1
- package/lib/host/registry.d.ts.map +1 -1
- package/lib/host/registry.js +124 -22
- package/lib/host/registry.js.map +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +174 -30
- package/lib/index.js.map +1 -1
- package/package.json +3 -2
package/lib/host/registry.d.ts
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
import { type PackumentLike, type RepoManifest } from './repo.ts';
|
|
2
2
|
/** How long one registry packument stays fresh. */
|
|
3
3
|
export declare const PACKUMENT_TTL_MS: number;
|
|
4
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* Registry lookups must not hang a page render.
|
|
6
|
+
*
|
|
7
|
+
* Raised from 10 s once the documents were measured: the largest package in a
|
|
8
|
+
* normal profile (`billion-context`, 6 624 versions) is a 26 MB JSON document
|
|
9
|
+
* and 10 MB even abbreviated, which takes a second on a fast mirror and much
|
|
10
|
+
* longer on a busy one. A deadline that the healthy case can miss is not a
|
|
11
|
+
* deadline, it is a coin flip — and its losing side was being drawn as "this
|
|
12
|
+
* plugin has no version history".
|
|
13
|
+
*/
|
|
5
14
|
export declare const PACKUMENT_TIMEOUT_MS: number;
|
|
6
15
|
/** Version lists are capped so one huge package cannot flood the tab. */
|
|
7
16
|
export declare const MAX_VERSIONS = 80;
|
|
@@ -36,6 +45,8 @@ export interface FullPackument extends PackumentLike {
|
|
|
36
45
|
export interface PackumentEntry {
|
|
37
46
|
at: number;
|
|
38
47
|
data: unknown;
|
|
48
|
+
/** Set when the last attempt failed, so a later render can say why. */
|
|
49
|
+
error?: string;
|
|
39
50
|
}
|
|
40
51
|
/** One cached tag lookup. */
|
|
41
52
|
export interface TagEntry {
|
|
@@ -43,6 +54,18 @@ export interface TagEntry {
|
|
|
43
54
|
tags?: Set<string>;
|
|
44
55
|
error?: string;
|
|
45
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* How long a failed packument lookup is remembered.
|
|
59
|
+
*
|
|
60
|
+
* A failure has to be remembered at all: the tab rebuilds the overview on every
|
|
61
|
+
* mount, and retrying a mirror that just timed out on every mount turns one
|
|
62
|
+
* slow render into a permanently slow page. It is kept much shorter than a
|
|
63
|
+
* successful entry, though — the failure is usually contention, not a broken
|
|
64
|
+
* package, so the next open should get a fresh chance.
|
|
65
|
+
*/
|
|
66
|
+
export declare const PACKUMENT_RETRY_MS: number;
|
|
67
|
+
/** How many package lookups may be in flight at once. */
|
|
68
|
+
export declare const PACKUMENT_CONCURRENCY = 4;
|
|
46
69
|
/** One version line the tab renders. */
|
|
47
70
|
export interface VersionRow {
|
|
48
71
|
version: string;
|
|
@@ -62,10 +85,25 @@ export interface ShapedPackument {
|
|
|
62
85
|
tags: Record<string, string>;
|
|
63
86
|
totalVersions?: number;
|
|
64
87
|
versionError?: string;
|
|
88
|
+
/** The `dist-tags.latest` that was rejected as broken, when there was one. */
|
|
89
|
+
staleTag?: string;
|
|
65
90
|
}
|
|
66
91
|
/**
|
|
67
92
|
* Fetch one package's packument, with a short-lived cache.
|
|
68
93
|
*
|
|
94
|
+
* Two failure modes were measured on the npmmirror mirror, and both produced
|
|
95
|
+
* the same symptom — a plugin drawn with no version history at all:
|
|
96
|
+
*
|
|
97
|
+
* - the tab asks for every installed package at once, and eleven concurrent
|
|
98
|
+
* connections to the mirror time out at a 10 s deadline while the same
|
|
99
|
+
* eleven asked one at a time answer in under 1.2 s;
|
|
100
|
+
* - the largest document in a normal profile is 26 MB, so even a healthy
|
|
101
|
+
* request is slow enough to lose that race.
|
|
102
|
+
*
|
|
103
|
+
* The abbreviated document cuts the transfer, the deadline was raised, and a
|
|
104
|
+
* timeout is retried once — a `UND_ERR_CONNECT_TIMEOUT` is contention on the
|
|
105
|
+
* socket, not a verdict about the package.
|
|
106
|
+
*
|
|
69
107
|
* @param base - registry base URL.
|
|
70
108
|
* @param packageName - package name.
|
|
71
109
|
* @param cache - shared cache.
|
|
@@ -107,6 +145,15 @@ export declare function cachedTags(repoUrl: string, cache: Map<string, TagEntry>
|
|
|
107
145
|
/**
|
|
108
146
|
* Trim one packument down to what the tab renders.
|
|
109
147
|
*
|
|
148
|
+
* `dist-tags.latest` is normally the newest stable release, and it is trusted as
|
|
149
|
+
* is — a prerelease above it must NOT become "latest", or every `-next` build
|
|
150
|
+
* would advertise itself over the stable line. The one case it cannot be
|
|
151
|
+
* trusted is a tag that sorts *below the version already installed*: that is not
|
|
152
|
+
* a release decision, it is a broken tag. npmmirror serves
|
|
153
|
+
* `@deepseek-ai/dsh-subagent-codex` with `latest: 0.0.1-rc.1` while the profile
|
|
154
|
+
* runs `0.2.1-rc.3`, which made the row claim the installed build was newer than
|
|
155
|
+
* latest and offered an "upgrade" that was really a downgrade.
|
|
156
|
+
*
|
|
110
157
|
* @param packument - the full registry document.
|
|
111
158
|
* @param installedVersion - the version in the profile.
|
|
112
159
|
* @param tagNames - the repository's tag names, when known.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/host/registry.ts"],"names":[],"mappings":"AAUA,OAAO,EAAe,KAAK,aAAa,EAAE,KAAK,YAAY,EAAE,MAAM,WAAW,CAAA;AAE9E,mDAAmD;AACnD,eAAO,MAAM,gBAAgB,QAAgB,CAAA;AAE7C
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/host/registry.ts"],"names":[],"mappings":"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"}
|
package/lib/host/registry.js
CHANGED
|
@@ -11,8 +11,25 @@ import { compareVersions, releasePathOf } from './version.js';
|
|
|
11
11
|
import { githubUrlOf } from './repo.js';
|
|
12
12
|
/** How long one registry packument stays fresh. */
|
|
13
13
|
export const PACKUMENT_TTL_MS = 5 * 60 * 1000;
|
|
14
|
-
/**
|
|
15
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Registry lookups must not hang a page render.
|
|
16
|
+
*
|
|
17
|
+
* Raised from 10 s once the documents were measured: the largest package in a
|
|
18
|
+
* normal profile (`billion-context`, 6 624 versions) is a 26 MB JSON document
|
|
19
|
+
* and 10 MB even abbreviated, which takes a second on a fast mirror and much
|
|
20
|
+
* longer on a busy one. A deadline that the healthy case can miss is not a
|
|
21
|
+
* deadline, it is a coin flip — and its losing side was being drawn as "this
|
|
22
|
+
* plugin has no version history".
|
|
23
|
+
*/
|
|
24
|
+
export const PACKUMENT_TIMEOUT_MS = 20 * 1000;
|
|
25
|
+
/**
|
|
26
|
+
* The npm registry's abbreviated document, which is what pnpm itself downloads.
|
|
27
|
+
*
|
|
28
|
+
* It drops the readme, the per-version tarball metadata and the maintainer
|
|
29
|
+
* lists — 26 MB becomes 10 MB for `billion-context` — while keeping the three
|
|
30
|
+
* fields the version list renders: `time`, `engines` and `deprecated`.
|
|
31
|
+
*/
|
|
32
|
+
const ABBREVIATED_ACCEPT = 'application/vnd.npm.install-v1+json';
|
|
16
33
|
/** Version lists are capped so one huge package cannot flood the tab. */
|
|
17
34
|
export const MAX_VERSIONS = 80;
|
|
18
35
|
/**
|
|
@@ -25,9 +42,34 @@ export const TAGS_TTL_MS = 30 * 60 * 1000;
|
|
|
25
42
|
export const TAGS_RETRY_MS = 5 * 60 * 1000;
|
|
26
43
|
/** Tag lookups must not hang a page render. */
|
|
27
44
|
export const TAGS_TIMEOUT_MS = 8 * 1000;
|
|
45
|
+
/**
|
|
46
|
+
* How long a failed packument lookup is remembered.
|
|
47
|
+
*
|
|
48
|
+
* A failure has to be remembered at all: the tab rebuilds the overview on every
|
|
49
|
+
* mount, and retrying a mirror that just timed out on every mount turns one
|
|
50
|
+
* slow render into a permanently slow page. It is kept much shorter than a
|
|
51
|
+
* successful entry, though — the failure is usually contention, not a broken
|
|
52
|
+
* package, so the next open should get a fresh chance.
|
|
53
|
+
*/
|
|
54
|
+
export const PACKUMENT_RETRY_MS = 45 * 1000;
|
|
55
|
+
/** How many package lookups may be in flight at once. */
|
|
56
|
+
export const PACKUMENT_CONCURRENCY = 4;
|
|
28
57
|
/**
|
|
29
58
|
* Fetch one package's packument, with a short-lived cache.
|
|
30
59
|
*
|
|
60
|
+
* Two failure modes were measured on the npmmirror mirror, and both produced
|
|
61
|
+
* the same symptom — a plugin drawn with no version history at all:
|
|
62
|
+
*
|
|
63
|
+
* - the tab asks for every installed package at once, and eleven concurrent
|
|
64
|
+
* connections to the mirror time out at a 10 s deadline while the same
|
|
65
|
+
* eleven asked one at a time answer in under 1.2 s;
|
|
66
|
+
* - the largest document in a normal profile is 26 MB, so even a healthy
|
|
67
|
+
* request is slow enough to lose that race.
|
|
68
|
+
*
|
|
69
|
+
* The abbreviated document cuts the transfer, the deadline was raised, and a
|
|
70
|
+
* timeout is retried once — a `UND_ERR_CONNECT_TIMEOUT` is contention on the
|
|
71
|
+
* socket, not a verdict about the package.
|
|
72
|
+
*
|
|
31
73
|
* @param base - registry base URL.
|
|
32
74
|
* @param packageName - package name.
|
|
33
75
|
* @param cache - shared cache.
|
|
@@ -37,26 +79,62 @@ export const TAGS_TIMEOUT_MS = 8 * 1000;
|
|
|
37
79
|
export async function fetchPackument(base, packageName, cache, force) {
|
|
38
80
|
const key = `${base}|${packageName}`;
|
|
39
81
|
const hit = cache.get(key);
|
|
40
|
-
if (force !== true && hit !== undefined
|
|
41
|
-
|
|
82
|
+
if (force !== true && hit !== undefined) {
|
|
83
|
+
// A remembered failure is served only within its own, shorter window.
|
|
84
|
+
if (hit.error === undefined && Date.now() - hit.at < PACKUMENT_TTL_MS)
|
|
85
|
+
return { data: hit.data };
|
|
86
|
+
if (hit.error !== undefined && Date.now() - hit.at < PACKUMENT_RETRY_MS)
|
|
87
|
+
return { error: hit.error };
|
|
42
88
|
}
|
|
43
89
|
const url = `${base}/${packageName.startsWith('@') ? '@' + encodeURIComponent(packageName.slice(1)) : encodeURIComponent(packageName)}`;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
90
|
+
let lastError = 'registry unreachable';
|
|
91
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
92
|
+
try {
|
|
93
|
+
const response = await fetch(url, {
|
|
94
|
+
headers: { accept: ABBREVIATED_ACCEPT },
|
|
95
|
+
signal: AbortSignal.timeout(PACKUMENT_TIMEOUT_MS),
|
|
96
|
+
});
|
|
97
|
+
if (!response.ok) {
|
|
98
|
+
// A missing package is a verdict, not contention: do not retry it.
|
|
99
|
+
const error = `registry ${response.status}${response.status === 404 ? ' (not found)' : ''}`;
|
|
100
|
+
cache.set(key, { at: Date.now(), data: undefined, error });
|
|
101
|
+
return { error };
|
|
102
|
+
}
|
|
103
|
+
const data = await response.json();
|
|
104
|
+
cache.set(key, { at: Date.now(), data });
|
|
105
|
+
return { data };
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
lastError = `registry unreachable: ${describeFetchError(error)}`;
|
|
51
109
|
}
|
|
52
|
-
const data = await response.json();
|
|
53
|
-
cache.set(key, { at: Date.now(), data });
|
|
54
|
-
return { data };
|
|
55
110
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
111
|
+
cache.set(key, { at: Date.now(), data: undefined, error: lastError });
|
|
112
|
+
return { error: lastError };
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Say what actually failed, not just that `fetch` did.
|
|
116
|
+
*
|
|
117
|
+
* Node reports every transport fault as `TypeError: fetch failed` and hangs the
|
|
118
|
+
* reason off `cause`; without unwrapping it, a DNS rejection, a refused
|
|
119
|
+
* connection and a socket timeout all read the same in the tab, which is the
|
|
120
|
+
* difference between "try again" and "this registry is wrong".
|
|
121
|
+
*
|
|
122
|
+
* @param error - whatever `fetch` threw.
|
|
123
|
+
* @returns a sentence with the underlying cause and code.
|
|
124
|
+
*/
|
|
125
|
+
function describeFetchError(error) {
|
|
126
|
+
if (!(error instanceof Error))
|
|
127
|
+
return String(error);
|
|
128
|
+
const cause = error.cause;
|
|
129
|
+
if (cause === undefined || cause === null) {
|
|
130
|
+
return error.name === 'Error' ? error.message : `${error.name}: ${error.message}`;
|
|
59
131
|
}
|
|
132
|
+
const record = cause;
|
|
133
|
+
const code = typeof record.code === 'string' ? record.code : undefined;
|
|
134
|
+
const message = typeof record.message === 'string' ? record.message : String(cause);
|
|
135
|
+
if (code !== undefined)
|
|
136
|
+
return `${code}: ${message}`;
|
|
137
|
+
return message === '' ? error.message : message;
|
|
60
138
|
}
|
|
61
139
|
/**
|
|
62
140
|
* Read a repository's tag names from the GitHub API.
|
|
@@ -127,6 +205,15 @@ export async function cachedTags(repoUrl, cache, force) {
|
|
|
127
205
|
/**
|
|
128
206
|
* Trim one packument down to what the tab renders.
|
|
129
207
|
*
|
|
208
|
+
* `dist-tags.latest` is normally the newest stable release, and it is trusted as
|
|
209
|
+
* is — a prerelease above it must NOT become "latest", or every `-next` build
|
|
210
|
+
* would advertise itself over the stable line. The one case it cannot be
|
|
211
|
+
* trusted is a tag that sorts *below the version already installed*: that is not
|
|
212
|
+
* a release decision, it is a broken tag. npmmirror serves
|
|
213
|
+
* `@deepseek-ai/dsh-subagent-codex` with `latest: 0.0.1-rc.1` while the profile
|
|
214
|
+
* runs `0.2.1-rc.3`, which made the row claim the installed build was newer than
|
|
215
|
+
* latest and offered an "upgrade" that was really a downgrade.
|
|
216
|
+
*
|
|
130
217
|
* @param packument - the full registry document.
|
|
131
218
|
* @param installedVersion - the version in the profile.
|
|
132
219
|
* @param tagNames - the repository's tag names, when known.
|
|
@@ -141,13 +228,13 @@ export function shapePackument(packument, installedVersion, tagNames) {
|
|
|
141
228
|
const tags = rawTags !== null && rawTags !== undefined && typeof rawTags === 'object'
|
|
142
229
|
? rawTags
|
|
143
230
|
: {};
|
|
144
|
-
const
|
|
231
|
+
const tagged = typeof tags.latest === 'string' ? tags.latest : undefined;
|
|
145
232
|
const timeRecord = packument?.time !== null && packument?.time !== undefined && typeof packument.time === 'object'
|
|
146
233
|
? packument.time
|
|
147
234
|
: {};
|
|
148
235
|
const names = Object.keys(versionsRecord);
|
|
149
|
-
const
|
|
150
|
-
const repoUrl = githubUrlOf(packument,
|
|
236
|
+
const taggedManifest = tagged === undefined ? undefined : versionsRecord[tagged];
|
|
237
|
+
const repoUrl = githubUrlOf(packument, taggedManifest);
|
|
151
238
|
const rows = names.map((version) => {
|
|
152
239
|
const manifest = versionsRecord[version] ?? {};
|
|
153
240
|
const engine = manifest?.engines?.dsh ?? manifest?.dsh?.engines?.dsh;
|
|
@@ -157,21 +244,36 @@ export function shapePackument(packument, installedVersion, tagNames) {
|
|
|
157
244
|
deprecated: typeof manifest.deprecated === 'string' ? manifest.deprecated : undefined,
|
|
158
245
|
engine: typeof engine === 'string' ? engine : undefined,
|
|
159
246
|
current: version === installedVersion,
|
|
160
|
-
latest:
|
|
247
|
+
latest: false,
|
|
161
248
|
url: repoUrl === undefined ? undefined : releasePathOf(tagNames, version),
|
|
162
249
|
};
|
|
163
250
|
});
|
|
164
251
|
rows.sort((left, right) => compareVersions(right.version, left.version));
|
|
252
|
+
/** A tag below what is already installed is broken, not a release channel. */
|
|
253
|
+
const broken = tagged !== undefined &&
|
|
254
|
+
installedVersion !== undefined &&
|
|
255
|
+
compareVersions(tagged, installedVersion) < 0;
|
|
256
|
+
let newest = tagged;
|
|
257
|
+
if (broken) {
|
|
258
|
+
// Recover the real head of the line: the highest version above the
|
|
259
|
+
// installed one, preferring a stable release over a prerelease.
|
|
260
|
+
const above = rows.filter((row) => compareVersions(row.version, installedVersion) > 0);
|
|
261
|
+
const stable = above.find((row) => !row.version.includes('-'));
|
|
262
|
+
newest = (stable ?? above[0])?.version ?? tagged;
|
|
263
|
+
}
|
|
264
|
+
for (const row of rows)
|
|
265
|
+
row.latest = row.version === newest;
|
|
165
266
|
const kept = rows.slice(0, MAX_VERSIONS);
|
|
166
267
|
const currentRow = rows.find((row) => row.current);
|
|
167
268
|
if (currentRow !== undefined && !kept.includes(currentRow))
|
|
168
269
|
kept.push(currentRow);
|
|
169
270
|
return {
|
|
170
|
-
latest:
|
|
271
|
+
latest: newest,
|
|
171
272
|
repoUrl,
|
|
172
273
|
versions: kept,
|
|
173
274
|
tags: Object.fromEntries(Object.entries(tags).filter((entry) => typeof entry[1] === 'string')),
|
|
174
275
|
totalVersions: rows.length,
|
|
276
|
+
...(broken ? { staleTag: tagged } : {}),
|
|
175
277
|
};
|
|
176
278
|
}
|
|
177
279
|
//# sourceMappingURL=registry.js.map
|
package/lib/host/registry.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/host/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAC7D,OAAO,EAAE,WAAW,EAAyC,MAAM,WAAW,CAAA;AAE9E,mDAAmD;AACnD,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAA;AAE7C
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/host/registry.ts"],"names":[],"mappings":"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"}
|
package/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA4BA,OAAO,EAEL,KAAK,WAAW,EAMjB,MAAM,mBAAmB,CAAA;AAE1B,kEAAkE;AAClE,eAAO,MAAM,IAAI,oBAAoB,CAAA;AAErC,gFAAgF;AAChF,eAAO,MAAM,MAAM,UAAgB,CAAA;AA8EnC;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,WAAW,GAAG,IAAI,CA6X5C"}
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { writeJson, isLoopbackRequest, readJsonBody } from './host/http.js';
|
|
2
2
|
import { buildOverview } from './host/overview.js';
|
|
3
3
|
import { runStep, validateStep } from './host/install.js';
|
|
4
|
+
import { nextPhase } from './host/progress.js';
|
|
4
5
|
import { asPluginManager, } from './host/manager.js';
|
|
5
6
|
/** Stable plugin name; matches the cordis.patch.yml insert id. */
|
|
6
7
|
export const name = 'plugin-upgrader';
|
|
@@ -18,14 +19,21 @@ const API_BASE = `${API_PREFIX}/`;
|
|
|
18
19
|
const JOB_RETENTION_MS = 10 * 60 * 1000;
|
|
19
20
|
/**
|
|
20
21
|
* A job that stops reporting progress for this long is treated as abandoned
|
|
21
|
-
* and no longer blocks new work.
|
|
22
|
-
*
|
|
22
|
+
* and no longer blocks new work.
|
|
23
|
+
*
|
|
24
|
+
* The heartbeat is a timer, not a per-step write: a single installation can
|
|
25
|
+
* legitimately run for minutes while pnpm verifies a lockfile, and that work is
|
|
26
|
+
* not visible as a finished step.
|
|
23
27
|
*/
|
|
24
28
|
const STALE_JOB_MS = 15 * 60 * 1000;
|
|
29
|
+
/** How often a running job re-stamps its heartbeat, independently of steps. */
|
|
30
|
+
const HEARTBEAT_MS = 2 * 1000;
|
|
25
31
|
/** How many log lines one job keeps for the panel. */
|
|
26
|
-
const JOB_LOG_LIMIT =
|
|
32
|
+
const JOB_LOG_LIMIT = 400;
|
|
33
|
+
/** How many characters of pnpm output one job keeps across all its steps. */
|
|
34
|
+
const JOB_LOG_CHARS = 24 * 1024;
|
|
27
35
|
/** How many log lines the overview ships for a job the tab adopts. */
|
|
28
|
-
const ADOPTED_LOG_LIMIT =
|
|
36
|
+
const ADOPTED_LOG_LIMIT = 60;
|
|
29
37
|
/**
|
|
30
38
|
* Mount the host half.
|
|
31
39
|
*
|
|
@@ -36,6 +44,8 @@ export function apply(ctx) {
|
|
|
36
44
|
const tagCache = new Map();
|
|
37
45
|
const jobs = new Map();
|
|
38
46
|
let inFlight = null;
|
|
47
|
+
/** The job a manager event belongs to, while one is running. */
|
|
48
|
+
let liveJob = null;
|
|
39
49
|
const pruneJobs = () => {
|
|
40
50
|
const cutoff = Date.now() - JOB_RETENTION_MS;
|
|
41
51
|
for (const [id, job] of jobs) {
|
|
@@ -43,11 +53,39 @@ export function apply(ctx) {
|
|
|
43
53
|
jobs.delete(id);
|
|
44
54
|
}
|
|
45
55
|
};
|
|
56
|
+
/**
|
|
57
|
+
* Append one line, dropping the oldest once the job holds too much text.
|
|
58
|
+
*
|
|
59
|
+
* @param job - the job to append to.
|
|
60
|
+
* @param message - the text of the line.
|
|
61
|
+
*/
|
|
62
|
+
const appendLog = (job, message) => {
|
|
63
|
+
const text = message.trimEnd();
|
|
64
|
+
if (text === '')
|
|
65
|
+
return;
|
|
66
|
+
for (const line of text.split(/\r?\n/)) {
|
|
67
|
+
if (line.trim() === '')
|
|
68
|
+
continue;
|
|
69
|
+
job.log.push(line);
|
|
70
|
+
job.logChars += line.length;
|
|
71
|
+
}
|
|
72
|
+
while (job.logChars > JOB_LOG_CHARS && job.log.length > 1) {
|
|
73
|
+
job.logChars -= job.log.shift()?.length ?? 0;
|
|
74
|
+
}
|
|
75
|
+
while (job.log.length > JOB_LOG_LIMIT) {
|
|
76
|
+
job.logChars -= job.log.shift()?.length ?? 0;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
46
79
|
/** The manager, or a reason the request cannot be served. */
|
|
47
80
|
const requireManager = () => asPluginManager(ctx.get('pluginManager', false));
|
|
48
81
|
/**
|
|
49
82
|
* Run a batch job to completion in the background.
|
|
50
83
|
*
|
|
84
|
+
* The heartbeat is a timer, not a per-step write. One upgrade can spend
|
|
85
|
+
* minutes inside a single pnpm run — verifying a lockfile, retrying a
|
|
86
|
+
* registry — and none of that finishes a step, so a step-driven heartbeat
|
|
87
|
+
* would declare a healthy job abandoned and let a second batch start.
|
|
88
|
+
*
|
|
51
89
|
* @param job - the mutable job record.
|
|
52
90
|
* @param steps - the requested steps.
|
|
53
91
|
*/
|
|
@@ -56,34 +94,49 @@ export function apply(ctx) {
|
|
|
56
94
|
if (manager === undefined) {
|
|
57
95
|
job.status = 'finished';
|
|
58
96
|
job.finishedAt = Date.now();
|
|
59
|
-
job
|
|
97
|
+
appendLog(job, 'job failed: plugin manager unavailable');
|
|
60
98
|
return;
|
|
61
99
|
}
|
|
62
100
|
job.status = 'running';
|
|
63
101
|
job.heartbeatAt = Date.now();
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
break;
|
|
67
|
-
inFlight = { requestId: step.requestId ?? '' };
|
|
68
|
-
job.current = `${step.name}@${step.version}`;
|
|
69
|
-
job.log.push(`→ ${job.current}`);
|
|
70
|
-
const outcome = await runStep(manager, step, (message) => {
|
|
71
|
-
job.log.push(message);
|
|
72
|
-
if (job.log.length > JOB_LOG_LIMIT)
|
|
73
|
-
job.log.splice(0, job.log.length - JOB_LOG_LIMIT);
|
|
74
|
-
});
|
|
75
|
-
inFlight = null;
|
|
102
|
+
liveJob = job;
|
|
103
|
+
const beat = setInterval(() => {
|
|
76
104
|
job.heartbeatAt = Date.now();
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
job.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
105
|
+
}, HEARTBEAT_MS);
|
|
106
|
+
try {
|
|
107
|
+
for (const step of steps) {
|
|
108
|
+
if (job.cancelled)
|
|
109
|
+
break;
|
|
110
|
+
job.current = `${step.name}@${step.version}`;
|
|
111
|
+
job.stepStartedAt = Date.now();
|
|
112
|
+
job.phase = 'waiting';
|
|
113
|
+
job.attempt = undefined;
|
|
114
|
+
appendLog(job, `→ ${job.current}`);
|
|
115
|
+
const outcome = await runStep(manager, step, (message) => appendLog(job, message), (requestId) => {
|
|
116
|
+
job.requestIds.push(requestId);
|
|
117
|
+
inFlight = { requestId };
|
|
118
|
+
});
|
|
119
|
+
inFlight = null;
|
|
120
|
+
job.heartbeatAt = Date.now();
|
|
121
|
+
job.results.push(outcome);
|
|
122
|
+
job.done = job.results.length;
|
|
123
|
+
if (!outcome.ok)
|
|
124
|
+
job.failed += 1;
|
|
125
|
+
appendLog(job, outcome.ok
|
|
126
|
+
? `✓ ${job.current} ${outcome.application ?? 'applied'}`
|
|
127
|
+
: `✗ ${job.current} ${outcome.message ?? 'failed'}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
finally {
|
|
131
|
+
clearInterval(beat);
|
|
132
|
+
liveJob = null;
|
|
133
|
+
inFlight = null;
|
|
84
134
|
}
|
|
85
135
|
job.status = job.cancelled ? 'cancelled' : 'finished';
|
|
86
136
|
job.current = undefined;
|
|
137
|
+
job.stepStartedAt = undefined;
|
|
138
|
+
job.phase = undefined;
|
|
139
|
+
job.attempt = undefined;
|
|
87
140
|
job.finishedAt = Date.now();
|
|
88
141
|
job.cacheDirty = true;
|
|
89
142
|
};
|
|
@@ -103,7 +156,7 @@ export function apply(ctx) {
|
|
|
103
156
|
job.abandoned = true;
|
|
104
157
|
job.current = undefined;
|
|
105
158
|
job.finishedAt = Date.now();
|
|
106
|
-
job
|
|
159
|
+
appendLog(job, '! job abandoned: no progress for 15 minutes');
|
|
107
160
|
}
|
|
108
161
|
return undefined;
|
|
109
162
|
};
|
|
@@ -147,6 +200,9 @@ export function apply(ctx) {
|
|
|
147
200
|
failed: live.failed,
|
|
148
201
|
current: live.current,
|
|
149
202
|
startedAt: live.startedAt,
|
|
203
|
+
stepStartedAt: live.stepStartedAt,
|
|
204
|
+
phase: live.phase,
|
|
205
|
+
attempt: live.attempt,
|
|
150
206
|
log: live.log.slice(-ADOPTED_LOG_LIMIT),
|
|
151
207
|
},
|
|
152
208
|
jobs: [...jobs.entries()]
|
|
@@ -198,14 +254,16 @@ export function apply(ctx) {
|
|
|
198
254
|
failed: 0,
|
|
199
255
|
results: [],
|
|
200
256
|
log: [],
|
|
257
|
+
logChars: 0,
|
|
201
258
|
cancelled: false,
|
|
202
259
|
startedAt: Date.now(),
|
|
260
|
+
requestIds: [],
|
|
203
261
|
};
|
|
204
262
|
jobs.set(id, job);
|
|
205
263
|
void runJob(job, steps).catch((error) => {
|
|
206
264
|
job.status = 'finished';
|
|
207
265
|
job.finishedAt = Date.now();
|
|
208
|
-
job
|
|
266
|
+
appendLog(job, `job failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
209
267
|
});
|
|
210
268
|
writeJson(res, 202, { id });
|
|
211
269
|
return;
|
|
@@ -217,7 +275,22 @@ export function apply(ctx) {
|
|
|
217
275
|
writeJson(res, 404, { error: 'unknown-job' });
|
|
218
276
|
return;
|
|
219
277
|
}
|
|
220
|
-
writeJson(res, 200,
|
|
278
|
+
writeJson(res, 200, {
|
|
279
|
+
id: job.id,
|
|
280
|
+
status: job.status,
|
|
281
|
+
total: job.total,
|
|
282
|
+
done: job.done,
|
|
283
|
+
failed: job.failed,
|
|
284
|
+
current: job.current,
|
|
285
|
+
phase: job.phase,
|
|
286
|
+
attempt: job.attempt,
|
|
287
|
+
startedAt: job.startedAt,
|
|
288
|
+
stepStartedAt: job.stepStartedAt,
|
|
289
|
+
finishedAt: job.finishedAt,
|
|
290
|
+
abandoned: job.abandoned,
|
|
291
|
+
log: job.log,
|
|
292
|
+
results: job.results,
|
|
293
|
+
});
|
|
221
294
|
return;
|
|
222
295
|
}
|
|
223
296
|
if (req.method === 'POST' && route === 'cancel') {
|
|
@@ -227,11 +300,17 @@ export function apply(ctx) {
|
|
|
227
300
|
return;
|
|
228
301
|
}
|
|
229
302
|
running.cancelled = true;
|
|
303
|
+
appendLog(running, '! cancel requested');
|
|
230
304
|
const manager = requireManager();
|
|
231
|
-
const target = inFlight;
|
|
232
|
-
if (manager !== undefined && target !==
|
|
305
|
+
const target = inFlight?.requestId ?? running.requestIds.at(-1);
|
|
306
|
+
if (manager !== undefined && target !== undefined && target !== '') {
|
|
233
307
|
try {
|
|
234
|
-
await manager.cancelInstall(target
|
|
308
|
+
const outcome = await manager.cancelInstall(target);
|
|
309
|
+
appendLog(running, outcome.status === 'not-running'
|
|
310
|
+
? '! the installation already settled; the batch stops after it'
|
|
311
|
+
: outcome.status === 'too-late'
|
|
312
|
+
? '! past the point of cancellation; finishing the current step'
|
|
313
|
+
: 'stopping the running installation…');
|
|
235
314
|
}
|
|
236
315
|
catch {
|
|
237
316
|
/* the step settles on its own; the flag above already stops the batch */
|
|
@@ -249,6 +328,71 @@ export function apply(ctx) {
|
|
|
249
328
|
writeJson(res, status, { error: error instanceof Error ? error.message : String(error) });
|
|
250
329
|
}
|
|
251
330
|
};
|
|
331
|
+
/**
|
|
332
|
+
* Mirror the manager's running pnpm output and phases into the job log.
|
|
333
|
+
*
|
|
334
|
+
* Without this the panel has nothing to show between a step starting and
|
|
335
|
+
* finishing: a lockfile verification or a registry retry can occupy minutes.
|
|
336
|
+
* Chunks from a removal carry no request id (the manager only tags the runs
|
|
337
|
+
* its caller identified), so a chunk with no id is attributed to whatever job
|
|
338
|
+
* is live — there is only ever one.
|
|
339
|
+
*
|
|
340
|
+
* The listeners are optional: a host whose manager does not emit these
|
|
341
|
+
* events must still load the tab, so a failed subscription is ignored and
|
|
342
|
+
* the panel simply falls back to per-step progress.
|
|
343
|
+
*
|
|
344
|
+
* @param name - the event name.
|
|
345
|
+
* @param listener - the callback.
|
|
346
|
+
* @param label - the effect label.
|
|
347
|
+
*/
|
|
348
|
+
const subscribe = (name, listener, label) => {
|
|
349
|
+
try {
|
|
350
|
+
ctx.effect(() => ctx.on(name, listener), label);
|
|
351
|
+
}
|
|
352
|
+
catch {
|
|
353
|
+
/* the panel degrades to step-level progress */
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
subscribe('plugin-manager/install-log', (chunk) => {
|
|
357
|
+
const job = liveJob;
|
|
358
|
+
if (job === null)
|
|
359
|
+
return;
|
|
360
|
+
const tagged = chunk.requestId !== undefined && chunk.requestId !== '';
|
|
361
|
+
if (tagged && !job.requestIds.includes(chunk.requestId))
|
|
362
|
+
return;
|
|
363
|
+
if (chunk.exitCode !== undefined && chunk.exitCode !== 0 && chunk.exitCode !== null) {
|
|
364
|
+
appendLog(job, `! pnpm exited with ${chunk.exitCode}`);
|
|
365
|
+
}
|
|
366
|
+
appendLog(job, chunk.text);
|
|
367
|
+
/**
|
|
368
|
+
* The manager's `install-state` event is optional. Without this, a host
|
|
369
|
+
* that does not emit it leaves the panel on `waiting` for the whole
|
|
370
|
+
* install, which is what makes a two-minute step read as a hang.
|
|
371
|
+
*/
|
|
372
|
+
job.phase = nextPhase(job.phase, chunk);
|
|
373
|
+
}, 'plugin-upgrader: install output');
|
|
374
|
+
subscribe('plugin-manager/install-state', (state) => {
|
|
375
|
+
const job = liveJob;
|
|
376
|
+
if (job === null)
|
|
377
|
+
return;
|
|
378
|
+
if (!job.requestIds.includes(state.requestId))
|
|
379
|
+
return;
|
|
380
|
+
job.phase = state.phase;
|
|
381
|
+
job.attempt =
|
|
382
|
+
state.attempt === undefined
|
|
383
|
+
? undefined
|
|
384
|
+
: {
|
|
385
|
+
index: state.attempt.index,
|
|
386
|
+
total: state.attempt.total,
|
|
387
|
+
registry: state.attempt.registry,
|
|
388
|
+
};
|
|
389
|
+
if (state.attempt !== undefined && state.attempt.total > 1) {
|
|
390
|
+
appendLog(job, `registry ${state.attempt.index}/${state.attempt.total}: ${state.attempt.registry ?? 'default'}`);
|
|
391
|
+
}
|
|
392
|
+
else if (state.phase === 'applying') {
|
|
393
|
+
appendLog(job, 'applying the change…');
|
|
394
|
+
}
|
|
395
|
+
}, 'plugin-upgrader: install phase');
|
|
252
396
|
ctx.effect(() => ctx.webServer.register({ kind: 'prefix', path: API_PREFIX, handler }), 'plugin-upgrader: routes');
|
|
253
397
|
}
|
|
254
398
|
//# sourceMappingURL=index.js.map
|