@quenty/nevermore-cli 4.43.0-canary.783.30683872723.0 → 4.43.1
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 +17 -14
- package/README.md +10 -0
- package/dist/commands/batch-command/batch-deploy-command.d.ts.map +1 -1
- package/dist/commands/batch-command/batch-deploy-command.js +101 -37
- package/dist/commands/batch-command/batch-deploy-command.js.map +1 -1
- package/dist/commands/deploy-command/index.d.ts +2 -0
- package/dist/commands/deploy-command/index.d.ts.map +1 -1
- package/dist/commands/deploy-command/index.js +35 -22
- package/dist/commands/deploy-command/index.js.map +1 -1
- package/dist/utils/job-context/base-job-context.d.ts.map +1 -1
- package/dist/utils/job-context/base-job-context.js +1 -2
- package/dist/utils/job-context/base-job-context.js.map +1 -1
- package/dist/utils/open-cloud/open-cloud-client.d.ts +8 -4
- package/dist/utils/open-cloud/open-cloud-client.d.ts.map +1 -1
- package/dist/utils/open-cloud/open-cloud-client.js +12 -18
- package/dist/utils/open-cloud/open-cloud-client.js.map +1 -1
- package/dist/utils/testing/parsers/batch-log-parser.d.ts.map +1 -1
- package/dist/utils/testing/parsers/batch-log-parser.js +15 -3
- package/dist/utils/testing/parsers/batch-log-parser.js.map +1 -1
- package/dist/utils/testing/parsers/batch-log-parser.test.js +30 -0
- package/dist/utils/testing/parsers/batch-log-parser.test.js.map +1 -1
- package/dist/utils/watch/github-context.test.js +9 -1
- package/dist/utils/watch/github-context.test.js.map +1 -1
- package/dist/utils/watch/http-watch-registry.js +5 -3
- package/dist/utils/watch/http-watch-registry.js.map +1 -1
- package/dist/utils/watch/stream-watch-loop.d.ts.map +1 -1
- package/dist/utils/watch/stream-watch-loop.js +42 -51
- package/dist/utils/watch/stream-watch-loop.js.map +1 -1
- package/package.json +5 -5
- package/src/commands/batch-command/batch-deploy-command.ts +135 -41
- package/src/commands/deploy-command/index.ts +41 -24
- package/src/utils/job-context/base-job-context.ts +1 -2
- package/src/utils/open-cloud/open-cloud-client.ts +12 -19
- package/src/utils/testing/parsers/batch-log-parser.test.ts +36 -0
- package/src/utils/testing/parsers/batch-log-parser.ts +16 -3
- package/src/utils/watch/github-context.test.ts +10 -1
- package/src/utils/watch/http-watch-registry.ts +5 -3
- package/src/utils/watch/stream-watch-loop.ts +73 -59
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -342,8 +342,10 @@ export class OpenCloudClient {
|
|
|
342
342
|
|
|
343
343
|
private async _fetchRawLogsAsync(taskPath: string): Promise<string> {
|
|
344
344
|
const apiKey = await this._resolveApiKeyAsync();
|
|
345
|
+
// Arrival order, deliberately. Do not sort by createTime: every print in a
|
|
346
|
+
// single frame shares one value, so it cannot order them anyway, and the
|
|
347
|
+
// fractional digit count varies, so comparing the strings reorders them.
|
|
345
348
|
const messages: string[] = [];
|
|
346
|
-
const structured: { message: string; createTime: string }[] = [];
|
|
347
349
|
let pageToken: string | undefined;
|
|
348
350
|
let pagesFetched = 0;
|
|
349
351
|
let entriesFetched = 0;
|
|
@@ -381,14 +383,7 @@ export class OpenCloudClient {
|
|
|
381
383
|
|
|
382
384
|
for (const entry of data.luauExecutionSessionTaskLogs ?? []) {
|
|
383
385
|
if (entry.structuredMessages?.length) {
|
|
384
|
-
|
|
385
|
-
// chronological order, and readers downstream assume it.
|
|
386
|
-
for (const msg of entry.structuredMessages) {
|
|
387
|
-
structured.push({
|
|
388
|
-
message: msg.message,
|
|
389
|
-
createTime: msg.createTime,
|
|
390
|
-
});
|
|
391
|
-
}
|
|
386
|
+
messages.push(...entry.structuredMessages.map((msg) => msg.message));
|
|
392
387
|
} else if (entry.messages?.length) {
|
|
393
388
|
messages.push(...entry.messages);
|
|
394
389
|
}
|
|
@@ -399,12 +394,6 @@ export class OpenCloudClient {
|
|
|
399
394
|
pageToken = data.nextPageToken || undefined;
|
|
400
395
|
} while (pageToken);
|
|
401
396
|
|
|
402
|
-
// Out-of-order messages silently destroy marker-delimited parsing: a
|
|
403
|
-
// trailing summary that arrives early ends the scan before any of the
|
|
404
|
-
// output it summarizes has been read.
|
|
405
|
-
structured.sort((a, b) => a.createTime.localeCompare(b.createTime));
|
|
406
|
-
messages.push(...structured.map((m) => m.message));
|
|
407
|
-
|
|
408
397
|
const text = messages.join('\n');
|
|
409
398
|
|
|
410
399
|
// Log volume is the prime suspect when engine output goes missing between
|
|
@@ -502,10 +491,14 @@ export class OpenCloudClient {
|
|
|
502
491
|
|
|
503
492
|
/**
|
|
504
493
|
* Resolve the newest saved or published version number of a place, via the
|
|
505
|
-
* Open Cloud Assets API (`asset:read` scope
|
|
506
|
-
*
|
|
507
|
-
*
|
|
508
|
-
*
|
|
494
|
+
* Open Cloud Assets API (`asset:read` scope). The returned number is the same
|
|
495
|
+
* value the Asset Delivery API's `/version/{n}` route expects, so it can be
|
|
496
|
+
* written straight into a `basePlace.version` pin.
|
|
497
|
+
*
|
|
498
|
+
* This used to add that `legacy-asset:manage` grants `asset:read` too. They
|
|
499
|
+
* are separate boxes on the credentials page and nothing here ever verified
|
|
500
|
+
* the implication, so the claim is gone rather than restated — it had already
|
|
501
|
+
* been copied into the `nevermore login` instructions as fact once.
|
|
509
502
|
*
|
|
510
503
|
* `saved` is the newest version of any kind — Studio saves and publishes
|
|
511
504
|
* share one increasing version sequence. `published` is the newest version
|
|
@@ -99,6 +99,42 @@ describe('parseBatchTestLogs', () => {
|
|
|
99
99
|
expect(result?.logs).toContain('PlayerBadgeHelper');
|
|
100
100
|
});
|
|
101
101
|
|
|
102
|
+
it('closes a section whose END was delivered after the summary', () => {
|
|
103
|
+
const logs = [
|
|
104
|
+
'===BATCH_TEST_BEGIN egghunt2026===',
|
|
105
|
+
'Tests: 1014 passed, 1077 total',
|
|
106
|
+
'===BATCH_TEST_SUMMARY===',
|
|
107
|
+
'[{"slug":"egghunt2026","success":true,"durationMs":110467}]',
|
|
108
|
+
'===BATCH_TEST_END egghunt2026 PASS 110467===',
|
|
109
|
+
].join('\n');
|
|
110
|
+
|
|
111
|
+
const result = parseBatchTestLogs(logs, SLUG_MAP).get('egghunt2026');
|
|
112
|
+
|
|
113
|
+
expect(result?.success).toBe(true);
|
|
114
|
+
expect(result?.error).toBeUndefined();
|
|
115
|
+
expect(result?.testCounts).toEqual({
|
|
116
|
+
passed: 1014,
|
|
117
|
+
failed: 0,
|
|
118
|
+
total: 1077,
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('keeps the summary payload out of the section it interrupts', () => {
|
|
123
|
+
const logs = [
|
|
124
|
+
'===BATCH_TEST_BEGIN egghunt2026===',
|
|
125
|
+
'Tests: 10 passed, 10 total',
|
|
126
|
+
'===BATCH_TEST_SUMMARY===',
|
|
127
|
+
'[{"slug":"egghunt2026","success":true},' +
|
|
128
|
+
'{"slug":"other","success":false,"error":"boom"}]',
|
|
129
|
+
'===BATCH_TEST_END egghunt2026 PASS 10===',
|
|
130
|
+
].join('\n');
|
|
131
|
+
|
|
132
|
+
const result = parseBatchTestLogs(logs, SLUG_MAP).get('egghunt2026');
|
|
133
|
+
|
|
134
|
+
expect(result?.logs).not.toContain('boom');
|
|
135
|
+
expect(result?.success).toBe(true);
|
|
136
|
+
});
|
|
137
|
+
|
|
102
138
|
it('counts tracebacks that jest reports as a clean pass', () => {
|
|
103
139
|
const logs = buildLogs(
|
|
104
140
|
[
|
|
@@ -70,6 +70,8 @@ export function parseBatchTestLogs(
|
|
|
70
70
|
let currentSlug: string | null = null;
|
|
71
71
|
let currentLines: string[] = [];
|
|
72
72
|
let summaryLineIndex = -1;
|
|
73
|
+
/** True between the summary marker and its JSON payload. */
|
|
74
|
+
let summaryPayloadPending = false;
|
|
73
75
|
let beginMarkersSeen = 0;
|
|
74
76
|
let endMarkersSeen = 0;
|
|
75
77
|
let strayEndMarkers = 0;
|
|
@@ -130,8 +132,19 @@ export function parseBatchTestLogs(
|
|
|
130
132
|
}
|
|
131
133
|
|
|
132
134
|
if (trimmed === SUMMARY_MARKER) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
+
// Keep scanning: the summary prints last but is not always delivered last,
|
|
136
|
+
// and a section's END can follow it.
|
|
137
|
+
if (summaryLineIndex < 0) {
|
|
138
|
+
summaryLineIndex = i;
|
|
139
|
+
}
|
|
140
|
+
summaryPayloadPending = true;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// The summary's JSON payload belongs to no section.
|
|
145
|
+
if (summaryPayloadPending && trimmed.trimStart().startsWith('[')) {
|
|
146
|
+
summaryPayloadPending = false;
|
|
147
|
+
continue;
|
|
135
148
|
}
|
|
136
149
|
|
|
137
150
|
// Accumulate unconditionally: output that precedes the first surviving
|
|
@@ -209,7 +222,7 @@ export function parseBatchTestLogs(
|
|
|
209
222
|
OutputHelper.warn(
|
|
210
223
|
summaryLineIndex >= lines.length - 2
|
|
211
224
|
? '[batch-log-parser] Summary sits at the end of the log — consistent with the head being dropped by a log size/retention limit.'
|
|
212
|
-
: '[batch-log-parser] Summary sits early in the log — consistent with
|
|
225
|
+
: '[batch-log-parser] Summary sits early in the log — consistent with messages delivered out of order.'
|
|
213
226
|
);
|
|
214
227
|
}
|
|
215
228
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, it, expect, vi, afterEach } from 'vitest';
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
2
|
import {
|
|
3
3
|
describeMissingWatchToken,
|
|
4
4
|
parseGithubRepository,
|
|
@@ -35,6 +35,15 @@ describe('tryResolveGithubDispatchContext token', () => {
|
|
|
35
35
|
'git rev-parse': 'main',
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
+
// Cleared so these exercise the git fallback rather than whatever the host
|
|
39
|
+
// happens to export. Actions sets both, and on a pull request GITHUB_REF_NAME
|
|
40
|
+
// is a merge ref — which resolution rightly refuses, so without this the
|
|
41
|
+
// whole describe passes locally and fails only in CI.
|
|
42
|
+
beforeEach(() => {
|
|
43
|
+
vi.stubEnv('GITHUB_REPOSITORY', '');
|
|
44
|
+
vi.stubEnv('GITHUB_REF_NAME', '');
|
|
45
|
+
});
|
|
46
|
+
|
|
38
47
|
// Registering ships the token to the watch service, so it has to be one the
|
|
39
48
|
// user handed over. Nearly every dev machine has a logged-in `gh`, so reading
|
|
40
49
|
// it by default would turn a local watch into a silent credential send.
|
|
@@ -239,9 +239,11 @@ function _describeFailure(
|
|
|
239
239
|
case 422: {
|
|
240
240
|
// 422 is the service's "your config cannot work" answer, and it covers
|
|
241
241
|
// several unrelated causes now — a missing workflow, an Open Cloud key
|
|
242
|
-
//
|
|
243
|
-
// the wrong vocabulary. Its own message names which, so the only thing
|
|
244
|
-
// worth adding is a remedy that could not already be in it.
|
|
242
|
+
// that cannot read a base place, a "saved" watch with no key, a baseline
|
|
243
|
+
// in the wrong vocabulary. Its own message names which, so the only thing
|
|
244
|
+
// worth adding is a remedy that could not already be in it. The scope
|
|
245
|
+
// itself is deliberately not named here: this file would then be a second
|
|
246
|
+
// place to correct when it is wrong, and it already has been once.
|
|
245
247
|
const workflows = [
|
|
246
248
|
...new Set(
|
|
247
249
|
request.watches.flatMap((w) =>
|
|
@@ -96,42 +96,51 @@ export async function runStreamWatchLoopAsync(
|
|
|
96
96
|
process.on('SIGINT', onSigint);
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
|
|
99
|
+
/** Everything this loop knows about one watch, in one place. */
|
|
100
|
+
interface WatchState {
|
|
101
|
+
entry: StreamWatchEntry;
|
|
102
|
+
/**
|
|
103
|
+
* What the place has actually been built from here, as an Open Cloud place
|
|
104
|
+
* version. Seeded from the lock and advanced only by a rebuild that
|
|
105
|
+
* succeeded: the service moves its own baseline whether or not anyone was
|
|
106
|
+
* listening, so this is the only durable record of what exists.
|
|
107
|
+
*/
|
|
108
|
+
built: number | undefined;
|
|
109
|
+
/**
|
|
110
|
+
* The last service-side token this watch was settled at. Purely an
|
|
111
|
+
* optimization — a reconnect that reports the same token needs no Open
|
|
112
|
+
* Cloud call — and deliberately not advanced when a rebuild fails, so the
|
|
113
|
+
* next `ready` retries it.
|
|
114
|
+
*/
|
|
115
|
+
settledAt?: string;
|
|
116
|
+
/**
|
|
117
|
+
* Set once the service has told us it cannot read this source, from either
|
|
118
|
+
* direction: the `ready` snapshot on connect, or a `poll-failed` event as
|
|
119
|
+
* it happens.
|
|
120
|
+
*
|
|
121
|
+
* Both are needed. A connection opened before the service's first failed
|
|
122
|
+
* poll gets a clean snapshot, and the next one is an hour away — so without
|
|
123
|
+
* the live tail a private base place would look fine for an hour before the
|
|
124
|
+
* watch admitted it was doing nothing.
|
|
125
|
+
*/
|
|
126
|
+
unreadable: boolean;
|
|
127
|
+
}
|
|
100
128
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
129
|
+
// One record per watch rather than parallel maps under the same key: every
|
|
130
|
+
// field above is a fact about the same thing, and holding them apart only
|
|
131
|
+
// creates the possibility of disagreeing about which watches exist.
|
|
132
|
+
const watches = new Map<string, WatchState>(
|
|
133
|
+
entries.map((e) => [
|
|
134
|
+
e.watchName,
|
|
135
|
+
{ entry: e, built: e.baseline, unreadable: false },
|
|
136
|
+
])
|
|
109
137
|
);
|
|
110
138
|
|
|
111
|
-
/**
|
|
112
|
-
* The last service-side token each watch was settled at. Purely an
|
|
113
|
-
* optimization — a reconnect that reports the same token needs no Open Cloud
|
|
114
|
-
* call — and deliberately not advanced when a rebuild fails, so the next
|
|
115
|
-
* `ready` retries it.
|
|
116
|
-
*/
|
|
117
|
-
const settledAt = new Map<string, string>();
|
|
118
|
-
|
|
119
139
|
let redeploys = 0;
|
|
120
140
|
let failures = 0;
|
|
121
141
|
let lastDropReason: string | undefined;
|
|
122
142
|
let unobservable = false;
|
|
123
143
|
|
|
124
|
-
/**
|
|
125
|
-
* Watches the service has told us it cannot read, from either direction:
|
|
126
|
-
* the `ready` snapshot on connect, or a `poll-failed` event as it happens.
|
|
127
|
-
*
|
|
128
|
-
* Both are needed. A connection opened before the service's first failed
|
|
129
|
-
* poll gets a clean snapshot, and the next one is an hour away — so without
|
|
130
|
-
* the live tail a private base place would look fine for an hour before the
|
|
131
|
-
* watch admitted it was doing nothing.
|
|
132
|
-
*/
|
|
133
|
-
const unreadable = new Set<string>();
|
|
134
|
-
|
|
135
144
|
/**
|
|
136
145
|
* Give up the stream once nothing on it can ever fire, so the caller can
|
|
137
146
|
* poll instead — this machine's credentials reach places the service's
|
|
@@ -141,7 +150,8 @@ export async function runStreamWatchLoopAsync(
|
|
|
141
150
|
// Counted against every entry this run owns, not against what the last
|
|
142
151
|
// frame happened to mention: a watch the service has stopped reporting
|
|
143
152
|
// altogether can no more produce a notification than one it cannot read.
|
|
144
|
-
|
|
153
|
+
const all = [...watches.values()];
|
|
154
|
+
if (all.length === 0 || !all.every((w) => w.unreadable)) {
|
|
145
155
|
return false;
|
|
146
156
|
}
|
|
147
157
|
unobservable = true;
|
|
@@ -149,6 +159,16 @@ export async function runStreamWatchLoopAsync(
|
|
|
149
159
|
return true;
|
|
150
160
|
};
|
|
151
161
|
|
|
162
|
+
/** Marks a watch unreadable, and says whether that was news worth printing. */
|
|
163
|
+
const markUnreadable = (name: string): boolean => {
|
|
164
|
+
const state = watches.get(name);
|
|
165
|
+
if (state == null || state.unreadable) {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
state.unreadable = true;
|
|
169
|
+
return true;
|
|
170
|
+
};
|
|
171
|
+
|
|
152
172
|
OutputHelper.info(
|
|
153
173
|
`Watching ${entries.length} base place${
|
|
154
174
|
entries.length === 1 ? '' : 's'
|
|
@@ -172,10 +192,11 @@ export async function runStreamWatchLoopAsync(
|
|
|
172
192
|
* what the place is actually at, which is the same number the build records.
|
|
173
193
|
*/
|
|
174
194
|
const settleAsync = async (
|
|
175
|
-
|
|
195
|
+
state: WatchState,
|
|
176
196
|
token: string | null
|
|
177
197
|
): Promise<void> => {
|
|
178
|
-
|
|
198
|
+
const entry = state.entry;
|
|
199
|
+
if (token != null && state.settledAt === token) {
|
|
179
200
|
return;
|
|
180
201
|
}
|
|
181
202
|
|
|
@@ -196,17 +217,17 @@ export async function runStreamWatchLoopAsync(
|
|
|
196
217
|
return;
|
|
197
218
|
}
|
|
198
219
|
|
|
199
|
-
const previous = built
|
|
220
|
+
const previous = state.built;
|
|
200
221
|
const settle = () => {
|
|
201
222
|
if (token != null) {
|
|
202
|
-
settledAt
|
|
223
|
+
state.settledAt = token;
|
|
203
224
|
}
|
|
204
225
|
};
|
|
205
226
|
|
|
206
227
|
if (previous === undefined) {
|
|
207
228
|
// Never built here and nothing locked: adopt what is there rather than
|
|
208
229
|
// reading "unknown -> something" as a change.
|
|
209
|
-
built
|
|
230
|
+
state.built = latest;
|
|
210
231
|
settle();
|
|
211
232
|
return;
|
|
212
233
|
}
|
|
@@ -221,7 +242,7 @@ export async function runStreamWatchLoopAsync(
|
|
|
221
242
|
try {
|
|
222
243
|
await redeployAsync(entry, latest);
|
|
223
244
|
redeploys++;
|
|
224
|
-
built
|
|
245
|
+
state.built = latest;
|
|
225
246
|
settle();
|
|
226
247
|
OutputHelper.info(`${entry.label}: rebuilt from v${latest}.`);
|
|
227
248
|
} catch (err) {
|
|
@@ -244,7 +265,7 @@ export async function runStreamWatchLoopAsync(
|
|
|
244
265
|
// Every connection starts here, including reconnects. Nothing is
|
|
245
266
|
// replayed while a client is away, so this is the whole catch-up.
|
|
246
267
|
onReadyAsync: async (ready: WatchStreamReady) => {
|
|
247
|
-
const ours = ready.watches.filter((s) =>
|
|
268
|
+
const ours = ready.watches.filter((s) => watches.has(s.name));
|
|
248
269
|
|
|
249
270
|
// A source the service cannot read never fires, and the socket says
|
|
250
271
|
// nothing about it — the failure is visible only here. Waiting
|
|
@@ -268,17 +289,16 @@ export async function runStreamWatchLoopAsync(
|
|
|
268
289
|
// the same set means a partial snapshot falls back too, instead of
|
|
269
290
|
// holding the socket for the ones that remain and silently never
|
|
270
291
|
// rebuilding the rest.
|
|
271
|
-
const missing = [...
|
|
292
|
+
const missing = [...watches.keys()].filter(
|
|
272
293
|
(name) => !ready.watches.some((s) => s.name === name)
|
|
273
294
|
);
|
|
274
295
|
for (const name of missing) {
|
|
275
|
-
if (
|
|
296
|
+
if (!markUnreadable(name)) {
|
|
276
297
|
continue;
|
|
277
298
|
}
|
|
278
|
-
unreadable.add(name);
|
|
279
299
|
OutputHelper.warn(
|
|
280
300
|
`The watch monitor no longer holds ${
|
|
281
|
-
|
|
301
|
+
watches.get(name)!.entry.label
|
|
282
302
|
} — another checkout has probably re-registered it.`
|
|
283
303
|
);
|
|
284
304
|
}
|
|
@@ -287,13 +307,12 @@ export async function runStreamWatchLoopAsync(
|
|
|
287
307
|
}
|
|
288
308
|
|
|
289
309
|
for (const status of failedToRead) {
|
|
290
|
-
if (
|
|
310
|
+
if (!markUnreadable(status.name)) {
|
|
291
311
|
continue;
|
|
292
312
|
}
|
|
293
|
-
unreadable.add(status.name);
|
|
294
313
|
OutputHelper.warn(
|
|
295
314
|
`The watch service cannot read ${
|
|
296
|
-
|
|
315
|
+
watches.get(status.name)!.entry.label
|
|
297
316
|
}` + (status.sourceError ? `: ${status.sourceError}` : '.')
|
|
298
317
|
);
|
|
299
318
|
}
|
|
@@ -302,25 +321,23 @@ export async function runStreamWatchLoopAsync(
|
|
|
302
321
|
}
|
|
303
322
|
|
|
304
323
|
for (const status of ours) {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
status.currentVersion
|
|
309
|
-
);
|
|
324
|
+
const state = watches.get(status.name)!;
|
|
325
|
+
if (!state.unreadable) {
|
|
326
|
+
await settleAsync(state, status.currentVersion);
|
|
310
327
|
}
|
|
311
328
|
}
|
|
312
329
|
},
|
|
313
330
|
|
|
314
331
|
onNotifyAsync: async (notify: WatchStreamNotify) => {
|
|
315
|
-
const
|
|
316
|
-
if (!
|
|
332
|
+
const state = watches.get(notify.watchName);
|
|
333
|
+
if (!state) {
|
|
317
334
|
OutputHelper.verbose(
|
|
318
335
|
`Ignoring a notification for "${notify.watchName}", which this ` +
|
|
319
336
|
'run does not watch.'
|
|
320
337
|
);
|
|
321
338
|
return;
|
|
322
339
|
}
|
|
323
|
-
await settleAsync(
|
|
340
|
+
await settleAsync(state, notify.version);
|
|
324
341
|
},
|
|
325
342
|
|
|
326
343
|
onEvent: (message) => {
|
|
@@ -334,17 +351,14 @@ export async function runStreamWatchLoopAsync(
|
|
|
334
351
|
// is polling something that could have been streamed, which still
|
|
335
352
|
// works.
|
|
336
353
|
const name = message.event.watchName;
|
|
337
|
-
if (
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
unreadable.has(name)
|
|
342
|
-
) {
|
|
354
|
+
if (message.event.type !== 'poll-failed' || name == null) {
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
if (!markUnreadable(name)) {
|
|
343
358
|
return;
|
|
344
359
|
}
|
|
345
|
-
unreadable.add(name);
|
|
346
360
|
OutputHelper.warn(
|
|
347
|
-
`The watch service cannot read ${
|
|
361
|
+
`The watch service cannot read ${watches.get(name)!.entry.label}: ${
|
|
348
362
|
message.event.message
|
|
349
363
|
}`
|
|
350
364
|
);
|