@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,597 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2023 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
7
+ if (value !== null && value !== void 0) {
8
+ if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
9
+ var dispose, inner;
10
+ if (async) {
11
+ if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
12
+ dispose = value[Symbol.asyncDispose];
13
+ }
14
+ if (dispose === void 0) {
15
+ if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
16
+ dispose = value[Symbol.dispose];
17
+ if (async) inner = dispose;
18
+ }
19
+ if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
20
+ if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
21
+ env.stack.push({ value: value, dispose: dispose, async: async });
22
+ }
23
+ else if (async) {
24
+ env.stack.push({ async: true });
25
+ }
26
+ return value;
27
+ };
28
+ var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
29
+ return function (env) {
30
+ function fail(e) {
31
+ env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
32
+ env.hasError = true;
33
+ }
34
+ var r, s = 0;
35
+ function next() {
36
+ while (r = env.stack.pop()) {
37
+ try {
38
+ if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
39
+ if (r.dispose) {
40
+ var result = r.dispose.call(r.value);
41
+ if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
42
+ }
43
+ else s |= 1;
44
+ }
45
+ catch (e) {
46
+ fail(e);
47
+ }
48
+ }
49
+ if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
50
+ if (env.hasError) throw env.error;
51
+ }
52
+ return next();
53
+ };
54
+ })(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
55
+ var e = new Error(message);
56
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
57
+ });
58
+ import { inspect } from 'util';
59
+ import { scriptReferenceToString, } from '../../config.js';
60
+ import { SimpleLogger, labelForScript } from '../simple-logger.js';
61
+ import { DEBUG } from '../logger.js';
62
+ import { StackMap } from './stack-map.js';
63
+ // To prevent using the global console accidentally, we shadow it with
64
+ // undefined
65
+ const console = undefined;
66
+ function markAsUsed(_) { }
67
+ markAsUsed(console);
68
+ /**
69
+ * State that the run tracker cares about for a currently running script.
70
+ */
71
+ class ScriptState {
72
+ #outputBuffer = [];
73
+ constructor(scriptReference, service, isRootScript) {
74
+ this.scriptReference = scriptReference;
75
+ this.service = service;
76
+ this.isRootScript = isRootScript;
77
+ }
78
+ bufferOutput(output) {
79
+ if (output.data.length === 0) {
80
+ return;
81
+ }
82
+ this.#outputBuffer.push({
83
+ stream: output.stream,
84
+ data: output.data,
85
+ });
86
+ }
87
+ replayAndEmptyBuffer() {
88
+ for (const output of this.#outputBuffer) {
89
+ if (output.stream === 'stdout') {
90
+ process.stdout.write(output.data);
91
+ }
92
+ else {
93
+ process.stderr.write(output.data);
94
+ }
95
+ }
96
+ this.#outputBuffer.length = 0;
97
+ }
98
+ get hasBufferedOutput() {
99
+ return this.#outputBuffer.length > 0;
100
+ }
101
+ }
102
+ export const noChange = Symbol('nochange');
103
+ export const nothing = Symbol('nothing');
104
+ /**
105
+ * Tracks the state of a run, and produces a single line of status text.
106
+ *
107
+ * A QuietLogger usually just has one of these, but in --watch mode we use
108
+ * one per iteration.
109
+ */
110
+ export class QuietRunLogger {
111
+ /**
112
+ * Currently running scripts, or failed scripts that we're about to
113
+ * report on. A script is added to this when it starts, and removed
114
+ * only when it successfully exits.
115
+ *
116
+ * Keyed by this._getKey
117
+ */
118
+ #running = new StackMap();
119
+ /** The number of commands we've started. */
120
+ #ran = 0;
121
+ /**
122
+ * The number of scripts with commands that were either fresh or whose output
123
+ * we restored.
124
+ */
125
+ #skipped = 0;
126
+ #encounteredFailures = false;
127
+ #servicesRunning = 0;
128
+ #servicesStarted = 0;
129
+ #analysisInfo = undefined;
130
+ #finishedScriptsWithCommands = new Set();
131
+ #statusLineState = 'initial';
132
+ #startTime = Date.now();
133
+ #rootPackage;
134
+ #simpleLogger;
135
+ #statusLineWriter;
136
+ /**
137
+ * Sometimes a script will fail multiple times, but we only want to report
138
+ * about the first failure for it that we find. Sometimes a script will
139
+ * even end up with multiple failures for the same reason, like multiple
140
+ * non-zero-exit-code events. This looks to be coming at the NodeJS level
141
+ * or above, and so we just cope.
142
+ */
143
+ #scriptsWithAlreadyReportedError = new Set();
144
+ constructor(rootPackage, statusLineWriter, console, simpleLogger) {
145
+ this.#rootPackage = rootPackage;
146
+ this.#statusLineWriter = statusLineWriter;
147
+ this.#simpleLogger = simpleLogger ?? new SimpleLogger(rootPackage, console);
148
+ this.console = console;
149
+ }
150
+ /**
151
+ * Used to make a new instance, keeping info about persistent services
152
+ * that continue from the previous run to the next.
153
+ */
154
+ makeInstanceForNextWatchRun() {
155
+ // Reuse the default logger, a minor savings.
156
+ const instance = new QuietRunLogger(this.#rootPackage, this.#statusLineWriter, this.console, this.#simpleLogger);
157
+ // Persistent services stay running between runs, so pass along what we
158
+ // know.
159
+ for (const [key, state] of this.#running) {
160
+ if (state.service) {
161
+ instance.#servicesRunning++;
162
+ instance.#running.set(key, state);
163
+ instance.#markScriptAsFinished(state.scriptReference);
164
+ }
165
+ }
166
+ return instance;
167
+ }
168
+ /**
169
+ * Takes an Event and updates our summary stats for this run.
170
+ *
171
+ * If it returns undefined, the previous message is still good. If it returns
172
+ * null, the message should be cleared entirely.
173
+ */
174
+ getUpdatedMessageAfterEvent(event) {
175
+ switch (event.type) {
176
+ case 'success': {
177
+ return this.#handleSuccess(event);
178
+ }
179
+ case 'failure': {
180
+ return this.#handleFailure(event);
181
+ }
182
+ case 'info': {
183
+ return this.#handleInfo(event);
184
+ }
185
+ case 'output': {
186
+ return this.#handleOutput(event);
187
+ }
188
+ default: {
189
+ const never = event;
190
+ throw new Error(`Unknown event type: ${JSON.stringify(never)}`);
191
+ }
192
+ }
193
+ }
194
+ /**
195
+ * Should be called once, at the end of a run.
196
+ */
197
+ printSummary() {
198
+ this.#statusLineState = 'done';
199
+ let scriptsStillRunning = false;
200
+ for (const state of this.#running.values()) {
201
+ if (state.service) {
202
+ continue;
203
+ }
204
+ scriptsStillRunning = true;
205
+ }
206
+ if (this.#encounteredFailures || scriptsStillRunning) {
207
+ this.#printFailureSummary();
208
+ return;
209
+ }
210
+ this.#printSuccessSummary();
211
+ }
212
+ #printFailureSummary() {
213
+ for (const [, state] of this.#running) {
214
+ const label = labelForScript(this.#rootPackage, state.scriptReference);
215
+ const key = scriptReferenceToString(state.scriptReference);
216
+ if (this.#scriptsWithAlreadyReportedError.has(key) || state.service) {
217
+ continue;
218
+ }
219
+ // We expected this to finish running, but it didn't. We haven't seen
220
+ // this in our testing, so it's as much of a test of our own code as
221
+ // anything else.
222
+ process.stderr.write(`\n❌ [${label}] did not exit successfully.`);
223
+ this.#reportOutputForFailingScript(state.scriptReference);
224
+ }
225
+ if (this.#scriptsWithAlreadyReportedError.size > 0) {
226
+ const s = this.#scriptsWithAlreadyReportedError.size === 1 ? '' : 's';
227
+ process.stderr.write(`❌ ${this.#scriptsWithAlreadyReportedError.size.toLocaleString()} script${s} failed.\n`);
228
+ }
229
+ else {
230
+ process.stderr.write(`❌ Failed.\n`);
231
+ }
232
+ }
233
+ #printSuccessSummary() {
234
+ const elapsed = Math.round((Date.now() - this.#startTime) / 100) / 10;
235
+ // In watch mode, we want to count services that we started as part of this
236
+ // run.
237
+ const count = this.#ran + this.#servicesStarted;
238
+ const s = count === 1 ? '' : 's';
239
+ this.console.log(`✅ Ran ${count.toLocaleString()} script${s} and skipped ${this.#skipped.toLocaleString()} in ${elapsed.toLocaleString()}s.`);
240
+ }
241
+ #reportOutputForFailingScript(script, cause) {
242
+ const state = this.#running.get(scriptReferenceToString(script));
243
+ if (!state) {
244
+ throw new Error(`Internal error: Got ${cause?.reason ? `${cause.reason} event` : 'leftover script'} for script without a start event. Events delivered out of order?
245
+ Script with failure: ${scriptReferenceToString(script)}
246
+ Known scripts: ${inspect([...this.#running.keys()])}
247
+ `);
248
+ }
249
+ state.replayAndEmptyBuffer();
250
+ }
251
+ #getStatusLine() {
252
+ switch (this.#statusLineState) {
253
+ case 'initial': {
254
+ return 'Starting';
255
+ }
256
+ case 'analyzing': {
257
+ return 'Analyzing';
258
+ }
259
+ case 'executing': {
260
+ if (this.#analysisInfo === undefined) {
261
+ return `??? Internal error: Analysis info missing ???`;
262
+ }
263
+ return this.#getExecutionStatusLine(this.#analysisInfo);
264
+ }
265
+ case 'done': {
266
+ // No status line now.
267
+ return nothing;
268
+ }
269
+ default: {
270
+ const never = this.#statusLineState;
271
+ throw new Error(`Unknown status line state: ${JSON.stringify(never)}`);
272
+ }
273
+ }
274
+ }
275
+ #getExecutionStatusLine(analysisInfo) {
276
+ const peekResult = this.#running.peek()?.[1];
277
+ let mostRecentScript = '';
278
+ if (peekResult !== undefined) {
279
+ mostRecentScript =
280
+ ' ' + labelForScript(this.#rootPackage, peekResult.scriptReference);
281
+ }
282
+ const done = this.#finishedScriptsWithCommands.size;
283
+ const total = analysisInfo.scriptsWithCommands.size;
284
+ const percentDone = String(Math.round((done / total) * 100)).padStart(3, ' ') + '%';
285
+ let servicesInfo = '';
286
+ if (analysisInfo.hasServices) {
287
+ const s = this.#servicesRunning === 1 ? '' : 's';
288
+ servicesInfo = ` [${this.#servicesRunning.toLocaleString()} service${s}]`;
289
+ }
290
+ let failureInfo = '';
291
+ if (this.#scriptsWithAlreadyReportedError.size > 0) {
292
+ failureInfo = ` [${this.#scriptsWithAlreadyReportedError.size.toLocaleString()} failed]`;
293
+ }
294
+ return `${percentDone} [${done.toLocaleString()} / ${total}] [${this.#running.size} running]${servicesInfo}${failureInfo}${mostRecentScript}`;
295
+ }
296
+ #markScriptAsFinished(script) {
297
+ const key = scriptReferenceToString(script);
298
+ // Almost always, a script that's finished will be one that we intended
299
+ // to execute as part of this run. However there's one exception:
300
+ // if a persistent service was started as part of the previous run, but then
301
+ // we change the package.json files so that we no longer need to run it,
302
+ // it will be shut down as part of _our_ run. In that case, we don't want
303
+ // to count it towards our total.
304
+ const isScriptOfInterest =
305
+ // Optimistically mark it as finished if we don't have analysis info yet.
306
+ // We'll remove it later if we find out it's not actually a script we
307
+ // care about.
308
+ this.#analysisInfo === undefined ||
309
+ this.#analysisInfo.scriptsWithCommands.has(key);
310
+ if (isScriptOfInterest) {
311
+ this.#finishedScriptsWithCommands.add(key);
312
+ }
313
+ }
314
+ #handleInfo(event) {
315
+ if (DEBUG) {
316
+ this.console.log(`info: ${event.detail} ${labelForScript(this.#rootPackage, event.script)}`);
317
+ }
318
+ switch (event.detail) {
319
+ case 'running': {
320
+ const key = scriptReferenceToString(event.script);
321
+ this.#running.set(key, new ScriptState(event.script, false, this.#analysisInfo?.rootScript === key));
322
+ return this.#getStatusLine();
323
+ }
324
+ case 'service-process-started': {
325
+ // Services don't end, so we count this as having finished.
326
+ this.#servicesRunning++;
327
+ this.#servicesStarted++;
328
+ const key = scriptReferenceToString(event.script);
329
+ this.#running.set(key, new ScriptState(event.script, true, this.#analysisInfo?.rootScript === key));
330
+ this.#markScriptAsFinished(event.script);
331
+ return this.#getStatusLine();
332
+ }
333
+ case 'service-stopped':
334
+ this.#servicesRunning--;
335
+ this.#running.delete(scriptReferenceToString(event.script));
336
+ return this.#getStatusLine();
337
+ case 'service-ready':
338
+ case 'watch-run-start':
339
+ case 'watch-run-end':
340
+ return noChange;
341
+ case 'locked': {
342
+ // the script is blocked on starting because something else is
343
+ // using a shared resource
344
+ return noChange;
345
+ }
346
+ case 'output-modified': {
347
+ // the script is being run because its output files have changed
348
+ // log nothing
349
+ return noChange;
350
+ }
351
+ case 'cache-info': {
352
+ // chatty event, e.g. transitory GitHub API errors.
353
+ // definitely don't log anything here
354
+ return noChange;
355
+ }
356
+ case 'analysis-started': {
357
+ this.#statusLineState = 'analyzing';
358
+ return this.#getStatusLine();
359
+ }
360
+ case 'analysis-completed': {
361
+ if (!event.rootScriptConfig) {
362
+ // will report the error in printSummary
363
+ this.#statusLineState = 'done';
364
+ return nothing;
365
+ }
366
+ else {
367
+ this.#statusLineState = 'executing';
368
+ this.#analysisInfo = this.#countScriptsWithCommands(event.rootScriptConfig);
369
+ for (const finished of this.#finishedScriptsWithCommands) {
370
+ if (!this.#analysisInfo.scriptsWithCommands.has(finished)) {
371
+ this.#finishedScriptsWithCommands.delete(finished);
372
+ }
373
+ }
374
+ }
375
+ return this.#getStatusLine();
376
+ }
377
+ default: {
378
+ const never = event;
379
+ throw new Error(`Unknown info event: ${JSON.stringify(never)}`);
380
+ }
381
+ }
382
+ }
383
+ #handleSuccess(event) {
384
+ if (DEBUG) {
385
+ this.console.log(`success: ${event.reason} ${labelForScript(this.#rootPackage, event.script)}`);
386
+ }
387
+ switch (event.reason) {
388
+ case 'cached': {
389
+ this.#markScriptAsFinished(event.script);
390
+ this.#skipped++;
391
+ return this.#getStatusLine();
392
+ }
393
+ case 'fresh': {
394
+ this.#markScriptAsFinished(event.script);
395
+ this.#skipped++;
396
+ return this.#getStatusLine();
397
+ }
398
+ case 'exit-zero': {
399
+ this.#running.delete(scriptReferenceToString(event.script));
400
+ this.#markScriptAsFinished(event.script);
401
+ this.#ran++;
402
+ return this.#getStatusLine();
403
+ }
404
+ case 'no-command': {
405
+ return noChange;
406
+ }
407
+ default: {
408
+ const never = event;
409
+ throw new Error(`Unknown success event: ${JSON.stringify(never)}`);
410
+ }
411
+ }
412
+ }
413
+ #handleFailure(event) {
414
+ if (DEBUG) {
415
+ this.console.log(`failure: ${event.reason} ${labelForScript(this.#rootPackage, event.script)}`);
416
+ }
417
+ this.#encounteredFailures = true;
418
+ {
419
+ const env_1 = { stack: [], error: void 0, hasError: false };
420
+ try {
421
+ const _pause = __addDisposableResource(env_1, this.#statusLineWriter.clearUntilDisposed(), false);
422
+ this.#reportFailure(event);
423
+ }
424
+ catch (e_1) {
425
+ env_1.error = e_1;
426
+ env_1.hasError = true;
427
+ }
428
+ finally {
429
+ __disposeResources(env_1);
430
+ }
431
+ }
432
+ return noChange;
433
+ }
434
+ #reportFailure(failure) {
435
+ const key = scriptReferenceToString(failure.script);
436
+ if (this.#scriptsWithAlreadyReportedError.has(key)) {
437
+ return;
438
+ }
439
+ this.#scriptsWithAlreadyReportedError.add(key);
440
+ const label = labelForScript(this.#rootPackage, failure.script);
441
+ switch (failure.reason) {
442
+ case 'exit-non-zero':
443
+ case 'signal':
444
+ case 'killed': {
445
+ let message;
446
+ if (failure.reason === 'exit-non-zero') {
447
+ message = `exited with exit code ${failure.status}`;
448
+ }
449
+ else if (failure.reason === 'signal') {
450
+ message = `was killed by signal ${failure.signal}`;
451
+ }
452
+ else {
453
+ message = `killed`;
454
+ }
455
+ const scriptHadOutput = this.#scriptHadOutput(failure.script);
456
+ const trailer = scriptHadOutput ? ' Output:\n' : '';
457
+ process.stderr.write(`\n❌ [${label}] ${message}.${trailer}\n`);
458
+ if (scriptHadOutput) {
459
+ this.#reportOutputForFailingScript(failure.script, failure);
460
+ }
461
+ this.#finishedScriptsWithCommands.add(key);
462
+ this.#running.delete(key);
463
+ return this.#getStatusLine();
464
+ }
465
+ case 'start-cancelled':
466
+ case 'aborted': {
467
+ // These events aren't very useful to log, because they are downstream
468
+ // of failures that already get reported elsewhere.
469
+ this.#running.delete(scriptReferenceToString(failure.script));
470
+ return this.#getStatusLine();
471
+ }
472
+ case 'dependency-service-exited-unexpectedly': {
473
+ // Also logged elswhere.
474
+ break;
475
+ }
476
+ case 'input-file-deleted-unexpectedly':
477
+ case 'output-file-deleted-unexpectedly':
478
+ case 'service-exited-unexpectedly':
479
+ case 'cycle':
480
+ case 'dependency-invalid':
481
+ case 'dependency-on-missing-package-json':
482
+ case 'dependency-on-missing-script':
483
+ case 'duplicate-dependency':
484
+ case 'failed-previous-watch-iteration':
485
+ case 'invalid-config-syntax':
486
+ case 'invalid-json-syntax':
487
+ case 'invalid-usage':
488
+ case 'launched-incorrectly':
489
+ case 'missing-package-json':
490
+ case 'no-scripts-in-package-json':
491
+ case 'script-not-found':
492
+ case 'script-not-wireit':
493
+ case 'spawn-error':
494
+ case 'unknown-error-thrown':
495
+ case 'wireit-config-but-no-script':
496
+ // The default log for these is good.
497
+ this.#simpleLogger.log(failure);
498
+ break;
499
+ default: {
500
+ const never = failure;
501
+ throw new Error(`Unknown failure event: ${JSON.stringify(never)}`);
502
+ }
503
+ }
504
+ }
505
+ #scriptHadOutput(script) {
506
+ const state = this.#running.get(scriptReferenceToString(script));
507
+ if (!state) {
508
+ throw new Error(`Internal error: could not find state for failing script. Events delivered out of order?
509
+ Script with output: ${labelForScript(this.#rootPackage, script)}
510
+ ${this.#running.size.toLocaleString()} known running scripts: ${inspect([...this.#running.keys()])}`);
511
+ }
512
+ return state.hasBufferedOutput;
513
+ }
514
+ #handleOutput(event) {
515
+ const key = scriptReferenceToString(event.script);
516
+ const state = this.#running.get(key);
517
+ if (!state) {
518
+ throw new Error(`Internal error: Got output event for unknown script. Events delivered out of order?
519
+ Script with output: ${labelForScript(this.#rootPackage, event.script)}
520
+ ${this.#running.size.toLocaleString()} known running scripts: ${inspect([...this.#running.keys()])}`);
521
+ }
522
+ if (state.service) {
523
+ const env_2 = { stack: [], error: void 0, hasError: false };
524
+ try {
525
+ // Pause the status line while we print this real quick, but then resume
526
+ // it.
527
+ const _pause = __addDisposableResource(env_2, this.#statusLineWriter.clearUntilDisposed(), false);
528
+ if (event.stream === 'stdout') {
529
+ process.stdout.write(event.data);
530
+ }
531
+ else {
532
+ process.stderr.write(event.data);
533
+ }
534
+ return noChange;
535
+ }
536
+ catch (e_2) {
537
+ env_2.error = e_2;
538
+ env_2.hasError = true;
539
+ }
540
+ finally {
541
+ __disposeResources(env_2);
542
+ }
543
+ }
544
+ if (state.isRootScript) {
545
+ this.#statusLineState = 'done';
546
+ // Unlike for a service, this is a terminal state, instead of pausing the
547
+ // status line, we stop it completely, because for the rest of the run
548
+ // we're just going to be printing the root script's output.
549
+ this.#statusLineWriter.clearAndStopRendering();
550
+ if (event.stream === 'stdout') {
551
+ process.stdout.write(event.data);
552
+ }
553
+ else {
554
+ process.stderr.write(event.data);
555
+ }
556
+ return nothing;
557
+ }
558
+ // Buffer everything else so that we can print it
559
+ // (possibly a second time) in case of failure.
560
+ state.bufferOutput(event);
561
+ return noChange;
562
+ }
563
+ #countScriptsWithCommands(rootScript) {
564
+ const scriptsWithCommands = new Set();
565
+ let hasServices = false;
566
+ const seen = new Set([scriptReferenceToString(rootScript)]);
567
+ const toVisit = [rootScript];
568
+ while (toVisit.length > 0) {
569
+ const script = toVisit.pop();
570
+ if (script.service) {
571
+ hasServices = true;
572
+ }
573
+ if (script.command !== undefined) {
574
+ // We only want to count scripts that actually run, rather than
575
+ // just holding dependencies.
576
+ scriptsWithCommands.add(scriptReferenceToString(script));
577
+ }
578
+ for (const dependency of script.dependencies.values()) {
579
+ const key = scriptReferenceToString(dependency.config);
580
+ if (seen.has(key)) {
581
+ continue;
582
+ }
583
+ seen.add(key);
584
+ toVisit.push(dependency.config);
585
+ }
586
+ }
587
+ return {
588
+ rootScript: scriptReferenceToString(rootScript),
589
+ scriptsWithCommands,
590
+ hasServices,
591
+ };
592
+ }
593
+ [Symbol.dispose]() {
594
+ this.#simpleLogger[Symbol.dispose]();
595
+ }
596
+ }
597
+ //# sourceMappingURL=run-tracker.js.map
@@ -0,0 +1,41 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2023 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ // To prevent using the global console accidentally, we shadow it with
7
+ // undefined
8
+ const console = undefined;
9
+ function markAsUsed(_) { }
10
+ markAsUsed(console);
11
+ /**
12
+ * A map that can also efficiently return the most recently added entry.
13
+ */
14
+ export class StackMap extends Map {
15
+ #stack = [];
16
+ set(key, value) {
17
+ if (!this.has(key)) {
18
+ this.#stack.push([key, value]);
19
+ }
20
+ return super.set(key, value);
21
+ }
22
+ // Surprisingly, we don't need to override delete, because we expect peek()
23
+ // to be called frequently, and it will remove any trailing deleted entries.
24
+ /**
25
+ * Returns the most recently added entry that's still in the map, or
26
+ * undefined if the map is empty.
27
+ */
28
+ peek() {
29
+ while (true) {
30
+ const last = this.#stack[this.#stack.length - 1];
31
+ if (!last) {
32
+ return;
33
+ }
34
+ if (this.has(last[0])) {
35
+ return last;
36
+ }
37
+ this.#stack.pop();
38
+ }
39
+ }
40
+ }
41
+ //# sourceMappingURL=stack-map.js.map