@@ -13,7 +13,7 @@ import { isConcurrentlyCommand, parseConcurrentlyCommand } from "./parseCommands
13
13
* dependencies or a full description (type `TaskConfigFull`).
14
14
*/
15
15
export interface TaskDefinitions {
16
- [ name : string ] : TaskConfig ;
16
+ readonly [ name : string ] : TaskConfig ;
17
17
}
18
18
19
19
/**
@@ -44,14 +44,14 @@ type TaskDependency =
44
44
| `${PackageName } #${TaskName | AnyTaskName } `
45
45
| "..." ;
46
46
47
- export type TaskDependencies = TaskDependency [ ] ;
47
+ export type TaskDependencies = readonly TaskDependency [ ] ;
48
48
49
49
export interface TaskConfig {
50
50
/**
51
51
* Task dependencies as a plain string array. Matched task will be scheduled to run before the current task.
52
52
* The strings specify dependencies for the task. See Task Dependencies Expansion above for details.
53
53
*/
54
- dependsOn : TaskDependencies ;
54
+ readonly dependsOn : TaskDependencies ;
55
55
56
56
/**
57
57
* Tasks that needs to run before the current task (example clean). See Task Dependencies Expansion above for
@@ -61,7 +61,7 @@ export interface TaskConfig {
61
61
* Notes 'before' is disallowed for non-script tasks since it has no effect on non-script tasks as they has no
62
62
* action to perform.
63
63
*/
64
- before : TaskDependencies ;
64
+ readonly before : TaskDependencies ;
65
65
66
66
/**
67
67
* Tasks that this task includes. The included tasks will be scheduled to
@@ -70,7 +70,7 @@ export interface TaskConfig {
70
70
*
71
71
* This should not be custom specified but derived from definition.
72
72
*/
73
- includes : TaskName [ ] ;
73
+ readonly includes : readonly TaskName [ ] ;
74
74
75
75
/**
76
76
* Tasks that needs to run after the current task (example copy tasks). See Task Dependencies Expansion above for
@@ -80,7 +80,7 @@ export interface TaskConfig {
80
80
* Notes 'after' is disallowed for non-script tasks since it has no effect on non-script tasks as they has no
81
81
* action to perform.
82
82
*/
83
- after : TaskDependencies ;
83
+ readonly after : TaskDependencies ;
84
84
85
85
/**
86
86
* Specify whether this is a script task or not. Default to true when this is omitted
@@ -92,30 +92,48 @@ export interface TaskConfig {
92
92
* If false, the task will only trigger the dependencies (and not look for the script in package.json).
93
93
* It can be used as an alias to a group of tasks.
94
94
*/
95
- script : boolean ;
95
+ readonly script : boolean ;
96
+ }
97
+
98
+ type Mutable < T > = { - readonly [ P in keyof T ] : T [ P ] } ;
99
+
100
+ type MutableTaskConfig = Mutable < TaskConfig > ;
101
+ interface MutableTaskDefinitions {
102
+ [ name : TaskName ] : MutableTaskConfig ;
96
103
}
97
104
98
105
// On file versions that allow fields to be omitted
99
106
export type TaskConfigOnDisk = TaskDependencies | Omit < Partial < TaskConfig > , "includes" > ;
100
107
export interface TaskDefinitionsOnDisk {
101
- [ name : string ] : TaskConfigOnDisk ;
108
+ readonly [ name : TaskName ] : TaskConfigOnDisk ;
109
+ }
110
+
111
+ export interface WriteableTaskDefinitionsOnDisk {
112
+ [ name : TaskName ] : Mutable < TaskConfigOnDisk > ;
102
113
}
103
114
115
+ export const isTaskDependencies = ( value : TaskConfigOnDisk ) : value is TaskDependencies => {
116
+ return Array . isArray ( value ) ;
117
+ } ;
118
+
119
+ const makeClonedOrEmptyArray = < T > ( value : readonly T [ ] | undefined ) : T [ ] =>
120
+ value ? [ ...value ] : [ ] ;
121
+
104
122
/**
105
123
* Convert and fill out default values from TaskConfigOnDisk to TaskConfig in memory
106
124
* @param config TaskConfig info loaded from a file
107
125
* @returns TaskConfig filled out with default values
108
126
*/
109
- function getFullTaskConfig ( config : TaskConfigOnDisk ) : TaskConfig {
110
- if ( Array . isArray ( config ) ) {
127
+ function getFullTaskConfig ( config : TaskConfigOnDisk ) : MutableTaskConfig {
128
+ if ( isTaskDependencies ( config ) ) {
111
129
return { dependsOn : config , script : true , includes : [ ] , before : [ ] , after : [ ] } ;
112
130
} else {
113
131
return {
114
- dependsOn : config . dependsOn ?? [ ] ,
132
+ dependsOn : makeClonedOrEmptyArray ( config . dependsOn ) ,
115
133
script : config . script ?? true ,
116
- before : config . before ?? [ ] ,
134
+ before : makeClonedOrEmptyArray ( config . before ) ,
117
135
includes : [ ] ,
118
- after : config . after ?? [ ] ,
136
+ after : makeClonedOrEmptyArray ( config . after ) ,
119
137
} ;
120
138
}
121
139
}
@@ -136,7 +154,7 @@ export const defaultCleanTaskName = "clean";
136
154
// subtasks that has no name inherit the dependency. (where as normally, all subtask does)
137
155
// (i.e. isDefault: true)
138
156
139
- export type TaskDefinition = TaskConfig & { isDefault ?: boolean } ;
157
+ export type TaskDefinition = TaskConfig & { readonly isDefault ?: boolean } ;
140
158
141
159
/**
142
160
* Get the default task definition for the given task name
@@ -156,17 +174,17 @@ const defaultTaskDefinition = {
156
174
includes : [ ] ,
157
175
after : [ "^*" ] , // TODO: include "*" so the user configured task will run first, but we need to make sure it doesn't cause circular dependency first
158
176
isDefault : true , // only propagate to unnamed sub tasks if it is a group task
159
- } satisfies TaskDefinition ;
177
+ } as const satisfies TaskDefinition ;
160
178
const defaultCleanTaskDefinition = {
161
179
dependsOn : [ ] ,
162
180
script : true ,
163
181
before : [ "*" ] , // clean are ran before all the tasks, add a week dependency.
164
182
includes : [ ] ,
165
183
after : [ ] ,
166
- } satisfies TaskDefinition ;
184
+ } as const satisfies TaskDefinition ;
167
185
168
186
const detectInvalid = (
169
- config : string [ ] ,
187
+ config : readonly string [ ] ,
170
188
isInvalid : ( value : string ) => boolean ,
171
189
name : string ,
172
190
kind : string ,
@@ -186,7 +204,7 @@ export function normalizeGlobalTaskDefinitions(
186
204
globalTaskDefinitionsOnDisk : TaskDefinitionsOnDisk | undefined ,
187
205
) : TaskDefinitions {
188
206
// Normalize all on disk config to full config and validate
189
- const taskDefinitions : TaskDefinitions = { } ;
207
+ const taskDefinitions : MutableTaskDefinitions = { } ;
190
208
if ( globalTaskDefinitionsOnDisk ) {
191
209
for ( const name in globalTaskDefinitionsOnDisk ) {
192
210
const full = getFullTaskConfig ( globalTaskDefinitionsOnDisk [ name ] ) ;
@@ -224,7 +242,7 @@ export function normalizeGlobalTaskDefinitions(
224
242
return taskDefinitions ;
225
243
}
226
244
227
- function expandDotDotDot ( config : string [ ] , inherited : string [ ] ) {
245
+ function expandDotDotDot ( config : readonly string [ ] , inherited : readonly string [ ] ) {
228
246
const expanded = config . filter ( ( value ) => value !== "..." ) ;
229
247
if ( inherited !== undefined && expanded . length !== config . length ) {
230
248
return expanded . concat ( inherited ) ;
@@ -285,7 +303,13 @@ export function getTaskDefinitions(
285
303
) : TaskDefinitions {
286
304
const packageScripts = json . scripts ?? { } ;
287
305
const packageTaskDefinitions = json . fluidBuild ?. tasks ;
288
- const taskDefinitions : TaskDefinitions = { } ;
306
+ const taskDefinitions : MutableTaskDefinitions = { } ;
307
+
308
+ const globalAllow = ( value ) =>
309
+ value . startsWith ( "^" ) ||
310
+ ( globalTaskDefinitions [ value ] !== undefined && ! globalTaskDefinitions [ value ] . script ) ||
311
+ packageScripts [ value ] !== undefined ;
312
+ const globalAllowExpansionsStar = ( value ) => value === "*" || globalAllow ( value ) ;
289
313
290
314
// Initialize from global TaskDefinition, and filter out script tasks if the package doesn't have the script
291
315
for ( const name in globalTaskDefinitions ) {
@@ -294,19 +318,16 @@ export function getTaskDefinitions(
294
318
// Skip script tasks if the package doesn't have the script
295
319
continue ;
296
320
}
297
- taskDefinitions [ name ] = { ...globalTaskDefinition } ;
298
- }
299
- const globalAllow = ( value ) =>
300
- value . startsWith ( "^" ) ||
301
- taskDefinitions [ value ] !== undefined ||
302
- packageScripts [ value ] !== undefined ;
303
- const globalAllowExpansionsStar = ( value ) => value === "*" || globalAllow ( value ) ;
304
- // Only keep task or script references that exists
305
- for ( const name in taskDefinitions ) {
306
- const taskDefinition = taskDefinitions [ name ] ;
307
- taskDefinition . dependsOn = taskDefinition . dependsOn . filter ( globalAllow ) ;
308
- taskDefinition . before = taskDefinition . before . filter ( globalAllowExpansionsStar ) ;
309
- taskDefinition . after = taskDefinition . after . filter ( globalAllowExpansionsStar ) ;
321
+ // Only keep task or script references that exists
322
+ // and make array clones in the process.
323
+ taskDefinitions [ name ] = {
324
+ dependsOn : globalTaskDefinition . dependsOn . filter ( globalAllow ) ,
325
+ script : globalTaskDefinition . script ,
326
+ before : globalTaskDefinition . before . filter ( globalAllowExpansionsStar ) ,
327
+ // `includes` are not inherited from the global task definitions (which should always be empty anyway)
328
+ includes : [ ] ,
329
+ after : globalTaskDefinition . after . filter ( globalAllowExpansionsStar ) ,
330
+ } ;
310
331
}
311
332
312
333
// Override from the package.json, and resolve "..." to the global dependencies if any
0 commit comments