@quenty/nevermore-cli 4.43.0-canary.783.30683872723.0 → 4.43.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.
Files changed (32) hide show
  1. package/CHANGELOG.md +13 -16
  2. package/README.md +10 -0
  3. package/dist/commands/batch-command/batch-deploy-command.d.ts.map +1 -1
  4. package/dist/commands/batch-command/batch-deploy-command.js +101 -37
  5. package/dist/commands/batch-command/batch-deploy-command.js.map +1 -1
  6. package/dist/commands/deploy-command/index.d.ts +2 -0
  7. package/dist/commands/deploy-command/index.d.ts.map +1 -1
  8. package/dist/commands/deploy-command/index.js +35 -22
  9. package/dist/commands/deploy-command/index.js.map +1 -1
  10. package/dist/utils/job-context/base-job-context.d.ts.map +1 -1
  11. package/dist/utils/job-context/base-job-context.js +1 -2
  12. package/dist/utils/job-context/base-job-context.js.map +1 -1
  13. package/dist/utils/open-cloud/open-cloud-client.d.ts +8 -4
  14. package/dist/utils/open-cloud/open-cloud-client.d.ts.map +1 -1
  15. package/dist/utils/open-cloud/open-cloud-client.js +8 -4
  16. package/dist/utils/open-cloud/open-cloud-client.js.map +1 -1
  17. package/dist/utils/watch/github-context.test.js +9 -1
  18. package/dist/utils/watch/github-context.test.js.map +1 -1
  19. package/dist/utils/watch/http-watch-registry.js +5 -3
  20. package/dist/utils/watch/http-watch-registry.js.map +1 -1
  21. package/dist/utils/watch/stream-watch-loop.d.ts.map +1 -1
  22. package/dist/utils/watch/stream-watch-loop.js +42 -51
  23. package/dist/utils/watch/stream-watch-loop.js.map +1 -1
  24. package/package.json +5 -5
  25. package/src/commands/batch-command/batch-deploy-command.ts +135 -41
  26. package/src/commands/deploy-command/index.ts +41 -24
  27. package/src/utils/job-context/base-job-context.ts +1 -2
  28. package/src/utils/open-cloud/open-cloud-client.ts +8 -4
  29. package/src/utils/watch/github-context.test.ts +10 -1
  30. package/src/utils/watch/http-watch-registry.ts +5 -3
  31. package/src/utils/watch/stream-watch-loop.ts +73 -59
  32. package/tsconfig.tsbuildinfo +1 -1
@@ -96,42 +96,51 @@ export async function runStreamWatchLoopAsync(
96
96
  process.on('SIGINT', onSigint);
97
97
  }
98
98
 
99
- const byWatchName = new Map(entries.map((e) => [e.watchName, e]));
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
- * What each place has actually been built from here, as an Open Cloud place
103
- * version. Seeded from the lock and advanced only by a rebuild that
104
- * succeeded: the service moves its own baseline whether or not anyone was
105
- * listening, so this map is the only durable record of what exists.
106
- */
107
- const built = new Map<string, number | undefined>(
108
- entries.map((e) => [e.watchName, e.baseline])
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
- if (unreadable.size < byWatchName.size) {
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
- entry: StreamWatchEntry,
195
+ state: WatchState,
176
196
  token: string | null
177
197
  ): Promise<void> => {
178
- if (token != null && settledAt.get(entry.watchName) === token) {
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.get(entry.watchName);
220
+ const previous = state.built;
200
221
  const settle = () => {
201
222
  if (token != null) {
202
- settledAt.set(entry.watchName, token);
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.set(entry.watchName, latest);
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.set(entry.watchName, latest);
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) => byWatchName.has(s.name));
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 = [...byWatchName.keys()].filter(
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 (unreadable.has(name)) {
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
- byWatchName.get(name)!.label
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 (unreadable.has(status.name)) {
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
- byWatchName.get(status.name)!.label
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
- if (!unreadable.has(status.name)) {
306
- await settleAsync(
307
- byWatchName.get(status.name)!,
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 entry = byWatchName.get(notify.watchName);
316
- if (!entry) {
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(entry, notify.version);
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
- message.event.type !== 'poll-failed' ||
339
- name == null ||
340
- !byWatchName.has(name) ||
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 ${byWatchName.get(name)!.label}: ${
361
+ `The watch service cannot read ${watches.get(name)!.entry.label}: ${
348
362
  message.event.message
349
363
  }`
350
364
  );