@rushstack/operation-graph 0.5.5 → 0.6.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 (76) hide show
  1. package/CHANGELOG.json +44 -0
  2. package/CHANGELOG.md +18 -1
  3. package/dist/tsdoc-metadata.json +1 -1
  4. package/lib-esm/IOperationRunner.js +4 -0
  5. package/lib-esm/IOperationRunner.js.map +1 -0
  6. package/lib-esm/Operation.js +218 -0
  7. package/lib-esm/Operation.js.map +1 -0
  8. package/lib-esm/OperationError.js +25 -0
  9. package/lib-esm/OperationError.js.map +1 -0
  10. package/lib-esm/OperationExecutionManager.js +138 -0
  11. package/lib-esm/OperationExecutionManager.js.map +1 -0
  12. package/lib-esm/OperationGroupRecord.js +62 -0
  13. package/lib-esm/OperationGroupRecord.js.map +1 -0
  14. package/lib-esm/OperationStatus.js +42 -0
  15. package/lib-esm/OperationStatus.js.map +1 -0
  16. package/lib-esm/Stopwatch.js +93 -0
  17. package/lib-esm/Stopwatch.js.map +1 -0
  18. package/lib-esm/WatchLoop.js +210 -0
  19. package/lib-esm/WatchLoop.js.map +1 -0
  20. package/lib-esm/WorkQueue.js +49 -0
  21. package/lib-esm/WorkQueue.js.map +1 -0
  22. package/lib-esm/calculateCriticalPath.js +83 -0
  23. package/lib-esm/calculateCriticalPath.js.map +1 -0
  24. package/lib-esm/index.js +10 -0
  25. package/lib-esm/index.js.map +1 -0
  26. package/lib-esm/protocol.types.js +4 -0
  27. package/lib-esm/protocol.types.js.map +1 -0
  28. package/package.json +28 -6
  29. /package/{lib → lib-commonjs}/IOperationRunner.js +0 -0
  30. /package/{lib → lib-commonjs}/IOperationRunner.js.map +0 -0
  31. /package/{lib → lib-commonjs}/Operation.js +0 -0
  32. /package/{lib → lib-commonjs}/Operation.js.map +0 -0
  33. /package/{lib → lib-commonjs}/OperationError.js +0 -0
  34. /package/{lib → lib-commonjs}/OperationError.js.map +0 -0
  35. /package/{lib → lib-commonjs}/OperationExecutionManager.js +0 -0
  36. /package/{lib → lib-commonjs}/OperationExecutionManager.js.map +0 -0
  37. /package/{lib → lib-commonjs}/OperationGroupRecord.js +0 -0
  38. /package/{lib → lib-commonjs}/OperationGroupRecord.js.map +0 -0
  39. /package/{lib → lib-commonjs}/OperationStatus.js +0 -0
  40. /package/{lib → lib-commonjs}/OperationStatus.js.map +0 -0
  41. /package/{lib → lib-commonjs}/Stopwatch.js +0 -0
  42. /package/{lib → lib-commonjs}/Stopwatch.js.map +0 -0
  43. /package/{lib → lib-commonjs}/WatchLoop.js +0 -0
  44. /package/{lib → lib-commonjs}/WatchLoop.js.map +0 -0
  45. /package/{lib → lib-commonjs}/WorkQueue.js +0 -0
  46. /package/{lib → lib-commonjs}/WorkQueue.js.map +0 -0
  47. /package/{lib → lib-commonjs}/calculateCriticalPath.js +0 -0
  48. /package/{lib → lib-commonjs}/calculateCriticalPath.js.map +0 -0
  49. /package/{lib → lib-commonjs}/index.js +0 -0
  50. /package/{lib → lib-commonjs}/index.js.map +0 -0
  51. /package/{lib → lib-commonjs}/protocol.types.js +0 -0
  52. /package/{lib → lib-commonjs}/protocol.types.js.map +0 -0
  53. /package/{lib → lib-dts}/IOperationRunner.d.ts +0 -0
  54. /package/{lib → lib-dts}/IOperationRunner.d.ts.map +0 -0
  55. /package/{lib → lib-dts}/Operation.d.ts +0 -0
  56. /package/{lib → lib-dts}/Operation.d.ts.map +0 -0
  57. /package/{lib → lib-dts}/OperationError.d.ts +0 -0
  58. /package/{lib → lib-dts}/OperationError.d.ts.map +0 -0
  59. /package/{lib → lib-dts}/OperationExecutionManager.d.ts +0 -0
  60. /package/{lib → lib-dts}/OperationExecutionManager.d.ts.map +0 -0
  61. /package/{lib → lib-dts}/OperationGroupRecord.d.ts +0 -0
  62. /package/{lib → lib-dts}/OperationGroupRecord.d.ts.map +0 -0
  63. /package/{lib → lib-dts}/OperationStatus.d.ts +0 -0
  64. /package/{lib → lib-dts}/OperationStatus.d.ts.map +0 -0
  65. /package/{lib → lib-dts}/Stopwatch.d.ts +0 -0
  66. /package/{lib → lib-dts}/Stopwatch.d.ts.map +0 -0
  67. /package/{lib → lib-dts}/WatchLoop.d.ts +0 -0
  68. /package/{lib → lib-dts}/WatchLoop.d.ts.map +0 -0
  69. /package/{lib → lib-dts}/WorkQueue.d.ts +0 -0
  70. /package/{lib → lib-dts}/WorkQueue.d.ts.map +0 -0
  71. /package/{lib → lib-dts}/calculateCriticalPath.d.ts +0 -0
  72. /package/{lib → lib-dts}/calculateCriticalPath.d.ts.map +0 -0
  73. /package/{lib → lib-dts}/index.d.ts +0 -0
  74. /package/{lib → lib-dts}/index.d.ts.map +0 -0
  75. /package/{lib → lib-dts}/protocol.types.d.ts +0 -0
  76. /package/{lib → lib-dts}/protocol.types.d.ts.map +0 -0
package/CHANGELOG.json CHANGED
@@ -1,6 +1,50 @@
1
1
  {
2
2
  "name": "@rushstack/operation-graph",
3
3
  "entries": [
4
+ {
5
+ "version": "0.6.0",
6
+ "tag": "@rushstack/operation-graph_v0.6.0",
7
+ "date": "Thu, 19 Feb 2026 00:04:53 GMT",
8
+ "comments": {
9
+ "minor": [
10
+ {
11
+ "comment": "Normalize package layout. CommonJS is now under `lib-commonjs`, DTS is now under `lib-dts`, and ESM is now under `lib-esm`. Imports to `lib` still work as before, handled by the `\"exports\"` field in `package.json`."
12
+ }
13
+ ],
14
+ "dependency": [
15
+ {
16
+ "comment": "Updating dependency \"@rushstack/node-core-library\" to `5.20.0`"
17
+ },
18
+ {
19
+ "comment": "Updating dependency \"@rushstack/terminal\" to `0.22.0`"
20
+ }
21
+ ]
22
+ }
23
+ },
24
+ {
25
+ "version": "0.5.7",
26
+ "tag": "@rushstack/operation-graph_v0.5.7",
27
+ "date": "Wed, 07 Jan 2026 01:12:24 GMT",
28
+ "comments": {
29
+ "dependency": [
30
+ {
31
+ "comment": "Updating dependency \"@rushstack/terminal\" to `0.21.0`"
32
+ }
33
+ ]
34
+ }
35
+ },
36
+ {
37
+ "version": "0.5.6",
38
+ "tag": "@rushstack/operation-graph_v0.5.6",
39
+ "date": "Mon, 05 Jan 2026 16:12:49 GMT",
40
+ "comments": {
41
+ "dependency": [
42
+ {
43
+ "comment": "Updating dependency \"@rushstack/terminal\" to `0.20.0`"
44
+ }
45
+ ]
46
+ }
47
+ },
4
48
  {
5
49
  "version": "0.5.5",
6
50
  "tag": "@rushstack/operation-graph_v0.5.5",
package/CHANGELOG.md CHANGED
@@ -1,6 +1,23 @@
1
1
  # Change Log - @rushstack/operation-graph
2
2
 
3
- This log was last generated on Sat, 06 Dec 2025 01:12:29 GMT and should not be manually modified.
3
+ This log was last generated on Thu, 19 Feb 2026 00:04:53 GMT and should not be manually modified.
4
+
5
+ ## 0.6.0
6
+ Thu, 19 Feb 2026 00:04:53 GMT
7
+
8
+ ### Minor changes
9
+
10
+ - Normalize package layout. CommonJS is now under `lib-commonjs`, DTS is now under `lib-dts`, and ESM is now under `lib-esm`. Imports to `lib` still work as before, handled by the `"exports"` field in `package.json`.
11
+
12
+ ## 0.5.7
13
+ Wed, 07 Jan 2026 01:12:24 GMT
14
+
15
+ _Version update only_
16
+
17
+ ## 0.5.6
18
+ Mon, 05 Jan 2026 16:12:49 GMT
19
+
20
+ _Version update only_
4
21
 
5
22
  ## 0.5.5
6
23
  Sat, 06 Dec 2025 01:12:29 GMT
@@ -5,7 +5,7 @@
5
5
  "toolPackages": [
6
6
  {
7
7
  "packageName": "@microsoft/api-extractor",
8
- "packageVersion": "7.54.0"
8
+ "packageVersion": "7.55.2"
9
9
  }
10
10
  ]
11
11
  }
@@ -0,0 +1,4 @@
1
+ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2
+ // See LICENSE in the project root for license information.
3
+ export {};
4
+ //# sourceMappingURL=IOperationRunner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IOperationRunner.js","sourceRoot":"","sources":["../src/IOperationRunner.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,2DAA2D","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport type { OperationStatus } from './OperationStatus';\nimport type { OperationError } from './OperationError';\nimport type { Stopwatch } from './Stopwatch';\n\n/**\n * Information passed to the executing `IOperationRunner`\n *\n * @beta\n */\nexport interface IOperationRunnerContext {\n /**\n * An abort signal for the overarching execution. Runners should do their best to gracefully abort\n * as soon as possible if the signal is aborted.\n */\n abortSignal: AbortSignal;\n\n /**\n * If this is the first time this operation has been executed.\n */\n isFirstRun: boolean;\n\n /**\n * A callback to the overarching orchestrator to request that the operation be invoked again.\n * Used in watch mode to signal that inputs have changed.\n *\n * @param detail - Optional detail about why the rerun is requested, e.g. the name of a changed file.\n */\n requestRun?: (detail?: string) => void;\n}\n\n/**\n * Interface contract for a single state of an operation.\n *\n * @beta\n */\nexport interface IOperationState {\n /**\n * The status code for the operation.\n */\n status: OperationStatus;\n /**\n * Whether the operation has been run at least once.\n */\n hasBeenRun: boolean;\n /**\n * The error, if the status is `OperationStatus.Failure`.\n */\n error: OperationError | undefined;\n /**\n * Timing information for the operation.\n */\n stopwatch: Stopwatch;\n}\n\n/**\n * Interface contract for the current and past state of an operation.\n *\n * @beta\n */\nexport interface IOperationStates {\n /**\n * The current state of the operation.\n */\n readonly state: Readonly<IOperationState> | undefined;\n /**\n * The previous state of the operation.\n */\n readonly lastState: Readonly<IOperationState> | undefined;\n}\n\n/**\n * The `Operation` class is a node in the dependency graph of work that needs to be scheduled by the\n * `OperationExecutionManager`. Each `Operation` has a `runner` member of type `IOperationRunner`, whose\n * implementation manages the actual process for running a single operation.\n *\n * @beta\n */\nexport interface IOperationRunner {\n /**\n * Name of the operation, for logging.\n */\n readonly name: string;\n\n /**\n * Indicates that this runner is architectural and should not be reported on.\n */\n silent: boolean;\n\n /**\n * Method to be executed for the operation.\n */\n executeAsync(context: IOperationRunnerContext): Promise<OperationStatus>;\n}\n"]}
@@ -0,0 +1,218 @@
1
+ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2
+ // See LICENSE in the project root for license information.
3
+ import { InternalError } from '@rushstack/node-core-library';
4
+ import { Stopwatch } from './Stopwatch';
5
+ import { OperationStatus } from './OperationStatus';
6
+ /**
7
+ * The `Operation` class is a node in the dependency graph of work that needs to be scheduled by the
8
+ * `OperationExecutionManager`. Each `Operation` has a `runner` member of type `IOperationRunner`, whose
9
+ * implementation manages the actual process of running a single operation.
10
+ *
11
+ * The graph of `Operation` instances will be cloned into a separate execution graph after processing.
12
+ *
13
+ * @beta
14
+ */
15
+ export class Operation {
16
+ constructor(options) {
17
+ /**
18
+ * A set of all dependencies which must be executed before this operation is complete.
19
+ */
20
+ this.dependencies = new Set();
21
+ /**
22
+ * A set of all operations that wait for this operation.
23
+ */
24
+ this.consumers = new Set();
25
+ /**
26
+ * When the scheduler is ready to process this `Operation`, the `runner` implements the actual work of
27
+ * running the operation.
28
+ */
29
+ this.runner = undefined;
30
+ /**
31
+ * This number represents how far away this Operation is from the furthest "root" operation (i.e.
32
+ * an operation with no consumers). This helps us to calculate the critical path (i.e. the
33
+ * longest chain of projects which must be executed in order, thereby limiting execution speed
34
+ * of the entire operation tree.
35
+ *
36
+ * This number is calculated via a memoized depth-first search, and when choosing the next
37
+ * operation to execute, the operation with the highest criticalPathLength is chosen.
38
+ *
39
+ * Example:
40
+ * (0) A
41
+ * \\
42
+ * (1) B C (0) (applications)
43
+ * \\ /|\\
44
+ * \\ / | \\
45
+ * (2) D | X (1) (utilities)
46
+ * | / \\
47
+ * |/ \\
48
+ * (2) Y Z (2) (other utilities)
49
+ *
50
+ * All roots (A & C) have a criticalPathLength of 0.
51
+ * B has a score of 1, since A depends on it.
52
+ * D has a score of 2, since we look at the longest chain (e.g D-\>B-\>A is longer than D-\>C)
53
+ * X has a score of 1, since the only package which depends on it is A
54
+ * Z has a score of 2, since only X depends on it, and X has a score of 1
55
+ * Y has a score of 2, since the chain Y-\>X-\>C is longer than Y-\>C
56
+ *
57
+ * The algorithm is implemented in AsyncOperationQueue.ts as calculateCriticalPathLength()
58
+ */
59
+ this.criticalPathLength = undefined;
60
+ /**
61
+ * The state of this operation the previous time a manager was invoked.
62
+ */
63
+ this.lastState = undefined;
64
+ /**
65
+ * The current state of this operation
66
+ */
67
+ this.state = undefined;
68
+ /**
69
+ * A cached execution promise for the current OperationExecutionManager invocation of this operation.
70
+ */
71
+ this._promise = undefined;
72
+ /**
73
+ * If true, then a run of this operation is currently wanted.
74
+ * This is used to track state from the `requestRun` callback passed to the runner.
75
+ */
76
+ this._runPending = true;
77
+ this.group = options.group;
78
+ this.runner = options.runner;
79
+ this.weight = options.weight ?? 1;
80
+ this.name = options.name;
81
+ this.metadata = options.metadata || {};
82
+ if (this.group) {
83
+ this.group.addOperation(this);
84
+ }
85
+ }
86
+ addDependency(dependency) {
87
+ this.dependencies.add(dependency);
88
+ dependency.consumers.add(this);
89
+ }
90
+ deleteDependency(dependency) {
91
+ this.dependencies.delete(dependency);
92
+ dependency.consumers.delete(this);
93
+ }
94
+ reset() {
95
+ // Reset operation state
96
+ this.lastState = this.state;
97
+ this.state = {
98
+ status: this.dependencies.size > 0 ? OperationStatus.Waiting : OperationStatus.Ready,
99
+ hasBeenRun: this.lastState?.hasBeenRun ?? false,
100
+ error: undefined,
101
+ stopwatch: new Stopwatch()
102
+ };
103
+ this._promise = undefined;
104
+ this._runPending = true;
105
+ }
106
+ /**
107
+ * @internal
108
+ */
109
+ async _executeAsync(context) {
110
+ const { state } = this;
111
+ if (!state) {
112
+ throw new Error(`Operation state has not been initialized.`);
113
+ }
114
+ if (!this._promise) {
115
+ this._promise = this._executeInnerAsync(context, state);
116
+ }
117
+ return this._promise;
118
+ }
119
+ async _executeInnerAsync(context, rawState) {
120
+ const state = rawState;
121
+ const { runner } = this;
122
+ const dependencyResults = await Promise.allSettled(Array.from(this.dependencies, (dependency) => dependency._executeAsync(context)));
123
+ const { abortSignal, requestRun, queueWork } = context;
124
+ if (abortSignal.aborted) {
125
+ state.status = OperationStatus.Aborted;
126
+ return state.status;
127
+ }
128
+ for (const result of dependencyResults) {
129
+ if (result.status === 'rejected' ||
130
+ result.value === OperationStatus.Blocked ||
131
+ result.value === OperationStatus.Failure) {
132
+ state.status = OperationStatus.Blocked;
133
+ return state.status;
134
+ }
135
+ }
136
+ state.status = OperationStatus.Ready;
137
+ const innerContext = {
138
+ abortSignal,
139
+ isFirstRun: !state.hasBeenRun,
140
+ requestRun: requestRun
141
+ ? (detail) => {
142
+ switch (this.state?.status) {
143
+ case OperationStatus.Waiting:
144
+ case OperationStatus.Ready:
145
+ case OperationStatus.Executing:
146
+ // If current status has not yet resolved to a fixed value,
147
+ // re-executing this operation does not require a full rerun
148
+ // of the operation graph. Simply mark that a run is requested.
149
+ // This variable is on the Operation instead of the
150
+ // containing closure to deal with scenarios in which
151
+ // the runner hangs on to an old copy of the callback.
152
+ this._runPending = true;
153
+ return;
154
+ case OperationStatus.Blocked:
155
+ case OperationStatus.Aborted:
156
+ case OperationStatus.Failure:
157
+ case OperationStatus.NoOp:
158
+ case OperationStatus.Success:
159
+ // The requestRun callback is assumed to remain constant
160
+ // throughout the lifetime of the process, so it is safe
161
+ // to capture here.
162
+ return requestRun(this.name, detail);
163
+ default:
164
+ // This line is here to enforce exhaustiveness
165
+ const currentStatus = this.state?.status;
166
+ throw new InternalError(`Unexpected status: ${currentStatus}`);
167
+ }
168
+ }
169
+ : undefined
170
+ };
171
+ // eslint-disable-next-line require-atomic-updates
172
+ state.status = await queueWork(async () => {
173
+ // Redundant variable to satisfy require-atomic-updates
174
+ const innerState = state;
175
+ if (abortSignal.aborted) {
176
+ innerState.status = OperationStatus.Aborted;
177
+ return innerState.status;
178
+ }
179
+ context.beforeExecute(this, innerState);
180
+ innerState.stopwatch.start();
181
+ innerState.status = OperationStatus.Executing;
182
+ // Mark that the operation has been started at least once.
183
+ innerState.hasBeenRun = true;
184
+ while (this._runPending) {
185
+ this._runPending = false;
186
+ try {
187
+ // We don't support aborting in the middle of a runner's execution.
188
+ innerState.status = runner ? await runner.executeAsync(innerContext) : OperationStatus.NoOp;
189
+ }
190
+ catch (error) {
191
+ innerState.status = OperationStatus.Failure;
192
+ innerState.error = error;
193
+ }
194
+ // Since runner.executeAsync is async, a change could have occurred that requires re-execution
195
+ // This operation is still active, so can re-execute immediately, rather than forcing a whole
196
+ // new execution pass.
197
+ // As currently written, this does mean that if a job is scheduled with higher priority while
198
+ // this operation is still executing, it will still wait for this retry. This may not be desired
199
+ // and if it becomes a problem, the retry loop will need to be moved outside of the `queueWork` call.
200
+ // This introduces complexity regarding tracking of timing and start/end logging, however.
201
+ if (this._runPending) {
202
+ if (abortSignal.aborted) {
203
+ innerState.status = OperationStatus.Aborted;
204
+ break;
205
+ }
206
+ else {
207
+ context.terminal.writeLine(`Immediate rerun requested. Executing.`);
208
+ }
209
+ }
210
+ }
211
+ state.stopwatch.stop();
212
+ context.afterExecute(this, state);
213
+ return state.status;
214
+ }, /* priority */ this.criticalPathLength ?? 0);
215
+ return state.status;
216
+ }
217
+ }
218
+ //# sourceMappingURL=Operation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Operation.js","sourceRoot":"","sources":["../src/Operation.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport { InternalError } from '@rushstack/node-core-library';\nimport type { ITerminal } from '@rushstack/terminal';\n\nimport { Stopwatch } from './Stopwatch';\nimport type {\n IOperationRunner,\n IOperationRunnerContext,\n IOperationState,\n IOperationStates\n} from './IOperationRunner';\nimport type { OperationError } from './OperationError';\nimport { OperationStatus } from './OperationStatus';\nimport type { OperationGroupRecord } from './OperationGroupRecord';\n\n/**\n * Options for constructing a new Operation.\n * @beta\n */\nexport interface IOperationOptions<TMetadata extends {} = {}, TGroupMetadata extends {} = {}> {\n /**\n * The name of this operation, for logging.\n */\n name: string;\n\n /**\n * The group that this operation belongs to. Will be used for logging and duration tracking.\n */\n group?: OperationGroupRecord<TGroupMetadata> | undefined;\n\n /**\n * When the scheduler is ready to process this `Operation`, the `runner` implements the actual work of\n * running the operation.\n */\n runner?: IOperationRunner | undefined;\n\n /**\n * The weight used by the scheduler to determine order of execution.\n */\n weight?: number | undefined;\n\n /**\n * The metadata for this operation.\n */\n metadata?: TMetadata | undefined;\n}\n\n/**\n * Type for the `requestRun` callback.\n * @beta\n */\nexport type OperationRequestRunCallback = (requestor: string, detail?: string) => void;\n\n/**\n * Information provided to `executeAsync` by the `OperationExecutionManager`.\n *\n * @beta\n */\nexport interface IExecuteOperationContext extends Omit<IOperationRunnerContext, 'isFirstRun' | 'requestRun'> {\n /**\n * Function to invoke before execution of an operation, for logging.\n */\n beforeExecute(operation: Operation, state: IOperationState): void;\n\n /**\n * Function to invoke after execution of an operation, for logging.\n */\n afterExecute(operation: Operation, state: IOperationState): void;\n\n /**\n * Function used to schedule the concurrency-limited execution of an operation.\n *\n * Will return OperationStatus.Aborted if execution is aborted before the task executes.\n */\n queueWork(workFn: () => Promise<OperationStatus>, priority: number): Promise<OperationStatus>;\n\n /**\n * A callback to the overarching orchestrator to request that the operation be invoked again.\n * Used in watch mode to signal that inputs have changed.\n *\n * @param requestor - The name of the operation requesting a rerun.\n * @param detail - Optional detail about why the rerun is requested, e.g. the name of a changed file.\n */\n requestRun?: OperationRequestRunCallback;\n\n /**\n * Terminal to write output to.\n */\n terminal: ITerminal;\n}\n\n/**\n * The `Operation` class is a node in the dependency graph of work that needs to be scheduled by the\n * `OperationExecutionManager`. Each `Operation` has a `runner` member of type `IOperationRunner`, whose\n * implementation manages the actual process of running a single operation.\n *\n * The graph of `Operation` instances will be cloned into a separate execution graph after processing.\n *\n * @beta\n */\nexport class Operation<TMetadata extends {} = {}, TGroupMetadata extends {} = {}>\n implements IOperationStates\n{\n /**\n * A set of all dependencies which must be executed before this operation is complete.\n */\n public readonly dependencies: Set<Operation<TMetadata, TGroupMetadata>> = new Set<\n Operation<TMetadata, TGroupMetadata>\n >();\n /**\n * A set of all operations that wait for this operation.\n */\n public readonly consumers: Set<Operation<TMetadata, TGroupMetadata>> = new Set<\n Operation<TMetadata, TGroupMetadata>\n >();\n /**\n * If specified, the name of a grouping to which this Operation belongs, for logging start and end times.\n */\n public readonly group: OperationGroupRecord<TGroupMetadata> | undefined;\n /**\n * The name of this operation, for logging.\n */\n public readonly name: string;\n\n /**\n * When the scheduler is ready to process this `Operation`, the `runner` implements the actual work of\n * running the operation.\n */\n public runner: IOperationRunner | undefined = undefined;\n\n /**\n * This number represents how far away this Operation is from the furthest \"root\" operation (i.e.\n * an operation with no consumers). This helps us to calculate the critical path (i.e. the\n * longest chain of projects which must be executed in order, thereby limiting execution speed\n * of the entire operation tree.\n *\n * This number is calculated via a memoized depth-first search, and when choosing the next\n * operation to execute, the operation with the highest criticalPathLength is chosen.\n *\n * Example:\n * (0) A\n * \\\\\n * (1) B C (0) (applications)\n * \\\\ /|\\\\\n * \\\\ / | \\\\\n * (2) D | X (1) (utilities)\n * | / \\\\\n * |/ \\\\\n * (2) Y Z (2) (other utilities)\n *\n * All roots (A & C) have a criticalPathLength of 0.\n * B has a score of 1, since A depends on it.\n * D has a score of 2, since we look at the longest chain (e.g D-\\>B-\\>A is longer than D-\\>C)\n * X has a score of 1, since the only package which depends on it is A\n * Z has a score of 2, since only X depends on it, and X has a score of 1\n * Y has a score of 2, since the chain Y-\\>X-\\>C is longer than Y-\\>C\n *\n * The algorithm is implemented in AsyncOperationQueue.ts as calculateCriticalPathLength()\n */\n public criticalPathLength: number | undefined = undefined;\n\n /**\n * The weight for this operation. This scalar is the contribution of this operation to the\n * `criticalPathLength` calculation above. Modify to indicate the following:\n * - `weight` === 1: indicates that this operation has an average duration\n * - `weight` &gt; 1: indicates that this operation takes longer than average and so the scheduler\n * should try to favor starting it over other, shorter operations. An example might be an operation that\n * bundles an entire application and runs whole-program optimization.\n * - `weight` &lt; 1: indicates that this operation takes less time than average and so the scheduler\n * should favor other, longer operations over it. An example might be an operation to unpack a cached\n * output, or an operation using NullOperationRunner, which might use a value of 0.\n */\n public weight: number;\n\n /**\n * The state of this operation the previous time a manager was invoked.\n */\n public lastState: IOperationState | undefined = undefined;\n\n /**\n * The current state of this operation\n */\n public state: IOperationState | undefined = undefined;\n\n /**\n * A cached execution promise for the current OperationExecutionManager invocation of this operation.\n */\n private _promise: Promise<OperationStatus> | undefined = undefined;\n\n /**\n * If true, then a run of this operation is currently wanted.\n * This is used to track state from the `requestRun` callback passed to the runner.\n */\n private _runPending: boolean = true;\n\n public readonly metadata: TMetadata;\n\n public constructor(options: IOperationOptions<TMetadata, TGroupMetadata>) {\n this.group = options.group;\n this.runner = options.runner;\n this.weight = options.weight ?? 1;\n this.name = options.name;\n this.metadata = options.metadata || ({} as TMetadata);\n\n if (this.group) {\n this.group.addOperation(this);\n }\n }\n\n public addDependency(dependency: Operation<TMetadata, TGroupMetadata>): void {\n this.dependencies.add(dependency);\n dependency.consumers.add(this);\n }\n\n public deleteDependency(dependency: Operation<TMetadata, TGroupMetadata>): void {\n this.dependencies.delete(dependency);\n dependency.consumers.delete(this);\n }\n\n public reset(): void {\n // Reset operation state\n this.lastState = this.state;\n\n this.state = {\n status: this.dependencies.size > 0 ? OperationStatus.Waiting : OperationStatus.Ready,\n hasBeenRun: this.lastState?.hasBeenRun ?? false,\n error: undefined,\n stopwatch: new Stopwatch()\n };\n\n this._promise = undefined;\n this._runPending = true;\n }\n\n /**\n * @internal\n */\n public async _executeAsync(context: IExecuteOperationContext): Promise<OperationStatus> {\n const { state } = this;\n if (!state) {\n throw new Error(`Operation state has not been initialized.`);\n }\n\n if (!this._promise) {\n this._promise = this._executeInnerAsync(context, state);\n }\n\n return this._promise;\n }\n\n private async _executeInnerAsync(\n context: IExecuteOperationContext,\n rawState: IOperationState\n ): Promise<OperationStatus> {\n const state: IOperationState = rawState;\n const { runner } = this;\n\n const dependencyResults: PromiseSettledResult<OperationStatus>[] = await Promise.allSettled(\n Array.from(this.dependencies, (dependency: Operation) => dependency._executeAsync(context))\n );\n\n const { abortSignal, requestRun, queueWork } = context;\n\n if (abortSignal.aborted) {\n state.status = OperationStatus.Aborted;\n return state.status;\n }\n\n for (const result of dependencyResults) {\n if (\n result.status === 'rejected' ||\n result.value === OperationStatus.Blocked ||\n result.value === OperationStatus.Failure\n ) {\n state.status = OperationStatus.Blocked;\n return state.status;\n }\n }\n\n state.status = OperationStatus.Ready;\n\n const innerContext: IOperationRunnerContext = {\n abortSignal,\n isFirstRun: !state.hasBeenRun,\n requestRun: requestRun\n ? (detail?: string) => {\n switch (this.state?.status) {\n case OperationStatus.Waiting:\n case OperationStatus.Ready:\n case OperationStatus.Executing:\n // If current status has not yet resolved to a fixed value,\n // re-executing this operation does not require a full rerun\n // of the operation graph. Simply mark that a run is requested.\n\n // This variable is on the Operation instead of the\n // containing closure to deal with scenarios in which\n // the runner hangs on to an old copy of the callback.\n this._runPending = true;\n return;\n\n case OperationStatus.Blocked:\n case OperationStatus.Aborted:\n case OperationStatus.Failure:\n case OperationStatus.NoOp:\n case OperationStatus.Success:\n // The requestRun callback is assumed to remain constant\n // throughout the lifetime of the process, so it is safe\n // to capture here.\n return requestRun(this.name, detail);\n default:\n // This line is here to enforce exhaustiveness\n const currentStatus: undefined = this.state?.status;\n throw new InternalError(`Unexpected status: ${currentStatus}`);\n }\n }\n : undefined\n };\n\n // eslint-disable-next-line require-atomic-updates\n state.status = await queueWork(async (): Promise<OperationStatus> => {\n // Redundant variable to satisfy require-atomic-updates\n const innerState: IOperationState = state;\n\n if (abortSignal.aborted) {\n innerState.status = OperationStatus.Aborted;\n return innerState.status;\n }\n\n context.beforeExecute(this, innerState);\n\n innerState.stopwatch.start();\n innerState.status = OperationStatus.Executing;\n // Mark that the operation has been started at least once.\n innerState.hasBeenRun = true;\n\n while (this._runPending) {\n this._runPending = false;\n try {\n // We don't support aborting in the middle of a runner's execution.\n innerState.status = runner ? await runner.executeAsync(innerContext) : OperationStatus.NoOp;\n } catch (error) {\n innerState.status = OperationStatus.Failure;\n innerState.error = error as OperationError;\n }\n\n // Since runner.executeAsync is async, a change could have occurred that requires re-execution\n // This operation is still active, so can re-execute immediately, rather than forcing a whole\n // new execution pass.\n\n // As currently written, this does mean that if a job is scheduled with higher priority while\n // this operation is still executing, it will still wait for this retry. This may not be desired\n // and if it becomes a problem, the retry loop will need to be moved outside of the `queueWork` call.\n // This introduces complexity regarding tracking of timing and start/end logging, however.\n\n if (this._runPending) {\n if (abortSignal.aborted) {\n innerState.status = OperationStatus.Aborted;\n break;\n } else {\n context.terminal.writeLine(`Immediate rerun requested. Executing.`);\n }\n }\n }\n\n state.stopwatch.stop();\n context.afterExecute(this, state);\n\n return state.status;\n }, /* priority */ this.criticalPathLength ?? 0);\n\n return state.status;\n }\n}\n"]}
@@ -0,0 +1,25 @@
1
+ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2
+ // See LICENSE in the project root for license information.
3
+ /**
4
+ * Encapsulates information about an error
5
+ *
6
+ * @beta
7
+ */
8
+ export class OperationError extends Error {
9
+ constructor(type, message) {
10
+ super(message);
11
+ // Manually set the prototype, as we can no longer extend built-in classes like Error, Array, Map, etc.
12
+ // https://github.com/microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
13
+ //
14
+ // Note: the prototype must also be set on any classes which extend this one
15
+ Object.setPrototypeOf(this, OperationError.prototype);
16
+ this._type = type;
17
+ }
18
+ get message() {
19
+ return `[${this._type}] '${super.message}'`;
20
+ }
21
+ toString() {
22
+ return this.message;
23
+ }
24
+ }
25
+ //# sourceMappingURL=OperationError.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OperationError.js","sourceRoot":"","sources":["../src/OperationError.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,2DAA2D;AAE3D;;;;GAIG;AACH,MAAM,OAAO,cAAe,SAAQ,KAAK;IAGvC,YAAmB,IAAY,EAAE,OAAe;QAC9C,KAAK,CAAC,OAAO,CAAC,CAAC;QAEf,uGAAuG;QACvG,+IAA+I;QAC/I,EAAE;QACF,4EAA4E;QAC5E,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,cAAc,CAAC,SAAS,CAAC,CAAC;QAEtD,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;IACpB,CAAC;IAED,IAAW,OAAO;QAChB,OAAO,IAAI,IAAI,CAAC,KAAK,MAAM,KAAK,CAAC,OAAO,GAAG,CAAC;IAC9C,CAAC;IAEM,QAAQ;QACb,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;CACF","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\n/**\n * Encapsulates information about an error\n *\n * @beta\n */\nexport class OperationError extends Error {\n protected _type: string;\n\n public constructor(type: string, message: string) {\n super(message);\n\n // Manually set the prototype, as we can no longer extend built-in classes like Error, Array, Map, etc.\n // https://github.com/microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work\n //\n // Note: the prototype must also be set on any classes which extend this one\n Object.setPrototypeOf(this, OperationError.prototype);\n\n this._type = type;\n }\n\n public get message(): string {\n return `[${this._type}] '${super.message}'`;\n }\n\n public toString(): string {\n return this.message;\n }\n}\n"]}
@@ -0,0 +1,138 @@
1
+ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2
+ // See LICENSE in the project root for license information.
3
+ import { Async } from '@rushstack/node-core-library';
4
+ import { OperationStatus } from './OperationStatus';
5
+ import { calculateCriticalPathLengths } from './calculateCriticalPath';
6
+ import { WorkQueue } from './WorkQueue';
7
+ /**
8
+ * A class which manages the execution of a set of tasks with interdependencies.
9
+ * Initially, and at the end of each task execution, all unblocked tasks
10
+ * are added to a ready queue which is then executed. This is done continually until all
11
+ * tasks are complete, or prematurely fails if any of the tasks fail.
12
+ *
13
+ * @beta
14
+ */
15
+ export class OperationExecutionManager {
16
+ constructor(operations) {
17
+ let trackedOperationCount = 0;
18
+ for (const operation of operations) {
19
+ if (!operation.runner?.silent) {
20
+ // Only count non-silent operations
21
+ trackedOperationCount++;
22
+ }
23
+ }
24
+ this._trackedOperationCount = trackedOperationCount;
25
+ this._operations = calculateCriticalPathLengths(operations);
26
+ this._groupRecords = new Set(Array.from(this._operations, (e) => e.group).filter((e) => e !== undefined));
27
+ for (const consumer of operations) {
28
+ for (const dependency of consumer.dependencies) {
29
+ if (!operations.has(dependency)) {
30
+ throw new Error(`Operation ${JSON.stringify(consumer.name)} declares a dependency on operation ` +
31
+ `${JSON.stringify(dependency.name)} that is not in the set of operations to execute.`);
32
+ }
33
+ }
34
+ }
35
+ }
36
+ /**
37
+ * Executes all operations which have been registered, returning a promise which is resolved when all the
38
+ * operations are completed successfully, or rejects when any operation fails.
39
+ */
40
+ async executeAsync(executionOptions) {
41
+ let hasReportedFailures = false;
42
+ const { abortSignal, parallelism, terminal, requestRun } = executionOptions;
43
+ if (abortSignal.aborted) {
44
+ return OperationStatus.Aborted;
45
+ }
46
+ const startedGroups = new Set();
47
+ const finishedGroups = new Set();
48
+ const maxParallelism = Math.min(this._operations.length, parallelism);
49
+ for (const groupRecord of this._groupRecords) {
50
+ groupRecord.reset();
51
+ }
52
+ for (const operation of this._operations) {
53
+ operation.reset();
54
+ }
55
+ terminal.writeVerboseLine(`Executing a maximum of ${maxParallelism} simultaneous tasks...`);
56
+ const workQueueAbortController = new AbortController();
57
+ const abortHandler = () => workQueueAbortController.abort();
58
+ abortSignal.addEventListener('abort', abortHandler, { once: true });
59
+ try {
60
+ const workQueue = new WorkQueue(workQueueAbortController.signal);
61
+ const executionContext = {
62
+ terminal,
63
+ abortSignal,
64
+ requestRun,
65
+ queueWork: (workFn, priority) => {
66
+ return workQueue.pushAsync(workFn, priority);
67
+ },
68
+ beforeExecute: (operation) => {
69
+ // Initialize group if uninitialized and log the group name
70
+ const { group, runner } = operation;
71
+ if (group) {
72
+ if (!startedGroups.has(group)) {
73
+ startedGroups.add(group);
74
+ group.startTimer();
75
+ terminal.writeLine(` ---- ${group.name} started ---- `);
76
+ executionOptions.beforeExecuteOperationGroup?.(group);
77
+ }
78
+ }
79
+ if (!runner?.silent) {
80
+ executionOptions.beforeExecuteOperation?.(operation);
81
+ }
82
+ },
83
+ afterExecute: (operation, state) => {
84
+ const { group, runner } = operation;
85
+ if (group) {
86
+ group.setOperationAsComplete(operation, state);
87
+ }
88
+ if (state.status === OperationStatus.Failure) {
89
+ // This operation failed. Mark it as such and all reachable dependents as blocked.
90
+ // Failed operations get reported, even if silent.
91
+ // Generally speaking, silent operations shouldn't be able to fail, so this is a safety measure.
92
+ const message = state.error?.message;
93
+ if (message) {
94
+ terminal.writeErrorLine(message);
95
+ }
96
+ hasReportedFailures = true;
97
+ }
98
+ if (!runner?.silent) {
99
+ executionOptions.afterExecuteOperation?.(operation);
100
+ }
101
+ if (group) {
102
+ // Log out the group name and duration if it is the last operation in the group
103
+ if (group?.finished && !finishedGroups.has(group)) {
104
+ finishedGroups.add(group);
105
+ const finishedLoggingWord = group.hasFailures
106
+ ? 'encountered an error'
107
+ : group.hasCancellations
108
+ ? 'cancelled'
109
+ : 'finished';
110
+ terminal.writeLine(` ---- ${group.name} ${finishedLoggingWord} (${group.duration.toFixed(3)}s) ---- `);
111
+ executionOptions.afterExecuteOperationGroup?.(group);
112
+ }
113
+ }
114
+ }
115
+ };
116
+ const workQueuePromise = Async.forEachAsync(workQueue, (workFn) => workFn(), {
117
+ concurrency: maxParallelism
118
+ });
119
+ await Promise.all(this._operations.map((record) => record._executeAsync(executionContext)));
120
+ // Terminate queue execution.
121
+ workQueueAbortController.abort();
122
+ await workQueuePromise;
123
+ }
124
+ finally {
125
+ // Cleanup resources
126
+ abortSignal.removeEventListener('abort', abortHandler);
127
+ }
128
+ const finalStatus = this._trackedOperationCount === 0
129
+ ? OperationStatus.NoOp
130
+ : abortSignal.aborted
131
+ ? OperationStatus.Aborted
132
+ : hasReportedFailures
133
+ ? OperationStatus.Failure
134
+ : OperationStatus.Success;
135
+ return finalStatus;
136
+ }
137
+ }
138
+ //# sourceMappingURL=OperationExecutionManager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OperationExecutionManager.js","sourceRoot":"","sources":["../src/OperationExecutionManager.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport { Async } from '@rushstack/node-core-library';\nimport type { ITerminal } from '@rushstack/terminal';\n\nimport type { IOperationState } from './IOperationRunner';\nimport type { IExecuteOperationContext, Operation, OperationRequestRunCallback } from './Operation';\nimport type { OperationGroupRecord } from './OperationGroupRecord';\nimport { OperationStatus } from './OperationStatus';\nimport { calculateCriticalPathLengths } from './calculateCriticalPath';\nimport { WorkQueue } from './WorkQueue';\n\n/**\n * Options for the current run.\n *\n * @beta\n */\nexport interface IOperationExecutionOptions<\n TOperationMetadata extends {} = {},\n TGroupMetadata extends {} = {}\n> {\n abortSignal: AbortSignal;\n parallelism: number;\n terminal: ITerminal;\n\n requestRun?: OperationRequestRunCallback;\n\n beforeExecuteOperation?: (operation: Operation<TOperationMetadata, TGroupMetadata>) => void;\n afterExecuteOperation?: (operation: Operation<TOperationMetadata, TGroupMetadata>) => void;\n beforeExecuteOperationGroup?: (operationGroup: OperationGroupRecord<TGroupMetadata>) => void;\n afterExecuteOperationGroup?: (operationGroup: OperationGroupRecord<TGroupMetadata>) => void;\n}\n\n/**\n * A class which manages the execution of a set of tasks with interdependencies.\n * Initially, and at the end of each task execution, all unblocked tasks\n * are added to a ready queue which is then executed. This is done continually until all\n * tasks are complete, or prematurely fails if any of the tasks fail.\n *\n * @beta\n */\nexport class OperationExecutionManager<TOperationMetadata extends {} = {}, TGroupMetadata extends {} = {}> {\n /**\n * The set of operations that will be executed\n */\n private readonly _operations: Operation<TOperationMetadata, TGroupMetadata>[];\n /**\n * The total number of non-silent operations in the graph.\n * Silent operations are generally used to simplify the construction of the graph.\n */\n private readonly _trackedOperationCount: number;\n\n private readonly _groupRecords: Set<OperationGroupRecord<TGroupMetadata>>;\n\n public constructor(operations: ReadonlySet<Operation<TOperationMetadata, TGroupMetadata>>) {\n let trackedOperationCount: number = 0;\n for (const operation of operations) {\n if (!operation.runner?.silent) {\n // Only count non-silent operations\n trackedOperationCount++;\n }\n }\n\n this._trackedOperationCount = trackedOperationCount;\n\n this._operations = calculateCriticalPathLengths(operations);\n\n this._groupRecords = new Set(Array.from(this._operations, (e) => e.group).filter((e) => e !== undefined));\n\n for (const consumer of operations) {\n for (const dependency of consumer.dependencies) {\n if (!operations.has(dependency)) {\n throw new Error(\n `Operation ${JSON.stringify(consumer.name)} declares a dependency on operation ` +\n `${JSON.stringify(dependency.name)} that is not in the set of operations to execute.`\n );\n }\n }\n }\n }\n\n /**\n * Executes all operations which have been registered, returning a promise which is resolved when all the\n * operations are completed successfully, or rejects when any operation fails.\n */\n public async executeAsync(\n executionOptions: IOperationExecutionOptions<TOperationMetadata, TGroupMetadata>\n ): Promise<OperationStatus> {\n let hasReportedFailures: boolean = false;\n\n const { abortSignal, parallelism, terminal, requestRun } = executionOptions;\n\n if (abortSignal.aborted) {\n return OperationStatus.Aborted;\n }\n\n const startedGroups: Set<OperationGroupRecord> = new Set();\n const finishedGroups: Set<OperationGroupRecord> = new Set();\n\n const maxParallelism: number = Math.min(this._operations.length, parallelism);\n\n for (const groupRecord of this._groupRecords) {\n groupRecord.reset();\n }\n\n for (const operation of this._operations) {\n operation.reset();\n }\n\n terminal.writeVerboseLine(`Executing a maximum of ${maxParallelism} simultaneous tasks...`);\n\n const workQueueAbortController: AbortController = new AbortController();\n const abortHandler: () => void = () => workQueueAbortController.abort();\n abortSignal.addEventListener('abort', abortHandler, { once: true });\n try {\n const workQueue: WorkQueue = new WorkQueue(workQueueAbortController.signal);\n\n const executionContext: IExecuteOperationContext = {\n terminal,\n abortSignal,\n\n requestRun,\n\n queueWork: (workFn: () => Promise<OperationStatus>, priority: number): Promise<OperationStatus> => {\n return workQueue.pushAsync(workFn, priority);\n },\n\n beforeExecute: (operation: Operation<TOperationMetadata, TGroupMetadata>): void => {\n // Initialize group if uninitialized and log the group name\n const { group, runner } = operation;\n if (group) {\n if (!startedGroups.has(group)) {\n startedGroups.add(group);\n group.startTimer();\n terminal.writeLine(` ---- ${group.name} started ---- `);\n executionOptions.beforeExecuteOperationGroup?.(group);\n }\n }\n if (!runner?.silent) {\n executionOptions.beforeExecuteOperation?.(operation);\n }\n },\n\n afterExecute: (\n operation: Operation<TOperationMetadata, TGroupMetadata>,\n state: IOperationState\n ): void => {\n const { group, runner } = operation;\n if (group) {\n group.setOperationAsComplete(operation, state);\n }\n\n if (state.status === OperationStatus.Failure) {\n // This operation failed. Mark it as such and all reachable dependents as blocked.\n // Failed operations get reported, even if silent.\n // Generally speaking, silent operations shouldn't be able to fail, so this is a safety measure.\n const message: string | undefined = state.error?.message;\n if (message) {\n terminal.writeErrorLine(message);\n }\n hasReportedFailures = true;\n }\n\n if (!runner?.silent) {\n executionOptions.afterExecuteOperation?.(operation);\n }\n\n if (group) {\n // Log out the group name and duration if it is the last operation in the group\n if (group?.finished && !finishedGroups.has(group)) {\n finishedGroups.add(group);\n const finishedLoggingWord: string = group.hasFailures\n ? 'encountered an error'\n : group.hasCancellations\n ? 'cancelled'\n : 'finished';\n terminal.writeLine(\n ` ---- ${group.name} ${finishedLoggingWord} (${group.duration.toFixed(3)}s) ---- `\n );\n executionOptions.afterExecuteOperationGroup?.(group);\n }\n }\n }\n };\n\n const workQueuePromise: Promise<void> = Async.forEachAsync(\n workQueue,\n (workFn: () => Promise<void>) => workFn(),\n {\n concurrency: maxParallelism\n }\n );\n\n await Promise.all(this._operations.map((record: Operation) => record._executeAsync(executionContext)));\n\n // Terminate queue execution.\n workQueueAbortController.abort();\n await workQueuePromise;\n } finally {\n // Cleanup resources\n abortSignal.removeEventListener('abort', abortHandler);\n }\n\n const finalStatus: OperationStatus =\n this._trackedOperationCount === 0\n ? OperationStatus.NoOp\n : abortSignal.aborted\n ? OperationStatus.Aborted\n : hasReportedFailures\n ? OperationStatus.Failure\n : OperationStatus.Success;\n\n return finalStatus;\n }\n}\n"]}
@@ -0,0 +1,62 @@
1
+ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2
+ // See LICENSE in the project root for license information.
3
+ import { InternalError } from '@rushstack/node-core-library';
4
+ import { OperationStatus } from './OperationStatus';
5
+ import { Stopwatch } from './Stopwatch';
6
+ /**
7
+ * Meta-entity that tracks information about a group of related operations.
8
+ *
9
+ * @beta
10
+ */
11
+ export class OperationGroupRecord {
12
+ get duration() {
13
+ return this._groupStopwatch ? this._groupStopwatch.duration : 0;
14
+ }
15
+ get finished() {
16
+ return this._remainingOperations.size === 0;
17
+ }
18
+ get hasCancellations() {
19
+ return this._hasCancellations;
20
+ }
21
+ get hasFailures() {
22
+ return this._hasFailures;
23
+ }
24
+ constructor(name, metadata = {}) {
25
+ this._operations = new Set();
26
+ this._remainingOperations = new Set();
27
+ this._groupStopwatch = new Stopwatch();
28
+ this._hasCancellations = false;
29
+ this._hasFailures = false;
30
+ this.name = name;
31
+ this.metadata = metadata;
32
+ }
33
+ addOperation(operation) {
34
+ this._operations.add(operation);
35
+ }
36
+ startTimer() {
37
+ // Keep this undefined until needed, then start to avoid subsequent calls to startTimer()
38
+ this._groupStopwatch.start();
39
+ }
40
+ setOperationAsComplete(operation, state) {
41
+ if (!this._remainingOperations.has(operation)) {
42
+ throw new InternalError(`Operation ${operation.name} is not in the group ${this.name}`);
43
+ }
44
+ if (state.status === OperationStatus.Aborted) {
45
+ this._hasCancellations = true;
46
+ }
47
+ else if (state.status === OperationStatus.Failure) {
48
+ this._hasFailures = true;
49
+ }
50
+ this._remainingOperations.delete(operation);
51
+ if (this._remainingOperations.size === 0) {
52
+ this._groupStopwatch.stop();
53
+ }
54
+ }
55
+ reset() {
56
+ this._remainingOperations = new Set(this._operations);
57
+ this._groupStopwatch.reset();
58
+ this._hasCancellations = false;
59
+ this._hasFailures = false;
60
+ }
61
+ }
62
+ //# sourceMappingURL=OperationGroupRecord.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OperationGroupRecord.js","sourceRoot":"","sources":["../src/OperationGroupRecord.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,2DAA2D;AAE3D,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAI7D,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC;;;;GAIG;AACH,MAAM,OAAO,oBAAoB;IAW/B,IAAW,QAAQ;QACjB,OAAO,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;IAClE,CAAC;IAED,IAAW,QAAQ;QACjB,OAAO,IAAI,CAAC,oBAAoB,CAAC,IAAI,KAAK,CAAC,CAAC;IAC9C,CAAC;IAED,IAAW,gBAAgB;QACzB,OAAO,IAAI,CAAC,iBAAiB,CAAC;IAChC,CAAC;IAED,IAAW,WAAW;QACpB,OAAO,IAAI,CAAC,YAAY,CAAC;IAC3B,CAAC;IAED,YAAmB,IAAY,EAAE,WAAsB,EAAe;QA1BrD,gBAAW,GAAmB,IAAI,GAAG,EAAE,CAAC;QACjD,yBAAoB,GAAmB,IAAI,GAAG,EAAE,CAAC;QAEjD,oBAAe,GAAc,IAAI,SAAS,EAAE,CAAC;QAC7C,sBAAiB,GAAY,KAAK,CAAC;QACnC,iBAAY,GAAY,KAAK,CAAC;QAsBpC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;IAEM,YAAY,CAAC,SAAoB;QACtC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IAClC,CAAC;IAEM,UAAU;QACf,yFAAyF;QACzF,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC;IAC/B,CAAC;IAEM,sBAAsB,CAAC,SAAoB,EAAE,KAAsB;QACxE,IAAI,CAAC,IAAI,CAAC,oBAAoB,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;YAC9C,MAAM,IAAI,aAAa,CAAC,aAAa,SAAS,CAAC,IAAI,wBAAwB,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QAC1F,CAAC;QAED,IAAI,KAAK,CAAC,MAAM,KAAK,eAAe,CAAC,OAAO,EAAE,CAAC;YAC7C,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC;QAChC,CAAC;aAAM,IAAI,KAAK,CAAC,MAAM,KAAK,eAAe,CAAC,OAAO,EAAE,CAAC;YACpD,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;QAC3B,CAAC;QAED,IAAI,CAAC,oBAAoB,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAC5C,IAAI,IAAI,CAAC,oBAAoB,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YACzC,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE,CAAC;QAC9B,CAAC;IACH,CAAC;IAEM,KAAK;QACV,IAAI,CAAC,oBAAoB,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACtD,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC;QAC7B,IAAI,CAAC,iBAAiB,GAAG,KAAK,CAAC;QAC/B,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC;IAC5B,CAAC;CACF","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport { InternalError } from '@rushstack/node-core-library';\n\nimport type { IOperationState } from './IOperationRunner';\nimport type { Operation } from './Operation';\nimport { OperationStatus } from './OperationStatus';\nimport { Stopwatch } from './Stopwatch';\n\n/**\n * Meta-entity that tracks information about a group of related operations.\n *\n * @beta\n */\nexport class OperationGroupRecord<TMetadata extends {} = {}> {\n private readonly _operations: Set<Operation> = new Set();\n private _remainingOperations: Set<Operation> = new Set();\n\n private _groupStopwatch: Stopwatch = new Stopwatch();\n private _hasCancellations: boolean = false;\n private _hasFailures: boolean = false;\n\n public readonly name: string;\n public readonly metadata: TMetadata;\n\n public get duration(): number {\n return this._groupStopwatch ? this._groupStopwatch.duration : 0;\n }\n\n public get finished(): boolean {\n return this._remainingOperations.size === 0;\n }\n\n public get hasCancellations(): boolean {\n return this._hasCancellations;\n }\n\n public get hasFailures(): boolean {\n return this._hasFailures;\n }\n\n public constructor(name: string, metadata: TMetadata = {} as TMetadata) {\n this.name = name;\n this.metadata = metadata;\n }\n\n public addOperation(operation: Operation): void {\n this._operations.add(operation);\n }\n\n public startTimer(): void {\n // Keep this undefined until needed, then start to avoid subsequent calls to startTimer()\n this._groupStopwatch.start();\n }\n\n public setOperationAsComplete(operation: Operation, state: IOperationState): void {\n if (!this._remainingOperations.has(operation)) {\n throw new InternalError(`Operation ${operation.name} is not in the group ${this.name}`);\n }\n\n if (state.status === OperationStatus.Aborted) {\n this._hasCancellations = true;\n } else if (state.status === OperationStatus.Failure) {\n this._hasFailures = true;\n }\n\n this._remainingOperations.delete(operation);\n if (this._remainingOperations.size === 0) {\n this._groupStopwatch.stop();\n }\n }\n\n public reset(): void {\n this._remainingOperations = new Set(this._operations);\n this._groupStopwatch.reset();\n this._hasCancellations = false;\n this._hasFailures = false;\n }\n}\n"]}