@laurence79/wireit 0.14.13-shared-cache.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 (54) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +1062 -0
  3. package/bin/wireit.js +9 -0
  4. package/lib/analyzer.js +1600 -0
  5. package/lib/caching/cache.js +7 -0
  6. package/lib/caching/github-actions-cache.js +832 -0
  7. package/lib/caching/local-cache.js +78 -0
  8. package/lib/caching/shared-cache.js +256 -0
  9. package/lib/cli-options.js +495 -0
  10. package/lib/cli.js +177 -0
  11. package/lib/config.js +18 -0
  12. package/lib/error.js +160 -0
  13. package/lib/event.js +7 -0
  14. package/lib/execution/base.js +108 -0
  15. package/lib/execution/no-command.js +32 -0
  16. package/lib/execution/service.js +1017 -0
  17. package/lib/execution/standard.js +683 -0
  18. package/lib/executor.js +249 -0
  19. package/lib/fingerprint.js +164 -0
  20. package/lib/ide.js +583 -0
  21. package/lib/language-server.js +135 -0
  22. package/lib/logging/combination-logger.js +41 -0
  23. package/lib/logging/debug-logger.js +43 -0
  24. package/lib/logging/logger.js +38 -0
  25. package/lib/logging/metrics-logger.js +108 -0
  26. package/lib/logging/quiet/run-tracker.js +597 -0
  27. package/lib/logging/quiet/stack-map.js +41 -0
  28. package/lib/logging/quiet/writeover-line.js +197 -0
  29. package/lib/logging/quiet-logger.js +78 -0
  30. package/lib/logging/simple-logger.js +296 -0
  31. package/lib/logging/watch-logger.js +81 -0
  32. package/lib/script-child-process.js +270 -0
  33. package/lib/util/ast.js +71 -0
  34. package/lib/util/async-cache.js +24 -0
  35. package/lib/util/copy.js +120 -0
  36. package/lib/util/deferred.js +35 -0
  37. package/lib/util/delete.js +120 -0
  38. package/lib/util/dispose.js +16 -0
  39. package/lib/util/fs.js +258 -0
  40. package/lib/util/glob.js +255 -0
  41. package/lib/util/line-monitor.js +69 -0
  42. package/lib/util/manifest.js +31 -0
  43. package/lib/util/optimize-mkdirs.js +55 -0
  44. package/lib/util/package-json-reader.js +61 -0
  45. package/lib/util/package-json.js +179 -0
  46. package/lib/util/script-data-dir.js +19 -0
  47. package/lib/util/shuffle.js +16 -0
  48. package/lib/util/unreachable.js +12 -0
  49. package/lib/util/windows.js +87 -0
  50. package/lib/util/worker-pool.js +61 -0
  51. package/lib/watcher.js +396 -0
  52. package/package.json +470 -0
  53. package/schema.json +132 -0
  54. package/wireit.svg +1 -0
@@ -0,0 +1,1017 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ import { BaseExecutionWithCommand } from './base.js';
7
+ import { Fingerprint } from '../fingerprint.js';
8
+ import { Deferred } from '../util/deferred.js';
9
+ import { ScriptChildProcess } from '../script-child-process.js';
10
+ import { LineMonitor } from '../util/line-monitor.js';
11
+ function unknownState(state) {
12
+ return new Error(`Unknown service state ${state.id}`);
13
+ }
14
+ function unexpectedState(state) {
15
+ return new Error(`Unexpected service state ${state.id}`);
16
+ }
17
+ /**
18
+ * Execution for a {@link ServiceScriptConfig}.
19
+ *
20
+ * Note that this class represents a service _bound to one particular execution_
21
+ * of the script graph. In non-watch mode (`npm run ...`), there will be one
22
+ * instance of this class per service. In watch mode (`npm run --watch ...`),
23
+ * there will be one instance of this class per service _per watch iteration_,
24
+ * and the underlying child process will be transfered between instances of this
25
+ * class whenever possible to avoid restarts.
26
+ *
27
+ * ```
28
+ * ┌─────────┐
29
+ * ╭─◄─ abort ────┤ INITIAL │
30
+ * │ └────┬────┘
31
+ * │ │
32
+ * ▼ execute
33
+ * │ │
34
+ * │ ┌───────▼────────┐
35
+ * ├─◄─ abort ─┤ EXECUTING_DEPS ├──── depExecErr ────►────╮
36
+ * │ └───────┬────────┘ │
37
+ * │ │ │
38
+ * ▼ depsExecuted │
39
+ * │ │ │
40
+ * │ ┌───────▼────────┐ │
41
+ * ├─◄─ abort ─┤ FINGERPRINTING │ │
42
+ * │ └───────┬────────┘ │
43
+ * │ │ │
44
+ * │ fingerprinted ▼
45
+ * │ │ │
46
+ * │ ╔══════════▼════════════╗ │
47
+ * ▼ ║ adoptee has different ╟─ yes ─╮ │
48
+ * │ ║ fingerprint? ║ │ │
49
+ * │ ╚══════════╤════════════╝ │ │
50
+ * │ │ ▼ │
51
+ * │ no │ │
52
+ * │ │ │ │
53
+ * │ │ ┌─────────▼────────┐ │
54
+ * ├─◄─ abort ─────────│─────◄────┤ STOPPING_ADOPTEE │ │
55
+ * │ │ └─────────┬────────┘ │
56
+ * │ │ │ │
57
+ * │ ▼ adopteeStopped │
58
+ * │ │ │ │
59
+ * │ ├─────◄──────────────╯ │
60
+ * │ │ │
61
+ * ▼ ╔═══════▼════════╗ │
62
+ * │ ║ is persistent? ╟───── yes ──╮ │
63
+ * │ ╚═══════╤════════╝ │ │
64
+ * │ │ │ │
65
+ * │ no │ │
66
+ * │ │ │ │
67
+ * │ ┌─────▼─────┐ │ │
68
+ * ├─◄─ abort ───┤ UNSTARTED │ ▼ ▼
69
+ * │ └─────┬─────┘ │ │
70
+ * │ │ │ │
71
+ * │ start │ │
72
+ * │ │ │ │
73
+ * │ │ ╭─────────◄────────╯ │
74
+ * │ │ │ │
75
+ * │ │ │ ╭─╮ │
76
+ * │ │ │ │start │
77
+ * │ ┌───────▼──▼─▼─┴┐ │
78
+ * ├─◄─ abort ─┤ DEPS_STARTING ├───── depStartErr ───►────┤
79
+ * │ └───────┬───────┘ │
80
+ * │ │ │
81
+ * │ depsStarted │
82
+ * │ │ │
83
+ * │ │ ┌────────────────┐ ╔═════▼════════╗
84
+ * │ ╭◄─ abort ─────│─◄─┤ STARTED_BROKEN ◄─ yes ─╢ has adoptee? ║
85
+ * │ │ │ └───────┬────────┘ ╚═════╤════════╝
86
+ * │ │ │ │ │
87
+ * │ │ │ detach no
88
+ * │ │ │ │ │
89
+ * │ │ │ ╰────────►────────╮ │
90
+ * ▼ │ ╔══════▼═══════╗ │ │
91
+ * │ ▼ ║ has adoptee? ╟───── yes ───────╮ │ │
92
+ * │ │ ╚══════╤═══════╝ │ │ │
93
+ * │ │ │ │ │ │
94
+ * │ │ no │ │ │
95
+ * │ │ │ ╭─╮ ▼ ▼ ▼
96
+ * │ │ │ │ start │ │ │
97
+ * │ │ ┌────▼──▼─┴┐ │ │ │
98
+ * │ ├◄─ abort ┤ STARTING ├──── startErr ──────►───────┤
99
+ * │ │ └────┬────┬┘ │ │ │
100
+ * │ │ │ │ │ │ │
101
+ * │ │ │ ╰─ depServiceExit ───────────►──────────╮
102
+ * │ │ │ (unless watch mode) │ │ │ │
103
+ * │ │ │ │ │ │ │
104
+ * │ │ started │ │ │ │
105
+ * │ │ │ │ │ │ │
106
+ * │ │ ╔══════════▼═══════════╗ │ │ │ │
107
+ * ▼ │ ║ has ready condition? ╟──╮ │ │ │ │
108
+ * │ │ ╚══════════╤═══════════╝ │ │ │ │ │
109
+ * │ │ │ │ │ │ │ │
110
+ * │ │ no yes │ │ │ │
111
+ * │ │ │ │ │ │ │ │
112
+ * │ ▼ ▼ ┌────▼─────┐ ▼ ▼ ▼ ▼
113
+ * │ │ │ │ READYING │ │ │ │ │
114
+ * │ │ │ └────┬───┬─┘ │ │ │ │
115
+ * │ │ │ │ │ │ │ │ │
116
+ * │ │ │ ready ╰── depServiceExit ───►───┤
117
+ * │ │ │ │ (unless watch mode) │
118
+ * │ │ │ ╭─────◄──────╯ │ │ │ │
119
+ * │ │ │ │ │ │ │ │
120
+ * │ │ ╭─╮ │ │ ╭──────────◄──────────╯ │ │ │
121
+ * │ │ start │ │ │ │ │ │ │
122
+ * │ │ ┌▼─┴─▼─▼─▼┐ │ │ │
123
+ * │ ├◄─ abort ┤ STARTED ├── exit ──────────►──────────┤ │
124
+ * │ │ └──────┬─┬┘ │ │ │
125
+ * │ │ │ │ │ │ │
126
+ * │ │ │ ╰── depServiceExit ────────────►─────────┤
127
+ * │ │ │ (unless watch mode) │ │ │
128
+ * │ ▼ │ │ │ │
129
+ * │ │ ╰─── detach ──►─┬─────◄─────╯ │ │
130
+ * │ │ │ │ │
131
+ * ▼ │ │ │ │
132
+ * │ │ ┌──────────┐ │ │ ┌────▼────┐
133
+ * │ ╰─────────► STOPPING │ ▼ ▼ │ FAILING │
134
+ * │ └┬─▲─┬─────┘ │ │ └────┬────┘
135
+ * │ abort │ │ │ │ │
136
+ * │ ╰─╯ │ │ │ exit
137
+ * │ exit │ │ │
138
+ * │ │ ╭─╮ │ │ ╭─╮ │
139
+ * │ │ │ start │ │ │ start │
140
+ * │ ┌────▼─▼─┴┐ ┌────▼─────┐ ┌───▼─▼─┴┐ │
141
+ * ╰──────────────► STOPPED │ │ DETACHED │ │ FAILED ◄────╯
142
+ * └┬─▲──────┘ └┬─▲───────┘ └┬─▲─────┘
143
+ * abort │ *all* │ abort │
144
+ * ╰─╯ ╰─╯ ╰─╯
145
+ * ```
146
+ */
147
+ export class ServiceScriptExecution extends BaseExecutionWithCommand {
148
+ #state;
149
+ #terminated;
150
+ #isWatchMode;
151
+ constructor(config, executor, logger, entireExecutionAborted, adoptee, isWatchMode) {
152
+ super(config, executor, logger);
153
+ this.#terminated = new Deferred();
154
+ /**
155
+ * Resolves as "ok" when this script decides it is no longer needed, and
156
+ * either has begun shutting down, or never needed to start in the first
157
+ * place.
158
+ *
159
+ * Resolves with an error if this service exited unexpectedly, or if any of
160
+ * its own service dependencies exited unexpectedly.
161
+ */
162
+ this.terminated = this.#terminated.promise;
163
+ this.#isWatchMode = isWatchMode;
164
+ this.#state = {
165
+ id: 'initial',
166
+ entireExecutionAborted,
167
+ adoptee,
168
+ };
169
+ // Doing this here ensures that we always log when the
170
+ // service stops, no matter how that happens.
171
+ void this.#terminated.promise.then(() => {
172
+ this._logger.log({
173
+ script: this._config,
174
+ type: 'info',
175
+ detail: 'service-stopped',
176
+ });
177
+ });
178
+ }
179
+ /**
180
+ * Return the fingerprint of this service. Throws if the fingerprint is not
181
+ * yet available. Returns undefined if the service is stopped/failed/detached.
182
+ */
183
+ get fingerprint() {
184
+ switch (this.#state.id) {
185
+ case 'stoppingAdoptee':
186
+ case 'unstarted':
187
+ case 'depsStarting':
188
+ case 'starting':
189
+ case 'readying':
190
+ case 'started':
191
+ case 'started-broken':
192
+ case 'stopping':
193
+ case 'failing': {
194
+ return this.#state.fingerprint;
195
+ }
196
+ case 'stopped':
197
+ case 'failed': {
198
+ return;
199
+ }
200
+ case 'initial':
201
+ case 'executingDeps':
202
+ case 'fingerprinting':
203
+ case 'detached': {
204
+ throw unexpectedState(this.#state);
205
+ }
206
+ default: {
207
+ throw unknownState(this.#state);
208
+ }
209
+ }
210
+ }
211
+ /**
212
+ * Take over ownership of this service's running child process, if there is
213
+ * one.
214
+ */
215
+ detach() {
216
+ switch (this.#state.id) {
217
+ case 'started':
218
+ case 'started-broken':
219
+ case 'stopping':
220
+ case 'failing': {
221
+ const { child, fingerprint } = this.#state;
222
+ this.#state = { id: 'detached' };
223
+ this.#stopLoggingChildStdio(child);
224
+ return { child, fingerprint };
225
+ }
226
+ case 'stopped':
227
+ case 'failed': {
228
+ return undefined;
229
+ }
230
+ case 'unstarted':
231
+ case 'depsStarting':
232
+ case 'starting':
233
+ case 'readying':
234
+ case 'initial':
235
+ case 'executingDeps':
236
+ case 'fingerprinting':
237
+ case 'stoppingAdoptee':
238
+ case 'detached': {
239
+ throw unexpectedState(this.#state);
240
+ }
241
+ default: {
242
+ throw unknownState(this.#state);
243
+ }
244
+ }
245
+ }
246
+ /**
247
+ * Note `execute` is a bit of a misnomer here, because we don't actually
248
+ * execute the command at this stage in the case of services.
249
+ */
250
+ _execute() {
251
+ switch (this.#state.id) {
252
+ case 'initial': {
253
+ const allConsumersDone = Promise.all(this._config.serviceConsumers.map((consumer) => this._executor.getExecution(consumer).servicesNotNeeded));
254
+ const abort = this._config.isPersistent
255
+ ? Promise.all([this.#state.entireExecutionAborted, allConsumersDone])
256
+ : allConsumersDone;
257
+ void abort.then(() => {
258
+ void this.abort();
259
+ });
260
+ this.#state = {
261
+ id: 'executingDeps',
262
+ deferredFingerprint: new Deferred(),
263
+ adoptee: this.#state.adoptee,
264
+ };
265
+ void this._executeDependencies().then((result) => {
266
+ if (result.ok) {
267
+ this.#onDepsExecuted(result.value);
268
+ }
269
+ else {
270
+ this.#onDepExecErr(result);
271
+ }
272
+ });
273
+ return this.#state.deferredFingerprint.promise;
274
+ }
275
+ case 'executingDeps':
276
+ case 'fingerprinting':
277
+ case 'stoppingAdoptee':
278
+ case 'unstarted':
279
+ case 'depsStarting':
280
+ case 'starting':
281
+ case 'readying':
282
+ case 'started':
283
+ case 'started-broken':
284
+ case 'stopping':
285
+ case 'stopped':
286
+ case 'failed':
287
+ case 'failing':
288
+ case 'detached': {
289
+ throw unexpectedState(this.#state);
290
+ }
291
+ default: {
292
+ throw unknownState(this.#state);
293
+ }
294
+ }
295
+ }
296
+ #onDepsExecuted(depFingerprints) {
297
+ switch (this.#state.id) {
298
+ case 'executingDeps': {
299
+ this.#state = {
300
+ id: 'fingerprinting',
301
+ deferredFingerprint: this.#state.deferredFingerprint,
302
+ adoptee: this.#state.adoptee,
303
+ };
304
+ void Fingerprint.compute(this._config, depFingerprints).then((result) => {
305
+ if (!result.ok) {
306
+ this.#onFingerprintingErr(result.error);
307
+ }
308
+ else {
309
+ this.#onFingerprinted(result.value);
310
+ }
311
+ });
312
+ return;
313
+ }
314
+ case 'stopped':
315
+ case 'failed': {
316
+ return;
317
+ }
318
+ case 'initial':
319
+ case 'fingerprinting':
320
+ case 'stoppingAdoptee':
321
+ case 'unstarted':
322
+ case 'depsStarting':
323
+ case 'starting':
324
+ case 'readying':
325
+ case 'started':
326
+ case 'started-broken':
327
+ case 'stopping':
328
+ case 'failing':
329
+ case 'detached': {
330
+ throw unexpectedState(this.#state);
331
+ }
332
+ default: {
333
+ throw unknownState(this.#state);
334
+ }
335
+ }
336
+ }
337
+ #onDepExecErr(result) {
338
+ switch (this.#state.id) {
339
+ case 'executingDeps': {
340
+ this.#state.deferredFingerprint.resolve(result);
341
+ const failure = result.error[0];
342
+ const detached = this.#state.adoptee?.detach();
343
+ if (detached !== undefined) {
344
+ this.#enterStartedBrokenState(failure, detached);
345
+ }
346
+ else {
347
+ this.#enterFailedState(failure);
348
+ }
349
+ return;
350
+ }
351
+ case 'started-broken':
352
+ case 'stopped':
353
+ case 'failed': {
354
+ return;
355
+ }
356
+ case 'initial':
357
+ case 'fingerprinting':
358
+ case 'stoppingAdoptee':
359
+ case 'unstarted':
360
+ case 'depsStarting':
361
+ case 'starting':
362
+ case 'readying':
363
+ case 'started':
364
+ case 'stopping':
365
+ case 'failing':
366
+ case 'detached': {
367
+ throw unexpectedState(this.#state);
368
+ }
369
+ default: {
370
+ throw unknownState(this.#state);
371
+ }
372
+ }
373
+ }
374
+ #onFingerprintingErr(failure) {
375
+ switch (this.#state.id) {
376
+ case 'fingerprinting': {
377
+ const detached = this.#state.adoptee?.detach();
378
+ if (detached !== undefined) {
379
+ this.#enterStartedBrokenState(failure, detached);
380
+ }
381
+ else {
382
+ this.#enterFailedState(failure);
383
+ }
384
+ return;
385
+ }
386
+ case 'failed':
387
+ case 'stopped': {
388
+ return;
389
+ }
390
+ case 'initial':
391
+ case 'executingDeps':
392
+ case 'stoppingAdoptee':
393
+ case 'unstarted':
394
+ case 'depsStarting':
395
+ case 'starting':
396
+ case 'readying':
397
+ case 'started':
398
+ case 'started-broken':
399
+ case 'stopping':
400
+ case 'failing':
401
+ case 'detached': {
402
+ throw unexpectedState(this.#state);
403
+ }
404
+ default: {
405
+ throw unknownState(this.#state);
406
+ }
407
+ }
408
+ }
409
+ #onFingerprinted(fingerprint) {
410
+ switch (this.#state.id) {
411
+ case 'fingerprinting': {
412
+ const adoptee = this.#state.adoptee;
413
+ if (adoptee?.fingerprint !== undefined &&
414
+ !adoptee.fingerprint.equal(fingerprint)) {
415
+ // There is a previous running version of this service, but the
416
+ // fingerprint changed, so we need to restart it.
417
+ this.#state = {
418
+ id: 'stoppingAdoptee',
419
+ fingerprint,
420
+ deferredFingerprint: this.#state.deferredFingerprint,
421
+ };
422
+ void adoptee.abort().then(() => {
423
+ this.#onAdopteeStopped();
424
+ });
425
+ return;
426
+ }
427
+ this.#state.deferredFingerprint.resolve({
428
+ ok: true,
429
+ value: fingerprint,
430
+ });
431
+ this.#state = {
432
+ id: 'unstarted',
433
+ fingerprint,
434
+ adoptee,
435
+ };
436
+ if (this._config.isPersistent) {
437
+ void this.start();
438
+ }
439
+ return;
440
+ }
441
+ case 'failed':
442
+ case 'stopped': {
443
+ return;
444
+ }
445
+ case 'initial':
446
+ case 'executingDeps':
447
+ case 'stoppingAdoptee':
448
+ case 'unstarted':
449
+ case 'depsStarting':
450
+ case 'starting':
451
+ case 'readying':
452
+ case 'started':
453
+ case 'started-broken':
454
+ case 'stopping':
455
+ case 'failing':
456
+ case 'detached': {
457
+ throw unexpectedState(this.#state);
458
+ }
459
+ default: {
460
+ throw unknownState(this.#state);
461
+ }
462
+ }
463
+ }
464
+ #onAdopteeStopped() {
465
+ switch (this.#state.id) {
466
+ case 'stoppingAdoptee': {
467
+ this.#state.deferredFingerprint.resolve({
468
+ ok: true,
469
+ value: this.#state.fingerprint,
470
+ });
471
+ this.#state = {
472
+ id: 'unstarted',
473
+ fingerprint: this.#state.fingerprint,
474
+ adoptee: undefined,
475
+ };
476
+ if (this._config.isPersistent) {
477
+ void this.start();
478
+ }
479
+ return;
480
+ }
481
+ case 'failed':
482
+ case 'stopped': {
483
+ return;
484
+ }
485
+ case 'initial':
486
+ case 'executingDeps':
487
+ case 'fingerprinting':
488
+ case 'unstarted':
489
+ case 'depsStarting':
490
+ case 'starting':
491
+ case 'readying':
492
+ case 'started':
493
+ case 'started-broken':
494
+ case 'stopping':
495
+ case 'failing':
496
+ case 'detached': {
497
+ throw unexpectedState(this.#state);
498
+ }
499
+ default: {
500
+ throw unknownState(this.#state);
501
+ }
502
+ }
503
+ }
504
+ /**
505
+ * Start this service if it isn't already started.
506
+ */
507
+ start() {
508
+ switch (this.#state.id) {
509
+ case 'unstarted': {
510
+ const started = new Deferred();
511
+ this.#state = {
512
+ id: 'depsStarting',
513
+ started,
514
+ fingerprint: this.#state.fingerprint,
515
+ adoptee: this.#state.adoptee,
516
+ };
517
+ void this._startServices().then((result) => {
518
+ if (result.ok) {
519
+ this.#onDepsStarted();
520
+ }
521
+ else {
522
+ this.#onDepStartErr(result);
523
+ }
524
+ });
525
+ void this.terminated.then((result) => {
526
+ if (started.settled) {
527
+ return;
528
+ }
529
+ // This service terminated before it started. Either a failure occured
530
+ // or we were aborted. If we were aborted, convert to a failure,
531
+ // because this is the start method, where ok means the service
532
+ // started.
533
+ started.resolve(!result.ok
534
+ ? result
535
+ : {
536
+ ok: false,
537
+ error: {
538
+ type: 'failure',
539
+ script: this._config,
540
+ reason: 'aborted',
541
+ },
542
+ });
543
+ });
544
+ return this.#state.started.promise;
545
+ }
546
+ case 'depsStarting':
547
+ case 'starting':
548
+ case 'readying': {
549
+ return this.#state.started.promise;
550
+ }
551
+ case 'started': {
552
+ return Promise.resolve({ ok: true, value: undefined });
553
+ }
554
+ case 'started-broken':
555
+ case 'failing':
556
+ case 'failed': {
557
+ return Promise.resolve({ ok: false, error: this.#state.failure });
558
+ }
559
+ case 'stopping':
560
+ case 'stopped': {
561
+ return Promise.resolve({
562
+ ok: false,
563
+ error: {
564
+ type: 'failure',
565
+ script: this._config,
566
+ reason: 'aborted',
567
+ },
568
+ });
569
+ }
570
+ case 'initial':
571
+ case 'executingDeps':
572
+ case 'stoppingAdoptee':
573
+ case 'fingerprinting':
574
+ case 'detached': {
575
+ throw unexpectedState(this.#state);
576
+ }
577
+ default: {
578
+ throw unknownState(this.#state);
579
+ }
580
+ }
581
+ }
582
+ #onDepsStarted() {
583
+ switch (this.#state.id) {
584
+ case 'depsStarting': {
585
+ const detached = this.#state.adoptee?.detach();
586
+ if (detached === undefined) {
587
+ const child = new ScriptChildProcess(this._config);
588
+ this.#state = {
589
+ id: 'starting',
590
+ child,
591
+ started: this.#state.started,
592
+ fingerprint: this.#state.fingerprint,
593
+ readyMonitor: this._config.service.readyWhen.lineMatches === undefined
594
+ ? undefined
595
+ : new LineMonitor(child, this._config.service.readyWhen.lineMatches),
596
+ };
597
+ void this.#state.child.started.then(() => {
598
+ this.#onChildStarted();
599
+ });
600
+ }
601
+ else {
602
+ this.#state.started.resolve({ ok: true, value: undefined });
603
+ this.#state = {
604
+ id: 'started',
605
+ child: detached.child,
606
+ fingerprint: this.#state.fingerprint,
607
+ };
608
+ }
609
+ void this.#state.child.completed.then(() => {
610
+ this.#onChildExited();
611
+ });
612
+ this.#startLoggingChildStdio(this.#state.child);
613
+ if (!this.#isWatchMode) {
614
+ // If we're in watch mode, we don't care about our dependency services
615
+ // exiting because:
616
+ //
617
+ // 1. If we're iteration N-1 which is about to be adopted into
618
+ // iteration N, our dependencies will sometimes intentionally
619
+ // restart. This should not cause us to fail, since we'll either
620
+ // also restart very shortly (when cascade is true), or we'll just
621
+ // keep running (when cascade is false).
622
+ //
623
+ // 2. If we're iteration N and our dependency unexpectedly exits by
624
+ // itself, it's not actually helpful if we also exit. In non-watch
625
+ // mode it's important because we want wireit itself to exit as
626
+ // soon as this happens, but not so in watch mode.
627
+ void this._anyServiceTerminated.then(() => {
628
+ this.#onDepServiceExit();
629
+ });
630
+ }
631
+ return;
632
+ }
633
+ case 'failed': {
634
+ return;
635
+ }
636
+ case 'initial':
637
+ case 'executingDeps':
638
+ case 'fingerprinting':
639
+ case 'stoppingAdoptee':
640
+ case 'unstarted':
641
+ case 'starting':
642
+ case 'readying':
643
+ case 'started':
644
+ case 'started-broken':
645
+ case 'stopping':
646
+ case 'stopped':
647
+ case 'failing':
648
+ case 'detached': {
649
+ throw unexpectedState(this.#state);
650
+ }
651
+ default: {
652
+ throw unknownState(this.#state);
653
+ }
654
+ }
655
+ }
656
+ #onDepStartErr(result) {
657
+ switch (this.#state.id) {
658
+ case 'depsStarting': {
659
+ // TODO(aomarks) The inconsistency between using single vs multiple
660
+ // failure result types is inconvenient. It's ok to just use the first
661
+ // one here, but would make more sense to return all of them.
662
+ const failure = result.error[0];
663
+ const detached = this.#state.adoptee?.detach();
664
+ if (detached !== undefined) {
665
+ this.#enterStartedBrokenState(failure, detached);
666
+ }
667
+ else {
668
+ this.#enterFailedState(failure);
669
+ }
670
+ return;
671
+ }
672
+ case 'failing':
673
+ case 'failed':
674
+ case 'stopping':
675
+ case 'stopped': {
676
+ return;
677
+ }
678
+ case 'initial':
679
+ case 'executingDeps':
680
+ case 'fingerprinting':
681
+ case 'stoppingAdoptee':
682
+ case 'unstarted':
683
+ case 'starting':
684
+ case 'readying':
685
+ case 'started':
686
+ case 'started-broken':
687
+ case 'detached': {
688
+ throw unexpectedState(this.#state);
689
+ }
690
+ default: {
691
+ throw unknownState(this.#state);
692
+ }
693
+ }
694
+ }
695
+ #onDepServiceExit() {
696
+ switch (this.#state.id) {
697
+ case 'started':
698
+ case 'started-broken': {
699
+ this.#state.child.kill();
700
+ this.#state = {
701
+ id: 'failing',
702
+ child: this.#state.child,
703
+ fingerprint: this.#state.fingerprint,
704
+ failure: {
705
+ type: 'failure',
706
+ script: this._config,
707
+ reason: 'dependency-service-exited-unexpectedly',
708
+ },
709
+ };
710
+ return;
711
+ }
712
+ case 'starting':
713
+ case 'readying': {
714
+ this.#state = {
715
+ id: 'failing',
716
+ child: this.#state.child,
717
+ fingerprint: this.#state.fingerprint,
718
+ failure: {
719
+ type: 'failure',
720
+ script: this._config,
721
+ reason: 'dependency-service-exited-unexpectedly',
722
+ },
723
+ };
724
+ return;
725
+ }
726
+ case 'stopped':
727
+ case 'stopping':
728
+ case 'failing':
729
+ case 'failed':
730
+ case 'detached': {
731
+ return;
732
+ }
733
+ case 'depsStarting':
734
+ case 'initial':
735
+ case 'executingDeps':
736
+ case 'fingerprinting':
737
+ case 'stoppingAdoptee':
738
+ case 'unstarted': {
739
+ throw unexpectedState(this.#state);
740
+ }
741
+ default: {
742
+ throw unknownState(this.#state);
743
+ }
744
+ }
745
+ }
746
+ #onChildStarted() {
747
+ switch (this.#state.id) {
748
+ case 'starting': {
749
+ this._logger.log({
750
+ script: this._config,
751
+ type: 'info',
752
+ detail: 'service-process-started',
753
+ });
754
+ if (this.#state.readyMonitor !== undefined) {
755
+ this.#state = {
756
+ id: 'readying',
757
+ child: this.#state.child,
758
+ fingerprint: this.#state.fingerprint,
759
+ started: this.#state.started,
760
+ readyMonitor: this.#state.readyMonitor,
761
+ };
762
+ void this.#state.readyMonitor.matched.then((result) => {
763
+ if (result.ok) {
764
+ this.#onChildReady();
765
+ }
766
+ // Otherwise the ready monitor aborted, so we don't care.
767
+ });
768
+ return;
769
+ }
770
+ this.#state.started.resolve({ ok: true, value: undefined });
771
+ this._logger.log({
772
+ script: this._config,
773
+ type: 'info',
774
+ detail: 'service-ready',
775
+ });
776
+ this.#state = {
777
+ id: 'started',
778
+ child: this.#state.child,
779
+ fingerprint: this.#state.fingerprint,
780
+ };
781
+ return;
782
+ }
783
+ case 'stopping':
784
+ case 'failing': {
785
+ this.#state.child.kill();
786
+ return;
787
+ }
788
+ case 'initial':
789
+ case 'executingDeps':
790
+ case 'fingerprinting':
791
+ case 'stoppingAdoptee':
792
+ case 'unstarted':
793
+ case 'depsStarting':
794
+ case 'readying':
795
+ case 'started':
796
+ case 'started-broken':
797
+ case 'stopped':
798
+ case 'failed':
799
+ case 'detached': {
800
+ throw unexpectedState(this.#state);
801
+ }
802
+ default: {
803
+ throw unknownState(this.#state);
804
+ }
805
+ }
806
+ }
807
+ #onChildReady() {
808
+ switch (this.#state.id) {
809
+ case 'readying': {
810
+ this.#state.started.resolve({ ok: true, value: undefined });
811
+ this._logger.log({
812
+ script: this._config,
813
+ type: 'info',
814
+ detail: 'service-ready',
815
+ });
816
+ this.#state = {
817
+ id: 'started',
818
+ child: this.#state.child,
819
+ fingerprint: this.#state.fingerprint,
820
+ };
821
+ return;
822
+ }
823
+ case 'starting':
824
+ case 'stopping':
825
+ case 'failing':
826
+ case 'initial':
827
+ case 'executingDeps':
828
+ case 'fingerprinting':
829
+ case 'stoppingAdoptee':
830
+ case 'unstarted':
831
+ case 'depsStarting':
832
+ case 'started':
833
+ case 'started-broken':
834
+ case 'stopped':
835
+ case 'failed':
836
+ case 'detached': {
837
+ throw unexpectedState(this.#state);
838
+ }
839
+ default: {
840
+ throw unknownState(this.#state);
841
+ }
842
+ }
843
+ }
844
+ #onChildExited() {
845
+ switch (this.#state.id) {
846
+ case 'stopping': {
847
+ this.#enterStoppedState();
848
+ return;
849
+ }
850
+ case 'readying': {
851
+ this.#state.readyMonitor.abort();
852
+ const event = {
853
+ script: this._config,
854
+ type: 'failure',
855
+ reason: 'service-exited-unexpectedly',
856
+ };
857
+ this._logger.log(event);
858
+ this.#enterFailedState(event);
859
+ return;
860
+ }
861
+ case 'started':
862
+ case 'started-broken': {
863
+ const event = {
864
+ script: this._config,
865
+ type: 'failure',
866
+ reason: 'service-exited-unexpectedly',
867
+ };
868
+ this._logger.log(event);
869
+ this.#enterFailedState(event);
870
+ return;
871
+ }
872
+ case 'failing': {
873
+ this.#enterFailedState(this.#state.failure);
874
+ return;
875
+ }
876
+ case 'failed':
877
+ case 'detached': {
878
+ return;
879
+ }
880
+ case 'initial':
881
+ case 'executingDeps':
882
+ case 'fingerprinting':
883
+ case 'stoppingAdoptee':
884
+ case 'unstarted':
885
+ case 'depsStarting':
886
+ case 'starting':
887
+ case 'stopped': {
888
+ throw unexpectedState(this.#state);
889
+ }
890
+ default: {
891
+ throw unknownState(this.#state);
892
+ }
893
+ }
894
+ }
895
+ /**
896
+ * Stop this service if it has started, and return a promise that resolves
897
+ * when it is stopped.
898
+ */
899
+ abort() {
900
+ switch (this.#state.id) {
901
+ case 'started':
902
+ case 'started-broken': {
903
+ this.#state.child.kill();
904
+ this.#state = {
905
+ id: 'stopping',
906
+ child: this.#state.child,
907
+ fingerprint: this.#state.fingerprint,
908
+ };
909
+ break;
910
+ }
911
+ case 'starting': {
912
+ this.#state.readyMonitor?.abort();
913
+ this.#state = {
914
+ id: 'stopping',
915
+ child: this.#state.child,
916
+ fingerprint: this.#state.fingerprint,
917
+ };
918
+ break;
919
+ }
920
+ case 'readying': {
921
+ this.#state.readyMonitor.abort();
922
+ this.#state.child.kill();
923
+ this.#state = {
924
+ id: 'stopping',
925
+ child: this.#state.child,
926
+ fingerprint: this.#state.fingerprint,
927
+ };
928
+ break;
929
+ }
930
+ case 'executingDeps':
931
+ case 'fingerprinting':
932
+ case 'stoppingAdoptee':
933
+ this.#state.deferredFingerprint.resolve({
934
+ ok: false,
935
+ error: [
936
+ {
937
+ type: 'failure',
938
+ script: this._config,
939
+ reason: 'aborted',
940
+ },
941
+ ],
942
+ });
943
+ this.#enterStoppedState();
944
+ break;
945
+ case 'initial':
946
+ case 'unstarted':
947
+ case 'depsStarting': {
948
+ this.#enterStoppedState();
949
+ break;
950
+ }
951
+ case 'stopping':
952
+ case 'stopped':
953
+ case 'failing':
954
+ case 'failed':
955
+ case 'detached': {
956
+ break;
957
+ }
958
+ default: {
959
+ throw unknownState(this.#state);
960
+ }
961
+ }
962
+ return this.#terminated.promise.then(() => undefined);
963
+ }
964
+ #enterStoppedState() {
965
+ this.#state = { id: 'stopped' };
966
+ this.#terminated.resolve({ ok: true, value: undefined });
967
+ this._servicesNotNeeded.resolve();
968
+ }
969
+ #enterFailedState(failure) {
970
+ this.#state = {
971
+ id: 'failed',
972
+ failure,
973
+ };
974
+ this._executor.notifyFailure();
975
+ this.#terminated.resolve({ ok: false, error: failure });
976
+ this._servicesNotNeeded.resolve();
977
+ }
978
+ #enterStartedBrokenState(failure, { child, fingerprint }) {
979
+ this.#startLoggingChildStdio(child);
980
+ void child.completed.then(() => {
981
+ this.#onChildExited();
982
+ });
983
+ this.#state = {
984
+ id: 'started-broken',
985
+ child,
986
+ fingerprint,
987
+ failure,
988
+ };
989
+ }
990
+ #startLoggingChildStdio(child) {
991
+ child.stdout.on('data', (data) => {
992
+ this._logger.log({
993
+ script: this._config,
994
+ type: 'output',
995
+ stream: 'stdout',
996
+ data,
997
+ });
998
+ });
999
+ child.stderr.on('data', (data) => {
1000
+ this._logger.log({
1001
+ script: this._config,
1002
+ type: 'output',
1003
+ stream: 'stderr',
1004
+ data,
1005
+ });
1006
+ });
1007
+ }
1008
+ #stopLoggingChildStdio(child) {
1009
+ // Note that for some reason, removing all listeners from stdout/stderr
1010
+ // without specifying the "data" event will also remove the listeners
1011
+ // directly on "child" inside the ScriptChildProceess for noticing when e.g.
1012
+ // the process has exited.
1013
+ child.stdout.removeAllListeners('data');
1014
+ child.stderr.removeAllListeners('data');
1015
+ }
1016
+ }
1017
+ //# sourceMappingURL=service.js.map