@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.
- package/LICENSE +202 -0
- package/README.md +1062 -0
- package/bin/wireit.js +9 -0
- package/lib/analyzer.js +1600 -0
- package/lib/caching/cache.js +7 -0
- package/lib/caching/github-actions-cache.js +832 -0
- package/lib/caching/local-cache.js +78 -0
- package/lib/caching/shared-cache.js +256 -0
- package/lib/cli-options.js +495 -0
- package/lib/cli.js +177 -0
- package/lib/config.js +18 -0
- package/lib/error.js +160 -0
- package/lib/event.js +7 -0
- package/lib/execution/base.js +108 -0
- package/lib/execution/no-command.js +32 -0
- package/lib/execution/service.js +1017 -0
- package/lib/execution/standard.js +683 -0
- package/lib/executor.js +249 -0
- package/lib/fingerprint.js +164 -0
- package/lib/ide.js +583 -0
- package/lib/language-server.js +135 -0
- package/lib/logging/combination-logger.js +41 -0
- package/lib/logging/debug-logger.js +43 -0
- package/lib/logging/logger.js +38 -0
- package/lib/logging/metrics-logger.js +108 -0
- package/lib/logging/quiet/run-tracker.js +597 -0
- package/lib/logging/quiet/stack-map.js +41 -0
- package/lib/logging/quiet/writeover-line.js +197 -0
- package/lib/logging/quiet-logger.js +78 -0
- package/lib/logging/simple-logger.js +296 -0
- package/lib/logging/watch-logger.js +81 -0
- package/lib/script-child-process.js +270 -0
- package/lib/util/ast.js +71 -0
- package/lib/util/async-cache.js +24 -0
- package/lib/util/copy.js +120 -0
- package/lib/util/deferred.js +35 -0
- package/lib/util/delete.js +120 -0
- package/lib/util/dispose.js +16 -0
- package/lib/util/fs.js +258 -0
- package/lib/util/glob.js +255 -0
- package/lib/util/line-monitor.js +69 -0
- package/lib/util/manifest.js +31 -0
- package/lib/util/optimize-mkdirs.js +55 -0
- package/lib/util/package-json-reader.js +61 -0
- package/lib/util/package-json.js +179 -0
- package/lib/util/script-data-dir.js +19 -0
- package/lib/util/shuffle.js +16 -0
- package/lib/util/unreachable.js +12 -0
- package/lib/util/windows.js +87 -0
- package/lib/util/worker-pool.js +61 -0
- package/lib/watcher.js +396 -0
- package/package.json +470 -0
- package/schema.json +132 -0
- 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
|