-
Notifications
You must be signed in to change notification settings - Fork 698
Expand file tree
/
Copy pathHeftTaskSession.ts
More file actions
305 lines (273 loc) · 10.5 KB
/
Copy pathHeftTaskSession.ts
File metadata and controls
305 lines (273 loc) · 10.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import * as path from 'path';
import { AsyncParallelHook, AsyncSeriesWaterfallHook } from 'tapable';
import type { MetricsCollector } from '../metrics/MetricsCollector';
import type { IScopedLogger } from './logging/ScopedLogger';
import type { HeftTask } from './HeftTask';
import type { IHeftPhaseSessionOptions } from './HeftPhaseSession';
import type { HeftParameterManager, IHeftParameters } from './HeftParameterManager';
import type { IDeleteOperation } from '../plugins/DeleteFilesPlugin';
import type { ICopyOperation } from '../plugins/CopyFilesPlugin';
import type { HeftPluginHost } from './HeftPluginHost';
import type { CancellationToken } from './CancellationToken';
import { WatchGlobFn } from '../plugins/FileGlobSpecifier';
import { InternalError } from '@rushstack/node-core-library';
/**
* The type of {@link IHeftTaskSession.parsedCommandLine}, which exposes details about the
* command line that was used to invoke Heft.
* @public
*/
export interface IHeftParsedCommandLine {
/**
* Returns the subcommand passed on the Heft command line, before any aliases have been expanded.
* This can be useful when printing error messages that need to refer to the invoked command line.
*
* @remarks
* For example, if the invoked command was `heft test --verbose`, then `commandName`
* would be `test`.
*
* Suppose the invoked command was `heft start` which is an alias for `heft build-watch --serve`.
* In this case, the `commandName` would be `start`. To get the expanded name `build-watch`,
* use {@link IHeftParsedCommandLine.unaliasedCommandName} instead.
*
* When invoking phases directly using `heft run`, the `commandName` is `run`.
*
* @see {@link IHeftParsedCommandLine.unaliasedCommandName}
*/
readonly commandName: string;
/**
* Returns the subcommand passed on the Heft command line, after any aliases have been expanded.
* This can be useful when printing error messages that need to refer to the invoked command line.
*
* @remarks
* For example, if the invoked command was `heft test --verbose`, then `unaliasedCommandName`
* would be `test`.
*
* Suppose the invoked command was `heft start` which is an alias for `heft build-watch --serve`.
* In this case, the `unaliasedCommandName` would be `build-watch`. To get the alias name
* `start`, use @see {@link IHeftParsedCommandLine.commandName} instead.
*
* When invoking phases directly using `heft run`, the `unaliasedCommandName` is `run`.
*
* @see {@link IHeftParsedCommandLine.commandName}
*/
readonly unaliasedCommandName: string;
}
/**
* The task session is responsible for providing session-specific information to Heft task plugins.
* The session provides access to the hooks that Heft will run as part of task execution, as well as
* access to parameters provided via the CLI. The session is also how you request access to other task
* plugins.
*
* @public
*/
export interface IHeftTaskSession {
/**
* The name of the task. This is defined in "heft.json".
*
* @public
*/
readonly taskName: string;
/**
* The hooks available to the task plugin.
*
* @public
*/
readonly hooks: IHeftTaskHooks;
/**
* Contains default parameters provided by Heft, as well as CLI parameters requested by the task
* plugin.
*
* @public
*/
readonly parameters: IHeftParameters;
/**
* Exposes details about the command line that was used to invoke Heft.
* This value is initially `undefined` and later filled in after the command line has been parsed.
*/
readonly parsedCommandLine: IHeftParsedCommandLine;
/**
* The temp folder for the task. This folder is unique for each task, and will be cleaned
* when Heft is run with `--clean`.
*
* @public
*/
readonly tempFolderPath: string;
/**
* The scoped logger for the task. Messages logged with this logger will be prefixed with
* the phase and task name, in the format "[<phaseName>:<taskName>]". It is highly recommended
* that writing to the console be performed via the logger, as it will ensure that logging messages
* are labeled with the source of the message.
*
* @public
*/
readonly logger: IScopedLogger;
/**
* Set a a callback which will be called if and after the specified plugin has been applied.
* This can be used to tap hooks on another plugin that exists within the same phase.
*
* @public
*/
requestAccessToPluginByName<T extends object>(
pluginToAccessPackage: string,
pluginToAccessName: string,
pluginApply: (pluginAccessor: T) => void
): void;
}
/**
* Hooks that are available to the task plugin.
*
* @public
*/
export interface IHeftTaskHooks {
/**
* The `run` hook is called after all dependency task executions have completed during a normal
* run, or during a watch mode run when no `runIncremental` hook is provided. It is where the
* plugin can perform its work. To use it, call `run.tapPromise(<pluginName>, <callback>)`.
*
* @public
*/
readonly run: AsyncParallelHook<IHeftTaskRunHookOptions>;
/**
* If provided, the `runIncremental` hook is called after all dependency task executions have completed
* during a watch mode run. It is where the plugin can perform incremental work. To use it, call
* `run.tapPromise(<pluginName>, <callback>)`.
*/
readonly runIncremental: AsyncParallelHook<IHeftTaskRunIncrementalHookOptions>;
/**
* If provided, the `registerFileOperations` hook is called exactly once before the first time either
* `run` or `runIncremental` would be invoked to provide the plugin an opportunity to request
* dynamic file copy or deletion operations.
*/
readonly registerFileOperations: AsyncSeriesWaterfallHook<IHeftTaskFileOperations>;
}
/**
* Options provided to the `run` hook.
*
* @public
*/
export interface IHeftTaskRunHookOptions {
/**
* A cancellation token that is used to signal that the build is cancelled. This
* can be used to stop operations early and allow for a new build to
* be started.
*
* @beta
*/
readonly cancellationToken: CancellationToken;
}
/**
* Options provided to the 'runIncremental' hook.
*
* @public
*/
export interface IHeftTaskRunIncrementalHookOptions extends IHeftTaskRunHookOptions {
/**
* A callback that can be invoked to tell the Heft runtime to schedule an incremental run of this
* task. If a run is already pending, does nothing.
*/
readonly requestRun: () => void;
/**
* Reads the specified globs and returns the result, filtering out files that have not changed since the last execution.
* All file system calls while reading the glob are tracked and will be watched for changes.
*
* If a change to the monitored files is detected, the task will be scheduled for re-execution.
*/
readonly watchGlobAsync: WatchGlobFn;
}
/**
* Options provided to the `registerFileOperations` hook.
*
* @public
*/
export interface IHeftTaskFileOperations {
/**
* Copy operations to be performed following the `run` or `runIncremental` hook. These operations will be
* performed after the task `run` or `runIncremental` hook has completed.
*
* @public
*/
copyOperations: Set<ICopyOperation>;
/**
* Delete operations to be performed following the `run` or `runIncremental` hook. These operations will be
* performed after the task `run` or `runIncremental` hook has completed.
*
* @public
*/
deleteOperations: Set<IDeleteOperation>;
}
export interface IHeftTaskSessionOptions extends IHeftPhaseSessionOptions {
task: HeftTask;
pluginHost: HeftPluginHost;
}
export class HeftTaskSession implements IHeftTaskSession {
public readonly taskName: string;
public readonly hooks: IHeftTaskHooks;
public readonly tempFolderPath: string;
public readonly logger: IScopedLogger;
private readonly _options: IHeftTaskSessionOptions;
private _parameters: IHeftParameters | undefined;
private _parsedCommandLine: IHeftParsedCommandLine;
/**
* @internal
*/
public readonly metricsCollector: MetricsCollector;
public get parameters(): IHeftParameters {
// Delay loading the parameters for the task until they're actually needed
if (!this._parameters) {
const parameterManager: HeftParameterManager = this._options.internalHeftSession.parameterManager;
const task: HeftTask = this._options.task;
this._parameters = parameterManager.getParametersForPlugin(task.pluginDefinition);
}
return this._parameters;
}
public get parsedCommandLine(): IHeftParsedCommandLine {
return this._parsedCommandLine;
}
public constructor(options: IHeftTaskSessionOptions) {
const {
internalHeftSession: {
heftConfiguration: { tempFolderPath: tempFolder },
loggingManager,
metricsCollector
},
phase,
task
} = options;
if (!options.internalHeftSession.parsedCommandLine) {
// This should not happen
throw new InternalError('Attempt to construct HeftTaskSession before command line has been parsed');
}
this._parsedCommandLine = options.internalHeftSession.parsedCommandLine;
this.logger = loggingManager.requestScopedLogger(`${phase.phaseName}:${task.taskName}`);
this.metricsCollector = metricsCollector;
this.taskName = task.taskName;
this.hooks = {
run: new AsyncParallelHook(['runHookOptions']),
runIncremental: new AsyncParallelHook(['runIncrementalHookOptions']),
registerFileOperations: new AsyncSeriesWaterfallHook(['fileOperations'])
};
// Guaranteed to be unique since phases are uniquely named, tasks are uniquely named within
// phases, and neither can have '.' in their names. We will also use the phase name and
// task name as the folder name (instead of the plugin name) since we want to enable re-use
// of plugins in multiple phases and tasks while maintaining unique temp/cache folders for
// each task.
const uniqueTaskFolderName: string = `${phase.phaseName}.${task.taskName}`;
// <projectFolder>/temp/<phaseName>.<taskName>
this.tempFolderPath = path.join(tempFolder, uniqueTaskFolderName);
this._options = options;
}
public requestAccessToPluginByName<T extends object>(
pluginToAccessPackage: string,
pluginToAccessName: string,
pluginApply: (pluginAccessor: T) => void
): void {
this._options.pluginHost.requestAccessToPluginByName(
this.taskName,
pluginToAccessPackage,
pluginToAccessName,
pluginApply
);
}
}