@xpack/xpm-lib 3.1.2 → 4.0.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/README.md +16 -212
- package/dist/classes/actions.d.ts +58 -0
- package/dist/classes/actions.d.ts.map +1 -0
- package/dist/classes/actions.js +250 -0
- package/dist/classes/actions.js.map +1 -0
- package/dist/classes/build-configurations.d.ts +78 -0
- package/dist/classes/build-configurations.d.ts.map +1 -0
- package/dist/classes/build-configurations.js +489 -0
- package/dist/classes/build-configurations.js.map +1 -0
- package/dist/classes/combinations-generator.d.ts +19 -0
- package/dist/classes/combinations-generator.d.ts.map +1 -0
- package/dist/classes/combinations-generator.js +48 -0
- package/dist/classes/combinations-generator.js.map +1 -0
- package/dist/classes/data-model.d.ts +21 -0
- package/dist/classes/data-model.d.ts.map +1 -0
- package/dist/classes/data-model.js +47 -0
- package/dist/classes/data-model.js.map +1 -0
- package/dist/classes/errors.d.ts +13 -0
- package/dist/classes/errors.d.ts.map +1 -0
- package/dist/classes/errors.js +13 -0
- package/dist/classes/errors.js.map +1 -0
- package/dist/classes/init-template-base.d.ts +47 -0
- package/dist/classes/init-template-base.d.ts.map +1 -0
- package/dist/classes/init-template-base.js +358 -0
- package/dist/classes/init-template-base.js.map +1 -0
- package/dist/classes/liquid-drop.d.ts +28 -0
- package/dist/classes/liquid-drop.d.ts.map +1 -0
- package/dist/classes/liquid-drop.js +70 -0
- package/dist/classes/liquid-drop.js.map +1 -0
- package/dist/classes/liquid-engine.d.ts +7 -0
- package/dist/classes/liquid-engine.d.ts.map +1 -0
- package/dist/classes/liquid-engine.js +72 -0
- package/dist/classes/liquid-engine.js.map +1 -0
- package/dist/classes/package.d.ts +31 -0
- package/dist/classes/package.d.ts.map +1 -0
- package/dist/classes/package.js +268 -0
- package/dist/classes/package.js.map +1 -0
- package/dist/classes/platform-detector.d.ts +14 -0
- package/dist/classes/platform-detector.d.ts.map +1 -0
- package/dist/classes/platform-detector.js +26 -0
- package/dist/classes/platform-detector.js.map +1 -0
- package/dist/classes/policies.d.ts +14 -0
- package/dist/classes/policies.d.ts.map +1 -0
- package/dist/classes/policies.js +20 -0
- package/dist/classes/policies.js.map +1 -0
- package/dist/classes/template-expander.d.ts +29 -0
- package/dist/classes/template-expander.d.ts.map +1 -0
- package/dist/classes/template-expander.js +62 -0
- package/dist/classes/template-expander.js.map +1 -0
- package/dist/data/substitutions-variables.d.ts +43 -0
- package/dist/data/substitutions-variables.d.ts.map +1 -0
- package/dist/{lib → data}/substitutions-variables.js +1 -16
- package/dist/data/substitutions-variables.js.map +1 -0
- package/dist/functions/chmod-recursively.d.ts +9 -0
- package/dist/functions/chmod-recursively.d.ts.map +1 -0
- package/dist/functions/chmod-recursively.js +66 -0
- package/dist/functions/chmod-recursively.js.map +1 -0
- package/dist/functions/filter-paths.d.ts +5 -0
- package/dist/functions/filter-paths.d.ts.map +1 -0
- package/dist/functions/filter-paths.js +16 -0
- package/dist/functions/filter-paths.js.map +1 -0
- package/dist/functions/is-something.d.ts +9 -0
- package/dist/functions/is-something.d.ts.map +1 -0
- package/dist/functions/is-something.js +25 -0
- package/dist/functions/is-something.js.map +1 -0
- package/dist/functions/matrix-expander.d.ts +17 -0
- package/dist/functions/matrix-expander.d.ts.map +1 -0
- package/dist/functions/matrix-expander.js +52 -0
- package/dist/functions/matrix-expander.js.map +1 -0
- package/dist/functions/perform-substitutions.d.ts +12 -0
- package/dist/functions/perform-substitutions.d.ts.map +1 -0
- package/dist/functions/perform-substitutions.js +76 -0
- package/dist/functions/perform-substitutions.js.map +1 -0
- package/dist/functions/utils.d.ts +8 -0
- package/dist/functions/utils.d.ts.map +1 -0
- package/dist/functions/utils.js +16 -0
- package/dist/functions/utils.js.map +1 -0
- package/dist/index.d.ts +22 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -29
- package/dist/index.js.map +1 -1
- package/dist/{lib/types.d.ts → types/json.d.ts} +31 -22
- package/dist/types/json.d.ts.map +1 -0
- package/dist/types/json.js +2 -0
- package/dist/types/json.js.map +1 -0
- package/dist/types/xpm-init-template.d.ts +21 -0
- package/dist/types/xpm-init-template.d.ts.map +1 -0
- package/dist/types/xpm-init-template.js +2 -0
- package/dist/types/xpm-init-template.js.map +1 -0
- package/dist/types/xpm.d.ts +16 -0
- package/dist/types/xpm.d.ts.map +1 -0
- package/dist/types/xpm.js +2 -0
- package/dist/types/xpm.js.map +1 -0
- package/package.json +53 -44
- package/src/CODE-REVIEW.md +2167 -0
- package/src/README.md +393 -6
- package/src/classes/actions.ts +1157 -0
- package/src/classes/build-configurations.ts +2127 -0
- package/src/classes/combinations-generator.ts +331 -0
- package/src/classes/data-model.ts +337 -0
- package/src/classes/errors.ts +105 -0
- package/src/classes/init-template-base.ts +1028 -0
- package/src/classes/liquid-drop.ts +376 -0
- package/src/classes/liquid-engine.ts +249 -0
- package/src/classes/package.ts +765 -0
- package/src/classes/platform-detector.ts +237 -0
- package/src/classes/policies.ts +200 -0
- package/src/classes/template-expander.ts +330 -0
- package/src/data/substitutions-variables.ts +390 -0
- package/src/functions/chmod-recursively.ts +195 -0
- package/src/functions/filter-paths.ts +126 -0
- package/src/functions/is-something.ts +223 -0
- package/src/functions/matrix-expander.ts +172 -0
- package/src/functions/perform-substitutions.ts +253 -0
- package/src/functions/utils.ts +151 -0
- package/src/index.ts +72 -19
- package/src/types/json.ts +519 -0
- package/src/types/xpm-init-template.ts +282 -0
- package/src/types/xpm.ts +162 -0
- package/dist/lib/chmod-recursive.d.ts +0 -7
- package/dist/lib/chmod-recursive.d.ts.map +0 -1
- package/dist/lib/chmod-recursive.js +0 -81
- package/dist/lib/chmod-recursive.js.map +0 -1
- package/dist/lib/errors.d.ts +0 -11
- package/dist/lib/errors.d.ts.map +0 -1
- package/dist/lib/errors.js +0 -26
- package/dist/lib/errors.js.map +0 -1
- package/dist/lib/functions/chmod-recursive.d.ts +0 -7
- package/dist/lib/functions/chmod-recursive.d.ts.map +0 -1
- package/dist/lib/functions/chmod-recursive.js +0 -81
- package/dist/lib/functions/chmod-recursive.js.map +0 -1
- package/dist/lib/functions/perform-substitutions.d.ts +0 -20
- package/dist/lib/functions/perform-substitutions.d.ts.map +0 -1
- package/dist/lib/functions/perform-substitutions.js +0 -85
- package/dist/lib/functions/perform-substitutions.js.map +0 -1
- package/dist/lib/functions/utils.d.ts +0 -30
- package/dist/lib/functions/utils.d.ts.map +0 -1
- package/dist/lib/functions/utils.js +0 -70
- package/dist/lib/functions/utils.js.map +0 -1
- package/dist/lib/init-template-base.d.ts +0 -46
- package/dist/lib/init-template-base.d.ts.map +0 -1
- package/dist/lib/init-template-base.js +0 -281
- package/dist/lib/init-template-base.js.map +0 -1
- package/dist/lib/liquid-actions.d.ts +0 -37
- package/dist/lib/liquid-actions.d.ts.map +0 -1
- package/dist/lib/liquid-actions.js +0 -148
- package/dist/lib/liquid-actions.js.map +0 -1
- package/dist/lib/liquid-build-configurations.d.ts +0 -47
- package/dist/lib/liquid-build-configurations.d.ts.map +0 -1
- package/dist/lib/liquid-build-configurations.js +0 -282
- package/dist/lib/liquid-build-configurations.js.map +0 -1
- package/dist/lib/liquid-drop.d.ts +0 -13
- package/dist/lib/liquid-drop.d.ts.map +0 -1
- package/dist/lib/liquid-drop.js +0 -56
- package/dist/lib/liquid-drop.js.map +0 -1
- package/dist/lib/liquid-engine.d.ts +0 -5
- package/dist/lib/liquid-engine.d.ts.map +0 -1
- package/dist/lib/liquid-engine.js +0 -85
- package/dist/lib/liquid-engine.js.map +0 -1
- package/dist/lib/liquid-package.d.ts +0 -17
- package/dist/lib/liquid-package.d.ts.map +0 -1
- package/dist/lib/liquid-package.js +0 -70
- package/dist/lib/liquid-package.js.map +0 -1
- package/dist/lib/package.d.ts +0 -66
- package/dist/lib/package.d.ts.map +0 -1
- package/dist/lib/package.js +0 -700
- package/dist/lib/package.js.map +0 -1
- package/dist/lib/perform-substitutions.d.ts +0 -20
- package/dist/lib/perform-substitutions.d.ts.map +0 -1
- package/dist/lib/perform-substitutions.js +0 -85
- package/dist/lib/perform-substitutions.js.map +0 -1
- package/dist/lib/policies.d.ts +0 -14
- package/dist/lib/policies.d.ts.map +0 -1
- package/dist/lib/policies.js +0 -33
- package/dist/lib/policies.js.map +0 -1
- package/dist/lib/substitutions-variables.d.ts +0 -117
- package/dist/lib/substitutions-variables.d.ts.map +0 -1
- package/dist/lib/substitutions-variables.js.map +0 -1
- package/dist/lib/types.d.ts.map +0 -1
- package/dist/lib/types.js +0 -13
- package/dist/lib/types.js.map +0 -1
- package/dist/lib/utils.d.ts +0 -30
- package/dist/lib/utils.d.ts.map +0 -1
- package/dist/lib/utils.js +0 -70
- package/dist/lib/utils.js.map +0 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/src/lib/errors.ts +0 -29
- package/src/lib/functions/chmod-recursive.ts +0 -103
- package/src/lib/functions/perform-substitutions.ts +0 -116
- package/src/lib/functions/utils.ts +0 -88
- package/src/lib/init-template-base.ts +0 -408
- package/src/lib/liquid-actions.ts +0 -223
- package/src/lib/liquid-build-configurations.ts +0 -433
- package/src/lib/liquid-drop.ts +0 -99
- package/src/lib/liquid-engine.ts +0 -135
- package/src/lib/liquid-package.ts +0 -108
- package/src/lib/package.ts +0 -947
- package/src/lib/policies.ts +0 -51
- package/src/lib/substitutions-variables.ts +0 -177
- package/src/lib/types.ts +0 -109
- package/src/package.json +0 -3
- package/src/tsconfig.json +0 -10
|
@@ -0,0 +1,2127 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of the xPack project (http://xpack.github.io).
|
|
3
|
+
* Copyright (c) 2021-2026 Liviu Ionescu. All rights reserved.
|
|
4
|
+
*
|
|
5
|
+
* Permission to use, copy, modify, and/or distribute this software
|
|
6
|
+
* for any purpose is hereby granted, under the terms of the MIT license.
|
|
7
|
+
*
|
|
8
|
+
* If a copy of the license was not distributed with this file, it can
|
|
9
|
+
* be obtained from https://opensource.org/license/mit.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// ----------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
import assert from 'node:assert'
|
|
15
|
+
import * as path from 'node:path'
|
|
16
|
+
import * as os from 'node:os'
|
|
17
|
+
|
|
18
|
+
import { Logger } from '@xpack/logger'
|
|
19
|
+
|
|
20
|
+
// ----------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
import { LiquidEngine } from './liquid-engine.js'
|
|
23
|
+
import {
|
|
24
|
+
LiquidSubstitutionsVariables,
|
|
25
|
+
LiquidSubstitutionsStrings,
|
|
26
|
+
} from '../data/substitutions-variables.js'
|
|
27
|
+
import { filterPath } from '../functions/filter-paths.js'
|
|
28
|
+
import { isJsonObject, isString } from '../functions/is-something.js'
|
|
29
|
+
import { getErrorMessage, hasLiquidSyntax } from '../functions/utils.js'
|
|
30
|
+
import { performSubstitutions } from '../functions/perform-substitutions.js'
|
|
31
|
+
import {
|
|
32
|
+
JsonBuildConfigurations,
|
|
33
|
+
JsonBuildConfigurationTemplate,
|
|
34
|
+
JsonBuildConfiguration,
|
|
35
|
+
JsonBuildConfigurationContent,
|
|
36
|
+
JsonDependencies,
|
|
37
|
+
JsonBuildConfigurationInherits,
|
|
38
|
+
} from '../types/json.js'
|
|
39
|
+
import { Actions, Action } from './actions.js'
|
|
40
|
+
import { buildFolderRelativePathPropertyName } from './data-model.js'
|
|
41
|
+
import { ConfigurationError } from './errors.js'
|
|
42
|
+
import { TemplateExpander } from './template-expander.js'
|
|
43
|
+
|
|
44
|
+
// ============================================================================
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Configuration parameters for constructing a build configurations collection.
|
|
48
|
+
*
|
|
49
|
+
* @remarks
|
|
50
|
+
* This interface defines the required configuration for creating an
|
|
51
|
+
* instance of {@link BuildConfigurations}. Most properties are mandatory
|
|
52
|
+
* except for the optional <code>jsonBuildConfigurations</code>, which can
|
|
53
|
+
* be undefined if there are no build configurations defined in the package.
|
|
54
|
+
*
|
|
55
|
+
* The parameters provide the collection with access to the Liquid templating
|
|
56
|
+
* engine, substitution variables hierarchy, build configuration definitions
|
|
57
|
+
* from the package manifest, and the logger for diagnostic output during
|
|
58
|
+
* configuration processing.
|
|
59
|
+
*/
|
|
60
|
+
export interface BuildConfigurationsConstructorParameters {
|
|
61
|
+
/**
|
|
62
|
+
* The Liquid templating engine for variable substitution.
|
|
63
|
+
*/
|
|
64
|
+
engine: LiquidEngine
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The variables available for substitution in configuration definitions.
|
|
68
|
+
*/
|
|
69
|
+
substitutionsVariables: LiquidSubstitutionsVariables
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The JSON build configurations definitions, or undefined if no build
|
|
73
|
+
* configurations are defined.
|
|
74
|
+
*/
|
|
75
|
+
jsonBuildConfigurations: JsonBuildConfigurations | undefined
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The logger instance for output and diagnostics.
|
|
79
|
+
*/
|
|
80
|
+
log: Logger
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* A collection of <b>xpm</b> build configurations.
|
|
85
|
+
*
|
|
86
|
+
* @remarks
|
|
87
|
+
* This class manages build configurations defined in package metadata,
|
|
88
|
+
* including template expansion with matrix parameters and initialisation of
|
|
89
|
+
* derived configuration instances.
|
|
90
|
+
*
|
|
91
|
+
* Configuration lifecycle phases:
|
|
92
|
+
*
|
|
93
|
+
* <ol>
|
|
94
|
+
* <li><b>Construction:</b> Basic setup without processing configurations.</li>
|
|
95
|
+
* <li><b>Initialisation:</b> Template name expansion without content
|
|
96
|
+
* evaluation.</li>
|
|
97
|
+
* <li><b>Retrieval:</b> On-demand instantiation when accessed via
|
|
98
|
+
* <code>get()</code>.</li>
|
|
99
|
+
* <li><b>Configuration Initialisation:</b> Full processing including
|
|
100
|
+
* inheritance, property resolution, dependency substitution, and
|
|
101
|
+
* action preparation.</li>
|
|
102
|
+
* </ol>
|
|
103
|
+
*
|
|
104
|
+
* This lazy evaluation strategy ensures that only configurations actually
|
|
105
|
+
* used incur the cost of template evaluation, inheritance resolution, and
|
|
106
|
+
* variable substitution.
|
|
107
|
+
*/
|
|
108
|
+
export class BuildConfigurations {
|
|
109
|
+
// --------------------------------------------------------------------------
|
|
110
|
+
// Public Members.
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The logger instance for output and diagnostics.
|
|
114
|
+
*
|
|
115
|
+
* @remarks
|
|
116
|
+
* This logger provides trace-level diagnostics throughout the build
|
|
117
|
+
* configuration lifecycle, including template expansion, inheritance
|
|
118
|
+
* resolution, property merging, and dependency substitution. It enables
|
|
119
|
+
* detailed debugging of complex build configuration hierarchies without
|
|
120
|
+
* impacting runtime performance when tracing is disabled.
|
|
121
|
+
*/
|
|
122
|
+
readonly log: Logger
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* The Liquid templating engine for variable substitution.
|
|
126
|
+
*
|
|
127
|
+
* @remarks
|
|
128
|
+
* This engine instance is shared across all build configurations and
|
|
129
|
+
* configured with custom filters for platform detection, path
|
|
130
|
+
* manipulation, and xpm-specific operations. It processes templates in
|
|
131
|
+
* configuration names, matrix parameters, properties, dependencies, and
|
|
132
|
+
* actions, ensuring consistent template evaluation throughout the
|
|
133
|
+
* configuration lifecycle.
|
|
134
|
+
*/
|
|
135
|
+
readonly engine: LiquidEngine
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* The variables available for substitution in configuration definitions.
|
|
139
|
+
*
|
|
140
|
+
* @remarks
|
|
141
|
+
* This comprehensive variable hierarchy provides the base context for all
|
|
142
|
+
* build configuration template evaluation, extended per-configuration with
|
|
143
|
+
* specific properties, dependencies, and matrix parameters.
|
|
144
|
+
*
|
|
145
|
+
* Base hierarchy includes:
|
|
146
|
+
*
|
|
147
|
+
* <ol>
|
|
148
|
+
* <li><b>Environment variables:</b> <code>env</code> namespace with system
|
|
149
|
+
* environment.</li>
|
|
150
|
+
* <li><b>Platform detection:</b> <code>os</code> namespace with
|
|
151
|
+
* platform-specific values.</li>
|
|
152
|
+
* <li><b>Path utilities:</b> <code>path</code> namespace with path
|
|
153
|
+
* manipulationfunctions.</li>
|
|
154
|
+
* <li><b>Package metadata:</b> <code>package</code> namespace with
|
|
155
|
+
* name, version, dependencies.</li>
|
|
156
|
+
* </ol>
|
|
157
|
+
*
|
|
158
|
+
* Individual configurations extend this with their own `properties`,
|
|
159
|
+
* `configuration`, and `matrix` namespaces during initialisation.
|
|
160
|
+
*/
|
|
161
|
+
readonly substitutionsVariables: LiquidSubstitutionsVariables
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* The JSON object containing build configuration definitions.
|
|
165
|
+
*
|
|
166
|
+
* @remarks
|
|
167
|
+
* This object holds raw build configuration definitions from the
|
|
168
|
+
* `package.json` `xpack.buildConfigurations` section. Configurations can be:
|
|
169
|
+
*
|
|
170
|
+
* <ol>
|
|
171
|
+
* <li><b>Regular configurations:</b> Direct objects with properties,
|
|
172
|
+
* dependencies, actions, and inheritance.</li>
|
|
173
|
+
* <li><b>Template configurations:</b> Objects with <code>matrix</code>
|
|
174
|
+
* and <code>template</code>
|
|
175
|
+
* properties for generating multiple configurations from a single
|
|
176
|
+
* definition.</li>
|
|
177
|
+
* </ol>
|
|
178
|
+
*
|
|
179
|
+
* Template configuration names (containing `{{` markers) trigger matrix
|
|
180
|
+
* expansion during initialisation, creating concrete configurations from
|
|
181
|
+
* the Cartesian product of matrix parameter values. Each configuration
|
|
182
|
+
* can inherit from others, creating complex dependency hierarchies.
|
|
183
|
+
*/
|
|
184
|
+
readonly jsonBuildConfigurations: JsonBuildConfigurations
|
|
185
|
+
|
|
186
|
+
// --------------------------------------------------------------------------
|
|
187
|
+
// Protected Members.
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Map of build configuration names to their corresponding instances.
|
|
191
|
+
*
|
|
192
|
+
* @remarks
|
|
193
|
+
* This map serves as the primary configuration registry, populated during
|
|
194
|
+
* collection initialisation with entries for all discovered configurations.
|
|
195
|
+
*
|
|
196
|
+
* Key characteristics:
|
|
197
|
+
*
|
|
198
|
+
* <ol>
|
|
199
|
+
* <li>Known only after <code>BuildConfigurations.initialise()</code>
|
|
200
|
+
* completes.</li>
|
|
201
|
+
* <li>Possibly empty if there are no build configurations defined.</li>
|
|
202
|
+
* <li>Values can be <code>undefined</code> to indicate a configuration
|
|
203
|
+
* exists but hasn't been instantiated yet (lazy loading).</li>
|
|
204
|
+
* <li>For template configurations, contains one entry per expanded
|
|
205
|
+
* combination, not the original template definition.</li>
|
|
206
|
+
* </ol>
|
|
207
|
+
*
|
|
208
|
+
* Configurations transition from `undefined` to instantiated when first
|
|
209
|
+
* accessed via {@link BuildConfigurations.get}, implementing the
|
|
210
|
+
* lazy evaluation pattern to avoid unnecessary processing.
|
|
211
|
+
*/
|
|
212
|
+
protected readonly _buildConfigurationsMap: Map<
|
|
213
|
+
string,
|
|
214
|
+
BuildConfiguration | undefined
|
|
215
|
+
> = new Map<string, BuildConfiguration | undefined>()
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Map of expanded build configuration names to their JSON source names.
|
|
219
|
+
*
|
|
220
|
+
* @remarks
|
|
221
|
+
* This reverse mapping enables retrieving the original configuration
|
|
222
|
+
* definition from `jsonBuildConfigurations` when lazy-loading
|
|
223
|
+
* configuration instances.
|
|
224
|
+
*
|
|
225
|
+
* Mapping behavior:
|
|
226
|
+
*
|
|
227
|
+
* <ol>
|
|
228
|
+
* <li><b>For regular configurations:</b> Maps configuration name to itself
|
|
229
|
+
* (identity mapping).</li>
|
|
230
|
+
* <li><b>For template configurations:</b> Maps each generated configuration
|
|
231
|
+
* name
|
|
232
|
+
* back to the original template name (e.g., <code>release-x64</code> →
|
|
233
|
+
* <code>release-\{\{ matrix.arch \}\}</code>).</li>
|
|
234
|
+
* <li>Known only after <code>BuildConfigurations.initialise()</code>
|
|
235
|
+
* completes.</li>
|
|
236
|
+
* <li>Enables <code>BuildConfigurations.get()</code> to locate the
|
|
237
|
+
* correct JSON definition when instantiating a configuration on
|
|
238
|
+
* demand.</li>
|
|
239
|
+
* </ol>
|
|
240
|
+
*
|
|
241
|
+
* This indirection is essential for lazy evaluation, allowing deferred
|
|
242
|
+
* instantiation while maintaining the connection to original definitions.
|
|
243
|
+
*/
|
|
244
|
+
protected readonly _jsonBuildConfigurationsNamesMap: Map<string, string> =
|
|
245
|
+
new Map<string, string>()
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Set of all build configuration names for duplicate detection.
|
|
249
|
+
*
|
|
250
|
+
* @remarks
|
|
251
|
+
* This set provides O(1) existence checks for configuration names,
|
|
252
|
+
* enabling efficient validation during template expansion to prevent
|
|
253
|
+
* duplicate configurations.
|
|
254
|
+
*
|
|
255
|
+
* Duplicate scenarios detected:
|
|
256
|
+
*
|
|
257
|
+
* <ol>
|
|
258
|
+
* <li>Explicit duplicates in <code>package.json</code> with identical
|
|
259
|
+
* names.</li>
|
|
260
|
+
* <li>Template expansion conflicts where different templates generate the
|
|
261
|
+
* same concrete configuration name.</li>
|
|
262
|
+
* <li>Conflicts between template-generated names and explicitly defined
|
|
263
|
+
* configuration names.</li>
|
|
264
|
+
* </ol>
|
|
265
|
+
*
|
|
266
|
+
* Detection occurs during {@link BuildConfigurations.initialise},
|
|
267
|
+
* throwing {@link ConfigurationError} when duplicates are found to ensure
|
|
268
|
+
* configuration name uniqueness.
|
|
269
|
+
*/
|
|
270
|
+
protected readonly _namesSet: Set<string> = new Set<string>()
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Flag indicating whether the collection has been initialised.
|
|
274
|
+
*
|
|
275
|
+
* @remarks
|
|
276
|
+
* This flag prevents redundant initialisation and ensures idempotent
|
|
277
|
+
* behavior when {@link BuildConfigurations.initialise} is called
|
|
278
|
+
* multiple times.
|
|
279
|
+
*
|
|
280
|
+
* State transitions:
|
|
281
|
+
*
|
|
282
|
+
* <ol>
|
|
283
|
+
* <li>Initially <code>false</code> after construction.</li>
|
|
284
|
+
* <li>Set to <code>true</code> after successful template expansion
|
|
285
|
+
* and configuration
|
|
286
|
+
* name registration.</li>
|
|
287
|
+
* <li>Checked at the beginning of
|
|
288
|
+
* <code>BuildConfigurations.initialise()</code> to return early if
|
|
289
|
+
* already initialised.</li>
|
|
290
|
+
* </ol>
|
|
291
|
+
*
|
|
292
|
+
* This pattern supports safe repeated calls during complex initialisation
|
|
293
|
+
* sequences without duplicating work or corrupting internal state.
|
|
294
|
+
*/
|
|
295
|
+
protected _isInitialised = false
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Cached array of all build configuration names in the collection.
|
|
299
|
+
*
|
|
300
|
+
* @remarks
|
|
301
|
+
* This array provides O(1) access to configuration names without
|
|
302
|
+
* repeatedly creating new arrays from the map keys, improving performance
|
|
303
|
+
* when the names are accessed multiple times.
|
|
304
|
+
*
|
|
305
|
+
* Key characteristics:
|
|
306
|
+
*
|
|
307
|
+
* <ol>
|
|
308
|
+
* <li>Empty initially after construction.</li>
|
|
309
|
+
* <li>Populated during
|
|
310
|
+
* <code>BuildConfigurations.initialise()</code> after all
|
|
311
|
+
* configuration names are determined.</li>
|
|
312
|
+
* <li>Contains all configuration names including those generated from
|
|
313
|
+
* templates.</li>
|
|
314
|
+
* <li>Returned by the <code>names</code> getter for efficient repeated
|
|
315
|
+
* access.</li>
|
|
316
|
+
* </ol>
|
|
317
|
+
*
|
|
318
|
+
* This cached approach avoids the overhead of calling
|
|
319
|
+
* `Array.from(map.keys())` on every access whilst still
|
|
320
|
+
* providing a clean getter interface.
|
|
321
|
+
*/
|
|
322
|
+
protected _names: string[] = []
|
|
323
|
+
|
|
324
|
+
// --------------------------------------------------------------------------
|
|
325
|
+
// Constructor and async initialiser.
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Constructs a build configurations collection.
|
|
329
|
+
*
|
|
330
|
+
* @remarks
|
|
331
|
+
* The constructor performs partial initialisation. Complete
|
|
332
|
+
* initialisation requires calling
|
|
333
|
+
* {@link BuildConfigurations.initialise}.
|
|
334
|
+
*
|
|
335
|
+
* @param engine - The Liquid templating engine for variable substitution.
|
|
336
|
+
* @param substitutionsVariables - The variables available for substitution.
|
|
337
|
+
* @param jsonBuildConfigurations - The JSON build configurations definitions,
|
|
338
|
+
* or undefined if no build configurations are defined.
|
|
339
|
+
* @param log - The logger instance for output and diagnostics.
|
|
340
|
+
*/
|
|
341
|
+
constructor({
|
|
342
|
+
engine,
|
|
343
|
+
substitutionsVariables,
|
|
344
|
+
jsonBuildConfigurations,
|
|
345
|
+
log,
|
|
346
|
+
}: BuildConfigurationsConstructorParameters) {
|
|
347
|
+
assert(log, 'log is required')
|
|
348
|
+
assert(engine, 'engine is required')
|
|
349
|
+
assert(substitutionsVariables, 'substitutionsVariables is required')
|
|
350
|
+
|
|
351
|
+
log.trace(`${BuildConfigurations.name}()`)
|
|
352
|
+
|
|
353
|
+
this.log = log
|
|
354
|
+
this.engine = engine
|
|
355
|
+
this.substitutionsVariables = substitutionsVariables
|
|
356
|
+
this.jsonBuildConfigurations = jsonBuildConfigurations ?? {}
|
|
357
|
+
|
|
358
|
+
// log.trace('substitutionsVariables => ', this.substitutionsVariables)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Completes the async initialisation of the build configurations collection.
|
|
363
|
+
*
|
|
364
|
+
* @remarks
|
|
365
|
+
* This method implements the first step of lazy evaluation. It processes
|
|
366
|
+
* all build configuration definitions by expanding template configuration
|
|
367
|
+
* names based on matrix parameters, but does not evaluate the configuration
|
|
368
|
+
* content or perform Liquid substitutions. The actual template evaluation
|
|
369
|
+
* and variable substitution occur later when individual configurations are
|
|
370
|
+
* initialised via {@link BuildConfiguration.initialise}, and only
|
|
371
|
+
* for configurations that are actually used. This approach avoids unnecessary
|
|
372
|
+
* operations on unused configurations.
|
|
373
|
+
*
|
|
374
|
+
* Processing steps:
|
|
375
|
+
*
|
|
376
|
+
* <ol>
|
|
377
|
+
* <li>Return early if already initialised (idempotent behaviour).</li>
|
|
378
|
+
* <li>Iterate through all build configuration definitions from the JSON
|
|
379
|
+
* object.</li>
|
|
380
|
+
* <li>For template configurations (names containing <code>\{\{</code>):
|
|
381
|
+
* <ul>
|
|
382
|
+
* <li>Call <code>_processTemplate()</code> to expand and register all
|
|
383
|
+
* generated configurations.</li>
|
|
384
|
+
* </ul>
|
|
385
|
+
* </li>
|
|
386
|
+
* <li>For regular configurations:
|
|
387
|
+
* <ul>
|
|
388
|
+
* <li>Validate uniqueness of the configuration name.</li>
|
|
389
|
+
* <li>Register the configuration in internal maps with
|
|
390
|
+
* <code>undefined</code> value (lazy loading).</li>
|
|
391
|
+
* </ul>
|
|
392
|
+
* </li>
|
|
393
|
+
* <li>Cache the array of all configuration names for efficient repeated
|
|
394
|
+
* access.</li>
|
|
395
|
+
* </ol>
|
|
396
|
+
*
|
|
397
|
+
* @returns A promise that resolves to `true` if initialisation was performed,
|
|
398
|
+
* or `false` if already initialised.
|
|
399
|
+
*
|
|
400
|
+
* @throws {@link ConfigurationError}
|
|
401
|
+
* If duplicate names are detected or template expansion fails.
|
|
402
|
+
*/
|
|
403
|
+
async initialise(): Promise<boolean> {
|
|
404
|
+
const log = this.log
|
|
405
|
+
|
|
406
|
+
if (this._isInitialised) {
|
|
407
|
+
log.trace(`${BuildConfigurations.name}.initialise() again`)
|
|
408
|
+
return false
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
log.trace(`${BuildConfigurations.name}.initialise()`)
|
|
412
|
+
|
|
413
|
+
for (const [
|
|
414
|
+
buildConfigurationName,
|
|
415
|
+
jsonBuildConfiguration,
|
|
416
|
+
] of Object.entries(this.jsonBuildConfigurations)) {
|
|
417
|
+
if (hasLiquidSyntax(buildConfigurationName)) {
|
|
418
|
+
await this._processTemplate({
|
|
419
|
+
buildConfigurationName,
|
|
420
|
+
jsonBuildConfigurationTemplate:
|
|
421
|
+
jsonBuildConfiguration as JsonBuildConfigurationTemplate,
|
|
422
|
+
})
|
|
423
|
+
} else {
|
|
424
|
+
if (this._namesSet.has(buildConfigurationName)) {
|
|
425
|
+
throw new ConfigurationError(
|
|
426
|
+
`build configuration name ` +
|
|
427
|
+
`"${buildConfigurationName}" already defined`
|
|
428
|
+
)
|
|
429
|
+
} else {
|
|
430
|
+
this._buildConfigurationsMap.set(buildConfigurationName, undefined)
|
|
431
|
+
this._jsonBuildConfigurationsNamesMap.set(
|
|
432
|
+
buildConfigurationName,
|
|
433
|
+
buildConfigurationName
|
|
434
|
+
)
|
|
435
|
+
this._namesSet.add(buildConfigurationName)
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const names = Array.from(this._buildConfigurationsMap.keys())
|
|
441
|
+
this._names = names
|
|
442
|
+
|
|
443
|
+
log.trace(`${BuildConfigurations.name}.initialise() =>`, names)
|
|
444
|
+
|
|
445
|
+
this._isInitialised = true
|
|
446
|
+
return true
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// --------------------------------------------------------------------------
|
|
450
|
+
// Public Methods.
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* The number of build configurations in the collection.
|
|
454
|
+
*
|
|
455
|
+
* @remarks
|
|
456
|
+
* This value is known only after `initialise()`.
|
|
457
|
+
*
|
|
458
|
+
* This getter provides direct access to the collection size, enabling
|
|
459
|
+
* callers to check for emptiness or iterate with knowledge of the
|
|
460
|
+
* collection's extent.
|
|
461
|
+
*
|
|
462
|
+
* @returns The number of build configurations in the collection.
|
|
463
|
+
*/
|
|
464
|
+
get size(): number {
|
|
465
|
+
assert(
|
|
466
|
+
this._isInitialised,
|
|
467
|
+
'BuildConfigurations collection must be initialised before ' +
|
|
468
|
+
'accessing size'
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
return this._buildConfigurationsMap.size
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Indicates whether the collection is empty.
|
|
476
|
+
*
|
|
477
|
+
* @remarks
|
|
478
|
+
* This value is known only after `initialise()`.
|
|
479
|
+
*
|
|
480
|
+
* @returns `true` if there are no build configurations, `false` otherwise.
|
|
481
|
+
*/
|
|
482
|
+
get isEmpty(): boolean {
|
|
483
|
+
assert(
|
|
484
|
+
this._isInitialised,
|
|
485
|
+
'BuildConfigurations collection must be initialised before ' +
|
|
486
|
+
'accessing isEmpty'
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
return this._buildConfigurationsMap.size === 0
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* The names of all build configurations.
|
|
494
|
+
*
|
|
495
|
+
* @remarks
|
|
496
|
+
* This value is known only after `initialise()`.
|
|
497
|
+
*
|
|
498
|
+
* This getter returns the cached array of configuration names for
|
|
499
|
+
* efficient repeated access without recreating the array.
|
|
500
|
+
*
|
|
501
|
+
* @returns An array of build configuration names.
|
|
502
|
+
*/
|
|
503
|
+
get names(): string[] {
|
|
504
|
+
assert(
|
|
505
|
+
this._isInitialised,
|
|
506
|
+
'BuildConfigurations collection must be initialised before ' +
|
|
507
|
+
'accessing names'
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
return this._names
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Retrieves the JSON configuration name for a build configuration.
|
|
515
|
+
*
|
|
516
|
+
* @param buildConfigurationName - The build configuration name to resolve.
|
|
517
|
+
* @returns The JSON configuration name associated with the given build
|
|
518
|
+
* configuration name.
|
|
519
|
+
*
|
|
520
|
+
* @remarks
|
|
521
|
+
* For template-generated configurations, this returns the template
|
|
522
|
+
* name.
|
|
523
|
+
*
|
|
524
|
+
* @throws {@link InputError}
|
|
525
|
+
* If the build configuration does not exist.
|
|
526
|
+
*/
|
|
527
|
+
getJsonName(buildConfigurationName: string): string {
|
|
528
|
+
assert(
|
|
529
|
+
this._isInitialised,
|
|
530
|
+
'BuildConfigurations collection must be initialised before ' +
|
|
531
|
+
'accessing getJsonName()'
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
const name = this._jsonBuildConfigurationsNamesMap.get(
|
|
535
|
+
buildConfigurationName
|
|
536
|
+
)
|
|
537
|
+
if (name === undefined) {
|
|
538
|
+
throw new ConfigurationError(
|
|
539
|
+
`build configuration "${buildConfigurationName}" does not exist`
|
|
540
|
+
)
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
return name
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Determines whether a JSON definition exists for a build configuration.
|
|
548
|
+
*
|
|
549
|
+
* @param buildConfigurationName - The build configuration name to check.
|
|
550
|
+
* @returns `true` if a JSON definition exists, `false` otherwise.
|
|
551
|
+
*/
|
|
552
|
+
hasJson(buildConfigurationName: string): boolean {
|
|
553
|
+
assert(
|
|
554
|
+
this._isInitialised,
|
|
555
|
+
'BuildConfigurations collection must be initialised before ' +
|
|
556
|
+
'accessing hasJson()'
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
return this._jsonBuildConfigurationsNamesMap.has(buildConfigurationName)
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Retrieves the JSON build configuration definition.
|
|
564
|
+
*
|
|
565
|
+
* @param buildConfigurationName - The build configuration name to resolve.
|
|
566
|
+
* @returns The JSON build configuration definition.
|
|
567
|
+
*/
|
|
568
|
+
getJson(buildConfigurationName: string): JsonBuildConfiguration {
|
|
569
|
+
assert(
|
|
570
|
+
this._isInitialised,
|
|
571
|
+
'BuildConfigurations collection must be initialised before ' +
|
|
572
|
+
'accessing getJson()'
|
|
573
|
+
)
|
|
574
|
+
|
|
575
|
+
return this.jsonBuildConfigurations[
|
|
576
|
+
this.getJsonName(buildConfigurationName)
|
|
577
|
+
]
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* Determines whether a build configuration is hidden.
|
|
582
|
+
*
|
|
583
|
+
* @param buildConfigurationName - The build configuration name to check.
|
|
584
|
+
* @returns `true` if the configuration is hidden, `false` otherwise.
|
|
585
|
+
*/
|
|
586
|
+
isHidden(buildConfigurationName: string): boolean {
|
|
587
|
+
assert(
|
|
588
|
+
this._isInitialised,
|
|
589
|
+
'BuildConfigurations collection must be initialised before ' +
|
|
590
|
+
'accessing isHidden()'
|
|
591
|
+
)
|
|
592
|
+
|
|
593
|
+
const jsonBuildConfigurationName = this.getJsonName(buildConfigurationName)
|
|
594
|
+
if (jsonBuildConfigurationName.includes('{{')) {
|
|
595
|
+
const jsonBuildConfigurationTemplate: JsonBuildConfigurationTemplate =
|
|
596
|
+
this.jsonBuildConfigurations[
|
|
597
|
+
jsonBuildConfigurationName
|
|
598
|
+
] as JsonBuildConfigurationTemplate
|
|
599
|
+
return jsonBuildConfigurationTemplate.template.hidden ?? false
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
const jsonBuildConfigurationContent: JsonBuildConfigurationContent = this
|
|
603
|
+
.jsonBuildConfigurations[
|
|
604
|
+
jsonBuildConfigurationName
|
|
605
|
+
] as JsonBuildConfigurationContent
|
|
606
|
+
return jsonBuildConfigurationContent.hidden ?? false
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Determines whether a build configuration exists in the collection.
|
|
611
|
+
*
|
|
612
|
+
* @param buildConfigurationName - The build configuration name to check.
|
|
613
|
+
* @returns `true` if the configuration exists, `false` otherwise.
|
|
614
|
+
*/
|
|
615
|
+
has(buildConfigurationName: string): boolean {
|
|
616
|
+
assert(
|
|
617
|
+
this._isInitialised,
|
|
618
|
+
'BuildConfigurations collection must be initialised before ' +
|
|
619
|
+
'accessing has()'
|
|
620
|
+
)
|
|
621
|
+
|
|
622
|
+
return this._buildConfigurationsMap.has(buildConfigurationName)
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* Retrieves a build configuration by name, creating it if required.
|
|
627
|
+
*
|
|
628
|
+
* @remarks
|
|
629
|
+
* This method implements lazy evaluation to avoid unnecessary
|
|
630
|
+
* operations. Build configurations are instantiated on demand but
|
|
631
|
+
* remain uninitialised until actually used.
|
|
632
|
+
*
|
|
633
|
+
* Retrieval process:
|
|
634
|
+
*
|
|
635
|
+
* <ol>
|
|
636
|
+
* <li>Check if the configuration already exists in the internal map.</li>
|
|
637
|
+
* <li>If found and already instantiated, return the existing instance.</li>
|
|
638
|
+
* <li>If the configuration name is unknown (not in JSON name mapping),
|
|
639
|
+
* throw <code>InputError</code>.</li>
|
|
640
|
+
* <li>For known but not yet instantiated configurations:
|
|
641
|
+
* <ul>
|
|
642
|
+
* <li>Resolve the original JSON configuration name (handles both
|
|
643
|
+
* regular and template-generated configurations).</li>
|
|
644
|
+
* <li>Retrieve the JSON configuration definition.</li>
|
|
645
|
+
* <li>Create a new <code>BuildConfiguration</code> instance.</li>
|
|
646
|
+
* <li>Store the instance in the map for future access.</li>
|
|
647
|
+
* </ul>
|
|
648
|
+
* </li>
|
|
649
|
+
* <li>Return the configuration instance (still uninitialised).</li>
|
|
650
|
+
* </ol>
|
|
651
|
+
*
|
|
652
|
+
* The two-step lazy evaluation process:
|
|
653
|
+
*
|
|
654
|
+
* <ol>
|
|
655
|
+
* <li>During collection initialisation
|
|
656
|
+
* (<code>BuildConfigurations.initialise()</code>), only the
|
|
657
|
+
* matrix of options is evaluated for each template, expanding
|
|
658
|
+
* configuration names without processing their content.</li>
|
|
659
|
+
* <li>Later, when a configuration is accessed via this method and
|
|
660
|
+
* subsequently initialised
|
|
661
|
+
* (<code>BuildConfiguration.initialise()</code>), the template
|
|
662
|
+
* is fully evaluated and Liquid substitutions are performed on
|
|
663
|
+
* all properties.</li>
|
|
664
|
+
* </ol>
|
|
665
|
+
*
|
|
666
|
+
* This approach ensures that only build configurations that are
|
|
667
|
+
* actually used incur the cost of template evaluation and variable
|
|
668
|
+
* substitution.
|
|
669
|
+
*
|
|
670
|
+
* @param buildConfigurationName - The build configuration name to retrieve.
|
|
671
|
+
* @returns The build configuration instance.
|
|
672
|
+
*
|
|
673
|
+
* @throws {@link InputError}
|
|
674
|
+
* If a configuration with the specified name does not exist.
|
|
675
|
+
*/
|
|
676
|
+
get(buildConfigurationName: string): BuildConfiguration {
|
|
677
|
+
assert(
|
|
678
|
+
this._isInitialised,
|
|
679
|
+
'BuildConfigurations collection must be initialised before ' +
|
|
680
|
+
'accessing get()'
|
|
681
|
+
)
|
|
682
|
+
|
|
683
|
+
const log = this.log
|
|
684
|
+
log.trace(`${BuildConfigurations.name}.get(${buildConfigurationName})`)
|
|
685
|
+
|
|
686
|
+
let buildConfiguration = this._buildConfigurationsMap.get(
|
|
687
|
+
buildConfigurationName
|
|
688
|
+
)
|
|
689
|
+
if (buildConfiguration === undefined) {
|
|
690
|
+
// This will throw InputError if the configuration doesn't exist
|
|
691
|
+
const jsonBuildConfigurationName: string = this.getJsonName(
|
|
692
|
+
buildConfigurationName
|
|
693
|
+
)
|
|
694
|
+
|
|
695
|
+
// Safety net: This fallback to empty object is defensive programming.
|
|
696
|
+
// The jsonBuildConfigurations[jsonBuildConfigurationName] should always
|
|
697
|
+
// be defined because getJsonName() throws if the configuration doesn't
|
|
698
|
+
// exist. The ?? {} provides protection against unexpected inconsistencies
|
|
699
|
+
// between the names map and the configurations object.
|
|
700
|
+
/* c8 ignore start - safety net, they are always defined */
|
|
701
|
+
const jsonBuildConfiguration: JsonBuildConfigurationContent = (this
|
|
702
|
+
.jsonBuildConfigurations[jsonBuildConfigurationName] ??
|
|
703
|
+
{}) as JsonBuildConfigurationContent
|
|
704
|
+
/* c8 ignore stop */
|
|
705
|
+
|
|
706
|
+
buildConfiguration = new BuildConfiguration({
|
|
707
|
+
buildConfigurationName,
|
|
708
|
+
jsonBuildConfiguration,
|
|
709
|
+
parentBuildConfigurations: this,
|
|
710
|
+
})
|
|
711
|
+
this._buildConfigurationsMap.set(
|
|
712
|
+
buildConfigurationName,
|
|
713
|
+
buildConfiguration
|
|
714
|
+
)
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// await buildConfiguration.initialise()
|
|
718
|
+
return buildConfiguration
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
// --------------------------------------------------------------------------
|
|
722
|
+
// Private Methods.
|
|
723
|
+
|
|
724
|
+
/**
|
|
725
|
+
* Processes a template build configuration by expanding it and registering
|
|
726
|
+
* the generated configurations.
|
|
727
|
+
*
|
|
728
|
+
* @remarks
|
|
729
|
+
* This helper method is called during collection initialisation for each
|
|
730
|
+
* build configuration whose name contains template syntax
|
|
731
|
+
* (<code>\{\{</code> markers).
|
|
732
|
+
*
|
|
733
|
+
* Processing steps:
|
|
734
|
+
*
|
|
735
|
+
* <ol>
|
|
736
|
+
* <li>Calls <code>_expandTemplateBuildConfigurations()</code> to generate
|
|
737
|
+
* all configuration instances from the template's matrix parameters.</li>
|
|
738
|
+
* <li>Validates that each expanded configuration name is unique and does
|
|
739
|
+
* not conflict with existing configurations.</li>
|
|
740
|
+
* <li>Registers each expanded configuration in the internal maps:
|
|
741
|
+
* <ul>
|
|
742
|
+
* <li><code>_buildConfigurationsMap</code>: Maps name to configuration
|
|
743
|
+
* instance.</li>
|
|
744
|
+
* <li><code>_jsonBuildConfigurationsNamesMap</code>: Maps expanded name
|
|
745
|
+
* back to original template name.</li>
|
|
746
|
+
* <li><code>_namesSet</code>: Tracks all registered
|
|
747
|
+
* names for duplicate detection.</li>
|
|
748
|
+
* </ul>
|
|
749
|
+
* </li>
|
|
750
|
+
* </ol>
|
|
751
|
+
*
|
|
752
|
+
* @param buildConfigurationName - The template configuration name
|
|
753
|
+
* containing Liquid variables.
|
|
754
|
+
* @param jsonBuildConfiguration - The JSON template definition containing
|
|
755
|
+
* matrix parameters and a configuration template.
|
|
756
|
+
* @returns A promise that resolves when processing is complete.
|
|
757
|
+
*
|
|
758
|
+
* @throws {@link ConfigurationError}
|
|
759
|
+
* If duplicate configuration names are detected during expansion or if
|
|
760
|
+
* template expansion fails.
|
|
761
|
+
*/
|
|
762
|
+
protected async _processTemplate({
|
|
763
|
+
buildConfigurationName,
|
|
764
|
+
jsonBuildConfigurationTemplate,
|
|
765
|
+
}: {
|
|
766
|
+
buildConfigurationName: string
|
|
767
|
+
jsonBuildConfigurationTemplate: JsonBuildConfigurationTemplate
|
|
768
|
+
}): Promise<void> {
|
|
769
|
+
// Expand templates and generate multiple build configurations.
|
|
770
|
+
try {
|
|
771
|
+
const expandedBuildConfigurationsMap =
|
|
772
|
+
await this._expandTemplateBuildConfigurations({
|
|
773
|
+
buildConfigurationName,
|
|
774
|
+
jsonBuildConfigurationTemplate,
|
|
775
|
+
})
|
|
776
|
+
for (const [
|
|
777
|
+
expandedBuildConfigurationName,
|
|
778
|
+
expandedBuildConfiguration,
|
|
779
|
+
] of expandedBuildConfigurationsMap) {
|
|
780
|
+
if (this._namesSet.has(expandedBuildConfigurationName)) {
|
|
781
|
+
throw new ConfigurationError(
|
|
782
|
+
`duplicate build configuration name ` +
|
|
783
|
+
`"${expandedBuildConfigurationName}" ` +
|
|
784
|
+
`could not be generated from template.`
|
|
785
|
+
)
|
|
786
|
+
} else {
|
|
787
|
+
this._buildConfigurationsMap.set(
|
|
788
|
+
expandedBuildConfigurationName,
|
|
789
|
+
expandedBuildConfiguration
|
|
790
|
+
)
|
|
791
|
+
this._jsonBuildConfigurationsNamesMap.set(
|
|
792
|
+
expandedBuildConfigurationName,
|
|
793
|
+
buildConfigurationName
|
|
794
|
+
)
|
|
795
|
+
this._namesSet.add(expandedBuildConfigurationName)
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
} catch (error) {
|
|
799
|
+
const message =
|
|
800
|
+
getErrorMessage(error) +
|
|
801
|
+
` in buildConfiguration "${buildConfigurationName}"`
|
|
802
|
+
throw new ConfigurationError(message)
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
/**
|
|
807
|
+
* Expands a template build configuration into multiple configurations.
|
|
808
|
+
*
|
|
809
|
+
* @remarks
|
|
810
|
+
* This method uses the {@link TemplateExpander} to compute the Cartesian
|
|
811
|
+
* product of matrix parameter values and creates a configuration for each
|
|
812
|
+
* combination, substituting matrix values into both the configuration name
|
|
813
|
+
* and content.
|
|
814
|
+
*
|
|
815
|
+
* Processing steps:
|
|
816
|
+
*
|
|
817
|
+
* <ol>
|
|
818
|
+
* <li>Validates matrix and template structure.</li>
|
|
819
|
+
* <li>Delegates to <code>TemplateExpander</code> for matrix processing and
|
|
820
|
+
* name expansion.</li>
|
|
821
|
+
* <li>Creates configuration instances via factory callback for each
|
|
822
|
+
* combination.</li>
|
|
823
|
+
* </ol>
|
|
824
|
+
*
|
|
825
|
+
* Matrix variables are scoped to individual configurations and accessible
|
|
826
|
+
* via the `matrix` namespace during property, dependency, and action
|
|
827
|
+
* evaluation.
|
|
828
|
+
*
|
|
829
|
+
* @param buildConfigurationName - The template configuration name containing
|
|
830
|
+
* Liquid variables.
|
|
831
|
+
* @param jsonBuildConfigurationTemplate - The template definition containing
|
|
832
|
+
* matrix parameters and a configuration template.
|
|
833
|
+
* @returns A promise that resolves to a map of expanded configuration names
|
|
834
|
+
* to their corresponding instances.
|
|
835
|
+
*
|
|
836
|
+
* @throws {@link ConfigurationError}
|
|
837
|
+
* If the matrix structure is invalid or substitution fails.
|
|
838
|
+
*/
|
|
839
|
+
protected async _expandTemplateBuildConfigurations({
|
|
840
|
+
buildConfigurationName,
|
|
841
|
+
jsonBuildConfigurationTemplate,
|
|
842
|
+
}: {
|
|
843
|
+
buildConfigurationName: string
|
|
844
|
+
jsonBuildConfigurationTemplate: JsonBuildConfigurationTemplate
|
|
845
|
+
}): Promise<Map<string, BuildConfiguration>> {
|
|
846
|
+
const log = this.log
|
|
847
|
+
log.trace(
|
|
848
|
+
`${BuildConfigurations.name}.` +
|
|
849
|
+
`#expandTemplateBuildConfigurations(${buildConfigurationName})`
|
|
850
|
+
)
|
|
851
|
+
|
|
852
|
+
// Validate template structure
|
|
853
|
+
if (!isJsonObject(jsonBuildConfigurationTemplate.matrix)) {
|
|
854
|
+
throw new ConfigurationError(
|
|
855
|
+
`buildConfiguration "${buildConfigurationName}" ` +
|
|
856
|
+
`matrix is not an object`
|
|
857
|
+
)
|
|
858
|
+
}
|
|
859
|
+
if (!isJsonObject(jsonBuildConfigurationTemplate.template)) {
|
|
860
|
+
throw new ConfigurationError(
|
|
861
|
+
`buildConfiguration "${buildConfigurationName}" ` +
|
|
862
|
+
`template is not a JSON object`
|
|
863
|
+
)
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// Use TemplateExpander for matrix processing and expansion
|
|
867
|
+
const expander = new TemplateExpander<
|
|
868
|
+
JsonBuildConfigurationContent,
|
|
869
|
+
BuildConfiguration
|
|
870
|
+
>({
|
|
871
|
+
engine: this.engine,
|
|
872
|
+
substitutionsVariables: this.substitutionsVariables,
|
|
873
|
+
log: this.log,
|
|
874
|
+
})
|
|
875
|
+
|
|
876
|
+
return await expander.expandTemplate({
|
|
877
|
+
templateName: buildConfigurationName,
|
|
878
|
+
matrix: jsonBuildConfigurationTemplate.matrix,
|
|
879
|
+
templateContent: jsonBuildConfigurationTemplate.template,
|
|
880
|
+
templateType: 'buildConfiguration',
|
|
881
|
+
instanceFactory: (
|
|
882
|
+
expandedName: string,
|
|
883
|
+
combination: Record<string, string>,
|
|
884
|
+
templateContent: JsonBuildConfigurationContent,
|
|
885
|
+
originalTemplateName: string
|
|
886
|
+
) =>
|
|
887
|
+
new BuildConfiguration({
|
|
888
|
+
buildConfigurationName: expandedName,
|
|
889
|
+
templateBuildConfigurationName: originalTemplateName,
|
|
890
|
+
jsonBuildConfiguration: templateContent,
|
|
891
|
+
parentBuildConfigurations: this,
|
|
892
|
+
matrixParameters: { ...combination },
|
|
893
|
+
}),
|
|
894
|
+
})
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
// ============================================================================
|
|
899
|
+
|
|
900
|
+
/**
|
|
901
|
+
* Configuration parameters for constructing a build configuration instance.
|
|
902
|
+
*
|
|
903
|
+
* @remarks
|
|
904
|
+
* This interface defines the required configuration for creating an
|
|
905
|
+
* instance of {@link BuildConfiguration}. Most properties are mandatory
|
|
906
|
+
* except for the optional <code>templateBuildConfigurationName</code> and
|
|
907
|
+
* <code>matrixParameters</code>, which are only needed for template-generated
|
|
908
|
+
* configurations created from matrix expansion.
|
|
909
|
+
*
|
|
910
|
+
* The parameters provide the configuration with its identity (name,
|
|
911
|
+
* optional template name), the JSON configuration definition, access to
|
|
912
|
+
* the parent collection for shared resources, and optional matrix parameter
|
|
913
|
+
* values for template-generated configurations.
|
|
914
|
+
*/
|
|
915
|
+
export interface BuildConfigurationConstructorParameters {
|
|
916
|
+
/**
|
|
917
|
+
* The configuration name after substitution.
|
|
918
|
+
*/
|
|
919
|
+
buildConfigurationName: string
|
|
920
|
+
|
|
921
|
+
/**
|
|
922
|
+
* The template configuration name, if derived from a template.
|
|
923
|
+
*/
|
|
924
|
+
templateBuildConfigurationName?: string
|
|
925
|
+
|
|
926
|
+
/**
|
|
927
|
+
* The JSON configuration definition.
|
|
928
|
+
*/
|
|
929
|
+
jsonBuildConfiguration: JsonBuildConfigurationContent
|
|
930
|
+
|
|
931
|
+
/**
|
|
932
|
+
* The parent configurations collection.
|
|
933
|
+
*/
|
|
934
|
+
parentBuildConfigurations: BuildConfigurations
|
|
935
|
+
|
|
936
|
+
/**
|
|
937
|
+
* Optional matrix parameter values for template-generated configurations.
|
|
938
|
+
*/
|
|
939
|
+
matrixParameters?: LiquidSubstitutionsStrings
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
/**
|
|
943
|
+
* An individual <b>xpm</b> build configuration.
|
|
944
|
+
*
|
|
945
|
+
* @remarks
|
|
946
|
+
* Build configurations are initialised lazily and may inherit
|
|
947
|
+
* properties, dependencies, and actions from other configurations.
|
|
948
|
+
*
|
|
949
|
+
* A configuration can exist in three states:
|
|
950
|
+
*
|
|
951
|
+
* <ol>
|
|
952
|
+
* <li><b>Undefined:</b> Name is known but instance not yet created.</li>
|
|
953
|
+
* <li><b>Instantiated:</b> Object exists but not yet fully processed.</li>
|
|
954
|
+
* <li><b>Initialised:</b> Inheritance resolved, properties evaluated,
|
|
955
|
+
* dependencies substituted, and actions prepared.</li>
|
|
956
|
+
* </ol>
|
|
957
|
+
*
|
|
958
|
+
* Inheritance is processed recursively with circular reference detection.
|
|
959
|
+
* Later inherited properties override earlier ones, and local properties
|
|
960
|
+
* override all inherited ones. Dependencies and actions are merged from
|
|
961
|
+
* all inherited configurations.
|
|
962
|
+
*/
|
|
963
|
+
export class BuildConfiguration {
|
|
964
|
+
// --------------------------------------------------------------------------
|
|
965
|
+
// Public Members.
|
|
966
|
+
|
|
967
|
+
/**
|
|
968
|
+
* The build configuration name after substitution.
|
|
969
|
+
*
|
|
970
|
+
* @remarks
|
|
971
|
+
* This is the final, expanded configuration name used for identification
|
|
972
|
+
* and selection. For template-generated configurations, this is the
|
|
973
|
+
* concrete name after matrix substitution (e.g., `release-x64` rather than
|
|
974
|
+
* `release-{{ matrix.arch }}`).
|
|
975
|
+
*
|
|
976
|
+
* The name is used for:
|
|
977
|
+
*
|
|
978
|
+
* <ol>
|
|
979
|
+
* <li>User-facing identification when listing or selecting
|
|
980
|
+
* configurations.</li>
|
|
981
|
+
* <li><b>Build folder path generation (default:</b>
|
|
982
|
+
* <code>build/\{name\}</code>).</li>
|
|
983
|
+
* <li>Logging and diagnostic output to track configuration lifecycle.</li>
|
|
984
|
+
* <li>Inheritance references from other configurations.</li>
|
|
985
|
+
* </ol>
|
|
986
|
+
*
|
|
987
|
+
* Names must be unique within the configurations collection, enforced
|
|
988
|
+
* during {@link BuildConfigurations.initialise}.
|
|
989
|
+
*/
|
|
990
|
+
readonly name: string
|
|
991
|
+
|
|
992
|
+
/**
|
|
993
|
+
* The template build configuration name, if derived from a template.
|
|
994
|
+
*
|
|
995
|
+
* @remarks
|
|
996
|
+
* For template-generated configurations, this preserves the original
|
|
997
|
+
* template name containing Liquid variables (e.g.,
|
|
998
|
+
* `release-{{ matrix.arch }}`), while `buildConfigurationName` holds the
|
|
999
|
+
* expanded concrete name.
|
|
1000
|
+
*
|
|
1001
|
+
* Usage:
|
|
1002
|
+
*
|
|
1003
|
+
* <ol>
|
|
1004
|
+
* <li>Undefined for regular (non-template) configurations.</li>
|
|
1005
|
+
* <li>Set to the template name for configurations generated from matrix
|
|
1006
|
+
* expansion.</li>
|
|
1007
|
+
* <li>Used to determine whether full JSON substitution is needed during
|
|
1008
|
+
* initialisation (templates require complete substitution, regular
|
|
1009
|
+
* configurations only substitute specific fields).</li>
|
|
1010
|
+
* <li>Enables tracing and debugging of template expansion process.</li>
|
|
1011
|
+
* </ol>
|
|
1012
|
+
*/
|
|
1013
|
+
readonly templateName?: string
|
|
1014
|
+
|
|
1015
|
+
/**
|
|
1016
|
+
* The parent build configurations collection.
|
|
1017
|
+
*
|
|
1018
|
+
* @remarks
|
|
1019
|
+
* This reference maintains the hierarchical relationship between
|
|
1020
|
+
* individual configurations and their containing collection, providing
|
|
1021
|
+
* essential context for configuration initialisation.
|
|
1022
|
+
*
|
|
1023
|
+
* The parent collection provides access to:
|
|
1024
|
+
*
|
|
1025
|
+
* <ol>
|
|
1026
|
+
* <li>Liquid templating engine for variable substitution.</li>
|
|
1027
|
+
* <li>Base substitution variables hierarchy (package metadata,
|
|
1028
|
+
* environment, platform detection).</li>
|
|
1029
|
+
* <li>Logger instance for diagnostic output.</li>
|
|
1030
|
+
* <li>JSON build configurations lookup for inheritance resolution.</li>
|
|
1031
|
+
* <li>Other configuration instances when processing inheritance chains.</li>
|
|
1032
|
+
* </ol>
|
|
1033
|
+
*
|
|
1034
|
+
* This design enables configurations to access shared resources without
|
|
1035
|
+
* duplicating them, while supporting complex inheritance relationships
|
|
1036
|
+
* where configurations reference and inherit from each other.
|
|
1037
|
+
*/
|
|
1038
|
+
readonly parentBuildConfigurations: BuildConfigurations
|
|
1039
|
+
|
|
1040
|
+
/**
|
|
1041
|
+
* The list of inherited configuration names.
|
|
1042
|
+
*
|
|
1043
|
+
* @remarks
|
|
1044
|
+
* This array specifies the inheritance chain for this configuration,
|
|
1045
|
+
* processed sequentially during initialisation with later entries
|
|
1046
|
+
* overriding earlier ones.
|
|
1047
|
+
*
|
|
1048
|
+
* Inheritance processing:
|
|
1049
|
+
*
|
|
1050
|
+
* <ol>
|
|
1051
|
+
* <li>Populated from <code>inherits</code> or deprecated
|
|
1052
|
+
* <code>inherit</code> field during
|
|
1053
|
+
* initialisation.</li>
|
|
1054
|
+
* <li>Supports both string (single parent) and array (multiple parents)
|
|
1055
|
+
* formats.</li>
|
|
1056
|
+
* <li>Each inherited configuration is initialised recursively before
|
|
1057
|
+
* merging its properties, dependencies, and actions.</li>
|
|
1058
|
+
* <li>Circular references are detected and rejected with
|
|
1059
|
+
* <code>InputError</code>.</li>
|
|
1060
|
+
* <li>Later inherited configurations override properties from earlier
|
|
1061
|
+
* ones, and local properties override all inherited ones.</li>
|
|
1062
|
+
* </ol>
|
|
1063
|
+
*/
|
|
1064
|
+
inheritsNames: string[] = []
|
|
1065
|
+
|
|
1066
|
+
/**
|
|
1067
|
+
* Indicates whether the configuration is hidden.
|
|
1068
|
+
*
|
|
1069
|
+
* @remarks
|
|
1070
|
+
* Hidden configurations are used for inheritance bases or intermediate
|
|
1071
|
+
* configurations that shouldn't be directly selected for building.
|
|
1072
|
+
*
|
|
1073
|
+
* Effects of hidden status:
|
|
1074
|
+
*
|
|
1075
|
+
* <ol>
|
|
1076
|
+
* <li>Hidden configurations don't compute build folder relative paths
|
|
1077
|
+
* during initialisation (optimization for inheritance-only configs).</li>
|
|
1078
|
+
* <li>May be excluded from user-facing configuration lists depending on
|
|
1079
|
+
* application logic.</li>
|
|
1080
|
+
* <li>Still fully initialised and available for inheritance by other
|
|
1081
|
+
* configurations.</li>
|
|
1082
|
+
* <li>Derived from <code>hidden</code> field in JSON configuration definition
|
|
1083
|
+
* (defaults to <code>false</code>).</li>
|
|
1084
|
+
* </ol>
|
|
1085
|
+
*
|
|
1086
|
+
* Common use case:
|
|
1087
|
+
*
|
|
1088
|
+
* Base configurations that define common properties,
|
|
1089
|
+
* dependencies, or actions inherited by multiple concrete configurations.
|
|
1090
|
+
*/
|
|
1091
|
+
readonly isHidden: boolean
|
|
1092
|
+
|
|
1093
|
+
/**
|
|
1094
|
+
* The resolved properties for this configuration.
|
|
1095
|
+
*
|
|
1096
|
+
* @remarks
|
|
1097
|
+
* This object contains the final merged properties after inheritance
|
|
1098
|
+
* resolution and becomes available in the `properties` namespace for
|
|
1099
|
+
* Liquid template substitution.
|
|
1100
|
+
*
|
|
1101
|
+
* Property resolution order:
|
|
1102
|
+
*
|
|
1103
|
+
* <ol>
|
|
1104
|
+
* <li>Start with empty object.</li>
|
|
1105
|
+
* <li>Merge properties from each inherited configuration in sequence
|
|
1106
|
+
* (later overrides earlier).</li>
|
|
1107
|
+
* <li>Merge local properties from JSON definition (overrides all
|
|
1108
|
+
* inherited).</li>
|
|
1109
|
+
* <li>Add computed <code>buildFolderRelativePath</code> property
|
|
1110
|
+
* for non-hidden
|
|
1111
|
+
* configurations.</li>
|
|
1112
|
+
* </ol>
|
|
1113
|
+
*
|
|
1114
|
+
* Properties are accessible in templates as `{{ properties.key }}` and
|
|
1115
|
+
* commonly used for compiler flags, toolchain paths, optimization
|
|
1116
|
+
* settings, and build-specific configuration values.
|
|
1117
|
+
*/
|
|
1118
|
+
properties: LiquidSubstitutionsStrings = {}
|
|
1119
|
+
|
|
1120
|
+
/**
|
|
1121
|
+
* The resolved dependencies after substitutions.
|
|
1122
|
+
*
|
|
1123
|
+
* @remarks
|
|
1124
|
+
* This object contains the final merged dependencies after inheritance
|
|
1125
|
+
* resolution and Liquid template substitution.
|
|
1126
|
+
*
|
|
1127
|
+
* Dependency resolution workflow:
|
|
1128
|
+
*
|
|
1129
|
+
* <ol>
|
|
1130
|
+
* <li>Start with empty object.</li>
|
|
1131
|
+
* <li>Merge <code>dependencies</code> from each inherited configuration
|
|
1132
|
+
* in sequence
|
|
1133
|
+
* (later overrides earlier).</li>
|
|
1134
|
+
* <li>Merge local <code>dependencies</code> from JSON definition.</li>
|
|
1135
|
+
* <li>Perform Liquid template substitution on the entire
|
|
1136
|
+
* <code>dependencies</code>
|
|
1137
|
+
* object with full configuration context (properties, matrix, etc.).</li>
|
|
1138
|
+
* </ol>
|
|
1139
|
+
*
|
|
1140
|
+
* This enables configuration-specific dependencies with dynamic version
|
|
1141
|
+
* ranges or package selection based on matrix parameters, platform
|
|
1142
|
+
* detection, or configuration properties.
|
|
1143
|
+
*/
|
|
1144
|
+
dependencies: JsonDependencies = {}
|
|
1145
|
+
|
|
1146
|
+
/**
|
|
1147
|
+
* The resolved development dependencies after substitutions.
|
|
1148
|
+
*
|
|
1149
|
+
* @remarks
|
|
1150
|
+
* This object contains the final merged development dependencies after
|
|
1151
|
+
* inheritance resolution and Liquid template substitution.
|
|
1152
|
+
*
|
|
1153
|
+
* Resolution workflow mirrors `dependencies`:
|
|
1154
|
+
*
|
|
1155
|
+
* <ol>
|
|
1156
|
+
* <li>Start with empty object.</li>
|
|
1157
|
+
* <li>Merge <code>devDependencies</code> from each inherited configuration
|
|
1158
|
+
* in sequence
|
|
1159
|
+
* (later overrides earlier).</li>
|
|
1160
|
+
* <li>Merge local <code>devDependencies</code> from JSON definition.</li>
|
|
1161
|
+
* <li>Perform Liquid template substitution on the entire
|
|
1162
|
+
* <code>devDependencies</code>
|
|
1163
|
+
* object with full configuration context.</li>
|
|
1164
|
+
* </ol>
|
|
1165
|
+
*
|
|
1166
|
+
* Typical use: Test frameworks, build tools, or debugging utilities
|
|
1167
|
+
* specific to certain configurations (e.g., debug builds might include
|
|
1168
|
+
* additional analysis tools).
|
|
1169
|
+
*/
|
|
1170
|
+
devDependencies: JsonDependencies = {}
|
|
1171
|
+
|
|
1172
|
+
/**
|
|
1173
|
+
* The JSON build configuration content from package metadata.
|
|
1174
|
+
*
|
|
1175
|
+
* @remarks
|
|
1176
|
+
* This holds the raw configuration definition as it appears in
|
|
1177
|
+
* `package.json`, before inheritance resolution and variable substitution.
|
|
1178
|
+
*
|
|
1179
|
+
* The definition is preserved to:
|
|
1180
|
+
*
|
|
1181
|
+
* <ol>
|
|
1182
|
+
* <li>Enable external modification (e.g., <code>xpm uninstall</code>
|
|
1183
|
+
* updates this
|
|
1184
|
+
* directly).</li>
|
|
1185
|
+
* <li>Support deferred template evaluation during
|
|
1186
|
+
* <code>BuildConfiguration.initialise()</code>.</li>
|
|
1187
|
+
* <li>Provide the source for inheritance when other configurations
|
|
1188
|
+
* reference this one.</li>
|
|
1189
|
+
* <li>Allow re-evaluation with different variable contexts if needed.</li>
|
|
1190
|
+
* </ol>
|
|
1191
|
+
*
|
|
1192
|
+
* This immutable storage ensures configurations can be safely referenced
|
|
1193
|
+
* during inheritance resolution without side effects.
|
|
1194
|
+
*/
|
|
1195
|
+
jsonBuildConfiguration: JsonBuildConfigurationContent
|
|
1196
|
+
|
|
1197
|
+
/**
|
|
1198
|
+
* Indicates whether this configuration originates from a template.
|
|
1199
|
+
*
|
|
1200
|
+
* @remarks
|
|
1201
|
+
* This flag determines the substitution strategy during configuration
|
|
1202
|
+
* initialisation, with template configurations requiring more extensive
|
|
1203
|
+
* processing.
|
|
1204
|
+
*
|
|
1205
|
+
* Template vs regular configuration processing:
|
|
1206
|
+
*
|
|
1207
|
+
* <ol>
|
|
1208
|
+
* <li>Template configurations (<code>isTemplate === true</code>):
|
|
1209
|
+
* <ul>
|
|
1210
|
+
* <li>Entire JSON configuration is stringified and substituted.</li>
|
|
1211
|
+
* <li>Matrix parameters available throughout all fields.</li>
|
|
1212
|
+
* <li>More expensive but supports matrix references anywhere.</li>
|
|
1213
|
+
* </ul>
|
|
1214
|
+
* </li>
|
|
1215
|
+
* <li>Regular configurations (<code>isTemplate === false</code>):
|
|
1216
|
+
* <ul>
|
|
1217
|
+
* <li>Only <code>inherits</code> field is substituted initially.</li>
|
|
1218
|
+
* <li>Other fields processed selectively during inheritance
|
|
1219
|
+
* resolution.</li>
|
|
1220
|
+
* <li>More efficient for configurations without matrix parameters.</li>
|
|
1221
|
+
* </ul>
|
|
1222
|
+
* </li>
|
|
1223
|
+
* </ol>
|
|
1224
|
+
*
|
|
1225
|
+
* Set to `true` when `templateBuildConfigurationName` is defined,
|
|
1226
|
+
* indicating the configuration was generated from a template expansion.
|
|
1227
|
+
*/
|
|
1228
|
+
isTemplate: boolean
|
|
1229
|
+
|
|
1230
|
+
// --------------------------------------------------------------------------
|
|
1231
|
+
// Protected Members.
|
|
1232
|
+
|
|
1233
|
+
/**
|
|
1234
|
+
* The logger instance for output and diagnostics.
|
|
1235
|
+
*
|
|
1236
|
+
* @remarks
|
|
1237
|
+
* This logger provides trace-level diagnostics throughout the build
|
|
1238
|
+
* configuration lifecycle, including template substitution, inheritance
|
|
1239
|
+
* resolution, property merging, dependency substitution, and action
|
|
1240
|
+
* preparation.
|
|
1241
|
+
*
|
|
1242
|
+
* It is initialised in the constructor from the parent collection's logger
|
|
1243
|
+
* and used consistently across all helper methods to maintain coherent
|
|
1244
|
+
* logging output. This enables detailed debugging of complex configuration
|
|
1245
|
+
* hierarchies without impacting runtime performance when tracing is
|
|
1246
|
+
* disabled.
|
|
1247
|
+
*/
|
|
1248
|
+
protected readonly _log: Logger
|
|
1249
|
+
|
|
1250
|
+
/**
|
|
1251
|
+
* The variables used for substitution in this configuration.
|
|
1252
|
+
*
|
|
1253
|
+
* @remarks
|
|
1254
|
+
* This extended variable hierarchy combines the base collection variables
|
|
1255
|
+
* with configuration-specific context, enabling accurate template
|
|
1256
|
+
* evaluation.
|
|
1257
|
+
*
|
|
1258
|
+
* Extension hierarchy:
|
|
1259
|
+
*
|
|
1260
|
+
* <ol>
|
|
1261
|
+
* <li>Starts with parent collection's base variables (env, os, path,
|
|
1262
|
+
* package).</li>
|
|
1263
|
+
* <li>Extended with <code>properties</code>: Merged from inheritance
|
|
1264
|
+
* chain and local
|
|
1265
|
+
* properties.</li>
|
|
1266
|
+
* <li>Extended with <code>matrix</code>: Parameter values for
|
|
1267
|
+
* template-generated
|
|
1268
|
+
* configurations.</li>
|
|
1269
|
+
* <li>Extended with <code>configuration</code>: The configuration
|
|
1270
|
+
* object itself
|
|
1271
|
+
* (name, dependencies, properties) accessible for self-reference.</li>
|
|
1272
|
+
* </ol>
|
|
1273
|
+
*
|
|
1274
|
+
* This complete context is used for all substitutions within the
|
|
1275
|
+
* configuration: properties, dependencies, devDependencies, and actions.
|
|
1276
|
+
*/
|
|
1277
|
+
protected _substitutionsVariables: LiquidSubstitutionsVariables
|
|
1278
|
+
|
|
1279
|
+
/**
|
|
1280
|
+
* The matrix parameter values for template-generated configurations.
|
|
1281
|
+
*
|
|
1282
|
+
* @remarks
|
|
1283
|
+
* For template-generated configurations, this object contains the specific
|
|
1284
|
+
* matrix parameter values that produced this configuration instance from
|
|
1285
|
+
* the template.
|
|
1286
|
+
*
|
|
1287
|
+
* Usage pattern:
|
|
1288
|
+
*
|
|
1289
|
+
* <ol>
|
|
1290
|
+
* <li>Undefined for regular (non-template) configurations.</li>
|
|
1291
|
+
* <li>For template configurations, contains key-value pairs from the matrix
|
|
1292
|
+
* combination (e.g.,
|
|
1293
|
+
* <code>\{ arch: 'x64', optimize: 'speed' \}</code>).</li>
|
|
1294
|
+
* <li>Merged into substitution variables during initialisation, making
|
|
1295
|
+
* values accessible via the <code>matrix</code> namespace throughout the
|
|
1296
|
+
* configuration.</li>
|
|
1297
|
+
* <li>Used in configuration name substitution, property values,
|
|
1298
|
+
* dependencies, and action commands.</li>
|
|
1299
|
+
* </ol>
|
|
1300
|
+
*
|
|
1301
|
+
* Example: A template `release-{{ matrix.arch }}` with matrix parameters
|
|
1302
|
+
* `{ arch: 'x64' }` becomes the concrete configuration `release-x64`.
|
|
1303
|
+
*/
|
|
1304
|
+
protected readonly matrixParameters?: LiquidSubstitutionsStrings
|
|
1305
|
+
|
|
1306
|
+
/**
|
|
1307
|
+
* The actions associated with this build configuration.
|
|
1308
|
+
*
|
|
1309
|
+
* @remarks
|
|
1310
|
+
* This actions collection is created during configuration initialisation
|
|
1311
|
+
* and combines inherited actions with local action definitions.
|
|
1312
|
+
*
|
|
1313
|
+
* Action assembly workflow:
|
|
1314
|
+
*
|
|
1315
|
+
* <ol>
|
|
1316
|
+
* <li>Undefined until <code>BuildConfiguration.initialise()</code> is
|
|
1317
|
+
* called.</li>
|
|
1318
|
+
* <li>Collect actions from all inherited configurations in the inheritance
|
|
1319
|
+
* chain.</li>
|
|
1320
|
+
* <li>Create new <code>Actions</code> collection with inherited
|
|
1321
|
+
* actions map and local action definitions.</li>
|
|
1322
|
+
* <li>Actions inherit the configuration's substitution variables context,
|
|
1323
|
+
* including properties and matrix parameters.</li>
|
|
1324
|
+
* </ol>
|
|
1325
|
+
*
|
|
1326
|
+
* Actions are accessible after configuration initialisation but remain
|
|
1327
|
+
* themselves uninitialised until retrieved and initialised individually,
|
|
1328
|
+
* maintaining the lazy evaluation pattern.
|
|
1329
|
+
*/
|
|
1330
|
+
protected _actions: Actions | undefined
|
|
1331
|
+
|
|
1332
|
+
/**
|
|
1333
|
+
* The resolved build folder relative path.
|
|
1334
|
+
*
|
|
1335
|
+
* @remarks
|
|
1336
|
+
* This path specifies where build outputs for this configuration should be
|
|
1337
|
+
* placed, computed during initialisation and added back to properties for
|
|
1338
|
+
* use in subsequent substitutions.
|
|
1339
|
+
*
|
|
1340
|
+
* Computation workflow:
|
|
1341
|
+
*
|
|
1342
|
+
* <ol>
|
|
1343
|
+
* <li>Undefined until <code>BuildConfiguration.initialise()</code> is
|
|
1344
|
+
* called.</li>
|
|
1345
|
+
* <li>Not computed for hidden configurations (optimization).</li>
|
|
1346
|
+
* <li>If <code>buildFolderRelativePath</code> property exists, perform Liquid
|
|
1347
|
+
* substitution with full configuration context.</li>
|
|
1348
|
+
* <li>Otherwise, generate default path:
|
|
1349
|
+
* <code>build/\{sanitized-config-name\}</code>.</li>
|
|
1350
|
+
* <li>Added to <code>properties.buildFolderRelativePath</code> for use
|
|
1351
|
+
* in action
|
|
1352
|
+
* commands and dependency references.</li>
|
|
1353
|
+
* </ol>
|
|
1354
|
+
*
|
|
1355
|
+
* The path is relative to the package root and used by build tools to
|
|
1356
|
+
* organize outputs from different configurations.
|
|
1357
|
+
*/
|
|
1358
|
+
protected _buildFolderRelativePath?: string
|
|
1359
|
+
|
|
1360
|
+
/**
|
|
1361
|
+
* Set of inherited configuration names for circular reference detection.
|
|
1362
|
+
*
|
|
1363
|
+
* @remarks
|
|
1364
|
+
* This set tracks the inheritance chain being processed to detect and
|
|
1365
|
+
* prevent circular inheritance references.
|
|
1366
|
+
*
|
|
1367
|
+
* Detection mechanism:
|
|
1368
|
+
*
|
|
1369
|
+
* <ol>
|
|
1370
|
+
* <li>Initially empty when configuration initialisation begins.</li>
|
|
1371
|
+
* <li>Each inherited configuration name is added before processing that
|
|
1372
|
+
* configuration's inheritance.</li>
|
|
1373
|
+
* <li>If a configuration attempts to inherit from a name already in the
|
|
1374
|
+
* set, a circular reference exists.</li>
|
|
1375
|
+
* <li>Circular references trigger <code>InputError</code> with details
|
|
1376
|
+
* about the problematic inheritance chain.</li>
|
|
1377
|
+
* </ol>
|
|
1378
|
+
*
|
|
1379
|
+
* Example: If config A inherits from B, B from C, and C from A, the
|
|
1380
|
+
* circular dependency is detected when C attempts to inherit from A.
|
|
1381
|
+
*/
|
|
1382
|
+
protected _inheritedNamesSet: Set<string> = new Set<string>()
|
|
1383
|
+
|
|
1384
|
+
/**
|
|
1385
|
+
* Flag indicating whether the configuration has been initialised.
|
|
1386
|
+
*
|
|
1387
|
+
* @remarks
|
|
1388
|
+
* This flag ensures idempotent initialization and prevents redundant
|
|
1389
|
+
* processing when {@link BuildConfiguration.initialise} is called
|
|
1390
|
+
* multiple times.
|
|
1391
|
+
*
|
|
1392
|
+
* State transitions:
|
|
1393
|
+
*
|
|
1394
|
+
* <ol>
|
|
1395
|
+
* <li>Initially <code>false</code> after construction.</li>
|
|
1396
|
+
* <li>Set to <code>true</code> after successful inheritance resolution,
|
|
1397
|
+
* property
|
|
1398
|
+
* merging, dependency substitution, and action preparation.</li>
|
|
1399
|
+
* <li>Checked at the start of
|
|
1400
|
+
* <code>BuildConfiguration.initialise()</code> to return early if
|
|
1401
|
+
* already initialised.</li>
|
|
1402
|
+
* </ol>
|
|
1403
|
+
*
|
|
1404
|
+
* This pattern is critical for inheritance processing, as configurations
|
|
1405
|
+
* may be initialised multiple times when referenced by multiple children,
|
|
1406
|
+
* but should only process their inheritance chain once.
|
|
1407
|
+
*/
|
|
1408
|
+
protected _isInitialised = false
|
|
1409
|
+
|
|
1410
|
+
// --------------------------------------------------------------------------
|
|
1411
|
+
// Constructor and async initialiser.
|
|
1412
|
+
|
|
1413
|
+
/**
|
|
1414
|
+
* Constructs a build configuration instance.
|
|
1415
|
+
*
|
|
1416
|
+
* @param buildConfigurationName - The configuration name after substitution.
|
|
1417
|
+
* @param templateBuildConfigurationName - The template configuration name, if
|
|
1418
|
+
* derived from a template.
|
|
1419
|
+
* @param jsonBuildConfiguration - The JSON configuration definition.
|
|
1420
|
+
* @param parentBuildConfigurations - The parent configurations collection.
|
|
1421
|
+
* @param matrixParameters - Optional matrix parameter values for
|
|
1422
|
+
* template-generated configurations.
|
|
1423
|
+
*
|
|
1424
|
+
* @remarks
|
|
1425
|
+
* The constructor performs partial initialisation. Full initialisation
|
|
1426
|
+
* requires calling {@link BuildConfiguration.initialise}.
|
|
1427
|
+
*/
|
|
1428
|
+
constructor({
|
|
1429
|
+
buildConfigurationName,
|
|
1430
|
+
templateBuildConfigurationName,
|
|
1431
|
+
jsonBuildConfiguration,
|
|
1432
|
+
parentBuildConfigurations,
|
|
1433
|
+
matrixParameters,
|
|
1434
|
+
}: BuildConfigurationConstructorParameters) {
|
|
1435
|
+
assert(buildConfigurationName, 'buildConfigurationName is required')
|
|
1436
|
+
assert(jsonBuildConfiguration, 'jsonBuildConfiguration is required')
|
|
1437
|
+
assert(parentBuildConfigurations, 'parentBuildConfigurations is required')
|
|
1438
|
+
|
|
1439
|
+
const log = parentBuildConfigurations.log
|
|
1440
|
+
this._log = log
|
|
1441
|
+
|
|
1442
|
+
log.trace(`${BuildConfiguration.name}(${buildConfigurationName})`)
|
|
1443
|
+
|
|
1444
|
+
this.name = buildConfigurationName
|
|
1445
|
+
this.jsonBuildConfiguration = jsonBuildConfiguration
|
|
1446
|
+
this.parentBuildConfigurations = parentBuildConfigurations
|
|
1447
|
+
if (matrixParameters !== undefined) {
|
|
1448
|
+
this.matrixParameters = matrixParameters
|
|
1449
|
+
}
|
|
1450
|
+
if (templateBuildConfigurationName !== undefined) {
|
|
1451
|
+
this.templateName = templateBuildConfigurationName
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
this._substitutionsVariables = {
|
|
1455
|
+
...this.parentBuildConfigurations.substitutionsVariables,
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
this.isHidden = this.jsonBuildConfiguration.hidden ?? false
|
|
1459
|
+
|
|
1460
|
+
this.isTemplate = this.templateName !== undefined
|
|
1461
|
+
|
|
1462
|
+
// The rest of the initialisation is done in the async initialiser.
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
/**
|
|
1466
|
+
* Completes the async initialisation of the build configuration.
|
|
1467
|
+
*
|
|
1468
|
+
* @remarks
|
|
1469
|
+
* This method resolves inheritance, applies variable substitutions,
|
|
1470
|
+
* computes dependencies, and prepares actions.
|
|
1471
|
+
*
|
|
1472
|
+
* Initialisation workflow:
|
|
1473
|
+
*
|
|
1474
|
+
* <ol>
|
|
1475
|
+
* <li>Return early if already initialised (idempotent behaviour).</li>
|
|
1476
|
+
* <li>For template configurations: Call
|
|
1477
|
+
* <code>_substituteTemplate()</code> to substitute
|
|
1478
|
+
* matrix parameters throughout the entire JSON structure.</li>
|
|
1479
|
+
* <li>For non-template configurations: Call
|
|
1480
|
+
* <code>_substituteInherits()</code> to substitute
|
|
1481
|
+
* only the inherits field.</li>
|
|
1482
|
+
* <li>Call <code>_processInherits()</code> to:
|
|
1483
|
+
* <ul>
|
|
1484
|
+
* <li>Process inheritance chain recursively with circular reference
|
|
1485
|
+
* detection.</li>
|
|
1486
|
+
* <li>Merge properties, dependencies, and devDependencies from inherited
|
|
1487
|
+
* configurations (later overrides earlier).</li>
|
|
1488
|
+
* <li>Collect inherited actions into a map.</li>
|
|
1489
|
+
* </ul>
|
|
1490
|
+
* </li>
|
|
1491
|
+
* <li>Apply local properties and update substitution variables context.</li>
|
|
1492
|
+
* <li>For visible configurations: Compute build folder relative path via
|
|
1493
|
+
* <code>_getBuildFolderRelativePath()</code>.</li>
|
|
1494
|
+
* <li>Substitute Liquid templates in dependencies and devDependencies.</li>
|
|
1495
|
+
* <li>Create actions collection with inherited actions and local
|
|
1496
|
+
* actions.</li>
|
|
1497
|
+
* </ol>
|
|
1498
|
+
*
|
|
1499
|
+
* The substitution context includes package variables, configuration
|
|
1500
|
+
* properties, matrix parameters (for templates), and the configuration
|
|
1501
|
+
* object itself accessible via `configuration.name`, etc.
|
|
1502
|
+
*
|
|
1503
|
+
* @returns A promise that resolves to `true` if initialisation was performed,
|
|
1504
|
+
* or `false` if already initialised.
|
|
1505
|
+
*
|
|
1506
|
+
* @throws {@link ConfigurationError}
|
|
1507
|
+
* If substitutions fail.
|
|
1508
|
+
*
|
|
1509
|
+
* @throws {@link InputError}
|
|
1510
|
+
* If inheritance references are invalid or circular.
|
|
1511
|
+
*/
|
|
1512
|
+
async initialise(): Promise<boolean> {
|
|
1513
|
+
const log = this._log
|
|
1514
|
+
log.trace(`${BuildConfiguration.name}.initialise()` + ` @${this.name}`)
|
|
1515
|
+
|
|
1516
|
+
if (this._isInitialised) {
|
|
1517
|
+
log.trace(
|
|
1518
|
+
`${BuildConfiguration.name}.initialise()` + ` @${this.name} again`
|
|
1519
|
+
)
|
|
1520
|
+
return false
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
log.trace(`${BuildConfiguration.name}.initialise()` + ` @${this.name}`)
|
|
1524
|
+
let localJsonBuildConfiguration: JsonBuildConfigurationContent
|
|
1525
|
+
|
|
1526
|
+
if (this.isTemplate) {
|
|
1527
|
+
localJsonBuildConfiguration = await this._substituteTemplate()
|
|
1528
|
+
} else {
|
|
1529
|
+
localJsonBuildConfiguration = await this._substituteInherits()
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
// Add inherited properties, dependencies, devDependencies, and actions.
|
|
1533
|
+
const inheritedActionsMap = await this._processInherits(
|
|
1534
|
+
localJsonBuildConfiguration
|
|
1535
|
+
)
|
|
1536
|
+
|
|
1537
|
+
this.properties = {
|
|
1538
|
+
...this.properties,
|
|
1539
|
+
...localJsonBuildConfiguration.properties,
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
assert(this.name, 'buildConfigurationName missing')
|
|
1543
|
+
this._substitutionsVariables = {
|
|
1544
|
+
...this.parentBuildConfigurations.substitutionsVariables,
|
|
1545
|
+
properties: {
|
|
1546
|
+
...this._substitutionsVariables.properties,
|
|
1547
|
+
...this.properties,
|
|
1548
|
+
},
|
|
1549
|
+
matrix: this.matrixParameters ?? {},
|
|
1550
|
+
configuration: {
|
|
1551
|
+
...localJsonBuildConfiguration,
|
|
1552
|
+
name: this.name,
|
|
1553
|
+
},
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
if (!this.isHidden) {
|
|
1557
|
+
this._buildFolderRelativePath = await this._getBuildFolderRelativePath()
|
|
1558
|
+
|
|
1559
|
+
// Add the buildFolderRelativePath property.
|
|
1560
|
+
// Note: the async initialiser was needed due to this async operation.
|
|
1561
|
+
const properties = this._substitutionsVariables.properties
|
|
1562
|
+
properties.buildFolderRelativePath = this._buildFolderRelativePath
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
this.dependencies = {
|
|
1566
|
+
...this.dependencies,
|
|
1567
|
+
...localJsonBuildConfiguration.dependencies,
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
this.devDependencies = {
|
|
1571
|
+
...this.devDependencies,
|
|
1572
|
+
...localJsonBuildConfiguration.devDependencies,
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
const unsubstitutedDependencies = {
|
|
1576
|
+
dependencies: this.dependencies,
|
|
1577
|
+
devDependencies: this.devDependencies,
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
const stringifiedDependencies = JSON.stringify(unsubstitutedDependencies)
|
|
1581
|
+
|
|
1582
|
+
if (hasLiquidSyntax(stringifiedDependencies)) {
|
|
1583
|
+
let substitutedDependencies
|
|
1584
|
+
try {
|
|
1585
|
+
substitutedDependencies = await performSubstitutions({
|
|
1586
|
+
log,
|
|
1587
|
+
engine: this.parentBuildConfigurations.engine,
|
|
1588
|
+
input: stringifiedDependencies,
|
|
1589
|
+
substitutionsVariables: this._substitutionsVariables,
|
|
1590
|
+
})
|
|
1591
|
+
} catch (error) {
|
|
1592
|
+
const message =
|
|
1593
|
+
getErrorMessage(error) +
|
|
1594
|
+
` in buildConfiguration "${this.name}" dependencies`
|
|
1595
|
+
throw new ConfigurationError(message)
|
|
1596
|
+
}
|
|
1597
|
+
const parsedDependencies = JSON.parse(
|
|
1598
|
+
substitutedDependencies
|
|
1599
|
+
) as JsonBuildConfigurationContent
|
|
1600
|
+
|
|
1601
|
+
// Safety net: These fallbacks to empty objects handle cases where the
|
|
1602
|
+
// dependencies fields might be undefined after JSON parsing. This is
|
|
1603
|
+
// unlikely because the JSON schema validation ensures these are objects
|
|
1604
|
+
// when present, but provides robustness against malformed configuration
|
|
1605
|
+
// or future schema changes.
|
|
1606
|
+
/* c8 ignore start - safety net, they are always defined */
|
|
1607
|
+
this.dependencies = parsedDependencies.dependencies ?? {}
|
|
1608
|
+
this.devDependencies = parsedDependencies.devDependencies ?? {}
|
|
1609
|
+
/* c8 ignore stop */
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
this._actions = new Actions({
|
|
1613
|
+
log: this._log,
|
|
1614
|
+
engine: this.parentBuildConfigurations.engine,
|
|
1615
|
+
substitutionsVariables: this._substitutionsVariables,
|
|
1616
|
+
inheritedActionsMap,
|
|
1617
|
+
jsonActions: localJsonBuildConfiguration.actions,
|
|
1618
|
+
buildConfiguration: this,
|
|
1619
|
+
})
|
|
1620
|
+
|
|
1621
|
+
log.trace(
|
|
1622
|
+
`${BuildConfiguration.name}.initialise() `,
|
|
1623
|
+
`@{this.buildConfigurationName}`
|
|
1624
|
+
)
|
|
1625
|
+
|
|
1626
|
+
if (!this.isHidden) {
|
|
1627
|
+
log.trace(
|
|
1628
|
+
this.name,
|
|
1629
|
+
'buildFolderRelativePath =>',
|
|
1630
|
+
this._buildFolderRelativePath
|
|
1631
|
+
)
|
|
1632
|
+
}
|
|
1633
|
+
log.trace(this.name, 'properties => ', this.properties)
|
|
1634
|
+
log.trace(this.name, 'dependencies => ', this.dependencies)
|
|
1635
|
+
log.trace(this.name, 'devDependencies => ', this.devDependencies)
|
|
1636
|
+
|
|
1637
|
+
// Action names are not available at this point.
|
|
1638
|
+
// log.trace(this.buildConfigurationName, 'actions => ',
|
|
1639
|
+
// this._actions.names)
|
|
1640
|
+
|
|
1641
|
+
this._isInitialised = true
|
|
1642
|
+
return true
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
// --------------------------------------------------------------------------
|
|
1646
|
+
// Public Methods.
|
|
1647
|
+
|
|
1648
|
+
/**
|
|
1649
|
+
* Retrieves the actions collection for this build configuration.
|
|
1650
|
+
*
|
|
1651
|
+
* @returns The actions collection.
|
|
1652
|
+
*/
|
|
1653
|
+
get actions(): Actions {
|
|
1654
|
+
assert(
|
|
1655
|
+
this._isInitialised,
|
|
1656
|
+
'BuildConfiguration must be initialised before ' + 'accessing actions'
|
|
1657
|
+
)
|
|
1658
|
+
|
|
1659
|
+
assert(this._actions !== undefined, 'Actions not initialised')
|
|
1660
|
+
return this._actions
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
/**
|
|
1664
|
+
* Retrieves the build folder relative path for this configuration.
|
|
1665
|
+
*
|
|
1666
|
+
* @returns The build folder relative path.
|
|
1667
|
+
*/
|
|
1668
|
+
get buildFolderRelativePath(): string {
|
|
1669
|
+
assert(
|
|
1670
|
+
this._isInitialised,
|
|
1671
|
+
'BuildConfiguration must be initialised before ' +
|
|
1672
|
+
'accessing buildFolderRelativePath'
|
|
1673
|
+
)
|
|
1674
|
+
assert(
|
|
1675
|
+
this._buildFolderRelativePath !== undefined,
|
|
1676
|
+
'BuildConfiguration _buildFolderRelativePath not initialised'
|
|
1677
|
+
)
|
|
1678
|
+
return this._buildFolderRelativePath
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
// --------------------------------------------------------------------------
|
|
1682
|
+
// Private Methods.
|
|
1683
|
+
|
|
1684
|
+
/**
|
|
1685
|
+
* Performs template substitution on the entire build configuration JSON.
|
|
1686
|
+
*
|
|
1687
|
+
* @remarks
|
|
1688
|
+
* This method is invoked during initialisation for template-generated
|
|
1689
|
+
* configurations to substitute matrix parameters throughout the entire
|
|
1690
|
+
* configuration definition.
|
|
1691
|
+
*
|
|
1692
|
+
* Processing steps:
|
|
1693
|
+
*
|
|
1694
|
+
* <ol>
|
|
1695
|
+
* <li>Stringify the entire JSON build configuration object.</li>
|
|
1696
|
+
* <li>Check if the stringified JSON contains template syntax
|
|
1697
|
+
* (<code>\{\{</code> or <code>\{%</code>).</li>
|
|
1698
|
+
* <li>If templates are found:
|
|
1699
|
+
* <ul>
|
|
1700
|
+
* <li>Perform Liquid substitutions with complete variable context
|
|
1701
|
+
* including matrix parameters.</li>
|
|
1702
|
+
* <li>Parse the substituted JSON string back into an object.</li>
|
|
1703
|
+
* </ul>
|
|
1704
|
+
* </li>
|
|
1705
|
+
* <li>If no templates are found, return the original configuration
|
|
1706
|
+
* as-is.</li>
|
|
1707
|
+
* </ol>
|
|
1708
|
+
*
|
|
1709
|
+
* This comprehensive substitution approach ensures matrix parameters can
|
|
1710
|
+
* be referenced anywhere within the configuration (properties, dependencies,
|
|
1711
|
+
* actions, etc.), which is necessary for template-generated configurations
|
|
1712
|
+
* but would be unnecessarily expensive for regular configurations.
|
|
1713
|
+
*
|
|
1714
|
+
* @returns A promise that resolves to the build configuration content with
|
|
1715
|
+
* all template variables substituted.
|
|
1716
|
+
*
|
|
1717
|
+
* @throws {@link ConfigurationError}
|
|
1718
|
+
* If Liquid template substitution fails.
|
|
1719
|
+
*/
|
|
1720
|
+
// eslint-disable-next-line max-len
|
|
1721
|
+
protected async _substituteTemplate(): Promise<JsonBuildConfigurationContent> {
|
|
1722
|
+
const log = this._log
|
|
1723
|
+
|
|
1724
|
+
// For templates, perform substitutions on the entire build
|
|
1725
|
+
// configuration JSON, since there can be matrix references everywhere.
|
|
1726
|
+
|
|
1727
|
+
let localJsonBuildConfiguration: JsonBuildConfigurationContent
|
|
1728
|
+
|
|
1729
|
+
const stringifiedJsonBuildConfiguration = JSON.stringify(
|
|
1730
|
+
this.jsonBuildConfiguration
|
|
1731
|
+
)
|
|
1732
|
+
if (hasLiquidSyntax(stringifiedJsonBuildConfiguration)) {
|
|
1733
|
+
let substitutedJsonBuildConfiguration
|
|
1734
|
+
try {
|
|
1735
|
+
substitutedJsonBuildConfiguration = await performSubstitutions({
|
|
1736
|
+
log,
|
|
1737
|
+
engine: this.parentBuildConfigurations.engine,
|
|
1738
|
+
input: stringifiedJsonBuildConfiguration,
|
|
1739
|
+
substitutionsVariables: {
|
|
1740
|
+
...this._substitutionsVariables,
|
|
1741
|
+
// Safety net: This fallback ensures matrix is always an object.
|
|
1742
|
+
// matrixParameters should be defined when processing templates with
|
|
1743
|
+
// matrix expansion, but this handles edge cases where
|
|
1744
|
+
// initialisation
|
|
1745
|
+
// order or template logic might reference matrix before it's set.
|
|
1746
|
+
/* c8 ignore start - safety net, they are always defined */
|
|
1747
|
+
matrix: this.matrixParameters ?? {},
|
|
1748
|
+
/* c8 ignore stop */
|
|
1749
|
+
configuration: {
|
|
1750
|
+
...this.jsonBuildConfiguration,
|
|
1751
|
+
name: this.name,
|
|
1752
|
+
},
|
|
1753
|
+
},
|
|
1754
|
+
})
|
|
1755
|
+
} catch (error) {
|
|
1756
|
+
const message =
|
|
1757
|
+
getErrorMessage(error) + ` in buildConfiguration "${this.name}"`
|
|
1758
|
+
throw new ConfigurationError(message)
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
localJsonBuildConfiguration = JSON.parse(
|
|
1762
|
+
substitutedJsonBuildConfiguration
|
|
1763
|
+
) as JsonBuildConfigurationContent
|
|
1764
|
+
} else {
|
|
1765
|
+
localJsonBuildConfiguration = this.jsonBuildConfiguration
|
|
1766
|
+
}
|
|
1767
|
+
return localJsonBuildConfiguration
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
/**
|
|
1771
|
+
* Performs selective substitution on the inherits field only.
|
|
1772
|
+
*
|
|
1773
|
+
* @remarks
|
|
1774
|
+
* This method is invoked during initialisation for regular (non-template)
|
|
1775
|
+
* configurations to substitute template variables in the inheritance
|
|
1776
|
+
* specification whilst leaving other fields untouched until later processing.
|
|
1777
|
+
*
|
|
1778
|
+
* Processing steps:
|
|
1779
|
+
*
|
|
1780
|
+
* <ol>
|
|
1781
|
+
* <li>Extract the <code>inherits</code> (or deprecated
|
|
1782
|
+
* <code>inherit</code>) field from the
|
|
1783
|
+
* configuration.</li>
|
|
1784
|
+
* <li>Stringify the inherits field and check for template syntax
|
|
1785
|
+
* (<code>\{\{</code> or <code>\{%</code>).</li>
|
|
1786
|
+
* <li>If templates are found:
|
|
1787
|
+
* <ul>
|
|
1788
|
+
* <li>Perform Liquid substitutions with current variable context.</li>
|
|
1789
|
+
* <li>Parse the substituted JSON string back into an object.</li>
|
|
1790
|
+
* <li>Return a new configuration object with the substituted inherits
|
|
1791
|
+
* field and all other fields unchanged.</li>
|
|
1792
|
+
* </ul>
|
|
1793
|
+
* </li>
|
|
1794
|
+
* <li>If no templates are found, return the original configuration
|
|
1795
|
+
* as-is.</li>
|
|
1796
|
+
* </ol>
|
|
1797
|
+
*
|
|
1798
|
+
* This selective approach is more efficient than full JSON substitution
|
|
1799
|
+
* for regular configurations that do not have matrix parameters. The
|
|
1800
|
+
* remaining fields (properties, dependencies, actions) are processed
|
|
1801
|
+
* during inheritance resolution and dependency substitution phases.
|
|
1802
|
+
*
|
|
1803
|
+
* @returns A promise that resolves to the build configuration content with
|
|
1804
|
+
* the inherits field substituted.
|
|
1805
|
+
*
|
|
1806
|
+
* @throws {@link ConfigurationError}
|
|
1807
|
+
* If Liquid template substitution fails on the inherits field.
|
|
1808
|
+
*/
|
|
1809
|
+
// eslint-disable-next-line max-len
|
|
1810
|
+
protected async _substituteInherits(): Promise<JsonBuildConfigurationContent> {
|
|
1811
|
+
const log = this._log
|
|
1812
|
+
|
|
1813
|
+
let localJsonBuildConfiguration: JsonBuildConfigurationContent
|
|
1814
|
+
|
|
1815
|
+
// For non-templates, first perform substitutions on 'inherits' only.
|
|
1816
|
+
// The rest of the entries are collected as-is and processed later.
|
|
1817
|
+
const stringifiedJsonInherits = JSON.stringify(
|
|
1818
|
+
this.jsonBuildConfiguration.inherits ?? {}
|
|
1819
|
+
)
|
|
1820
|
+
if (hasLiquidSyntax(stringifiedJsonInherits)) {
|
|
1821
|
+
let substitutedJsonInherits
|
|
1822
|
+
try {
|
|
1823
|
+
substitutedJsonInherits = await performSubstitutions({
|
|
1824
|
+
log,
|
|
1825
|
+
engine: this.parentBuildConfigurations.engine,
|
|
1826
|
+
input: stringifiedJsonInherits,
|
|
1827
|
+
substitutionsVariables: {
|
|
1828
|
+
...this._substitutionsVariables,
|
|
1829
|
+
configuration: {
|
|
1830
|
+
...this.jsonBuildConfiguration,
|
|
1831
|
+
name: this.name,
|
|
1832
|
+
},
|
|
1833
|
+
},
|
|
1834
|
+
})
|
|
1835
|
+
} catch (error) {
|
|
1836
|
+
const message =
|
|
1837
|
+
getErrorMessage(error) +
|
|
1838
|
+
` in buildConfiguration "${this.name}" inherits`
|
|
1839
|
+
throw new ConfigurationError(message)
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
localJsonBuildConfiguration = {
|
|
1843
|
+
...this.jsonBuildConfiguration,
|
|
1844
|
+
inherits: JSON.parse(
|
|
1845
|
+
substitutedJsonInherits
|
|
1846
|
+
) as JsonBuildConfigurationInherits,
|
|
1847
|
+
}
|
|
1848
|
+
} else {
|
|
1849
|
+
localJsonBuildConfiguration = this.jsonBuildConfiguration
|
|
1850
|
+
}
|
|
1851
|
+
return localJsonBuildConfiguration
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
/**
|
|
1855
|
+
* Parses the inherits field from JSON configuration.
|
|
1856
|
+
*
|
|
1857
|
+
* @remarks
|
|
1858
|
+
* This helper method extracts and normalises inheritance information from
|
|
1859
|
+
* the configuration, supporting both the current <code>inherits</code>
|
|
1860
|
+
* field and the deprecated <code>inherit</code> field. It handles both
|
|
1861
|
+
* string and array formats.
|
|
1862
|
+
*
|
|
1863
|
+
* Processing steps:
|
|
1864
|
+
*
|
|
1865
|
+
* <ol>
|
|
1866
|
+
* <li>Check for <code>inherits</code> field (current standard).</li>
|
|
1867
|
+
* <li>Fall back to <code>inherit</code> field (deprecated).</li>
|
|
1868
|
+
* <li>Convert single strings to single-element arrays.</li>
|
|
1869
|
+
* <li>Join array elements with line breaks and split to handle
|
|
1870
|
+
* multi-line strings.</li>
|
|
1871
|
+
* </ol>
|
|
1872
|
+
*
|
|
1873
|
+
* @param localJsonBuildConfiguration - The JSON configuration content.
|
|
1874
|
+
* @returns Array of inherited configuration names.
|
|
1875
|
+
*/
|
|
1876
|
+
private _parseInheritsField(
|
|
1877
|
+
localJsonBuildConfiguration: JsonBuildConfigurationContent
|
|
1878
|
+
): string[] {
|
|
1879
|
+
let jsonInherits: string[] = []
|
|
1880
|
+
if (isString(localJsonBuildConfiguration.inherits)) {
|
|
1881
|
+
jsonInherits = [localJsonBuildConfiguration.inherits as string]
|
|
1882
|
+
} else if (Array.isArray(localJsonBuildConfiguration.inherits)) {
|
|
1883
|
+
jsonInherits = localJsonBuildConfiguration.inherits as string[]
|
|
1884
|
+
} else if (isString(localJsonBuildConfiguration.inherit)) {
|
|
1885
|
+
jsonInherits = [localJsonBuildConfiguration.inherit as string]
|
|
1886
|
+
} else if (Array.isArray(localJsonBuildConfiguration.inherit)) {
|
|
1887
|
+
jsonInherits = localJsonBuildConfiguration.inherit as string[]
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
if (jsonInherits.length > 0) {
|
|
1891
|
+
const joinedInherits = jsonInherits.join(os.EOL)
|
|
1892
|
+
return joinedInherits.split(os.EOL)
|
|
1893
|
+
}
|
|
1894
|
+
return jsonInherits
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
/**
|
|
1898
|
+
* Processes and merges a single inherited configuration.
|
|
1899
|
+
*
|
|
1900
|
+
* @remarks
|
|
1901
|
+
* This helper method handles the initialisation of a single inherited
|
|
1902
|
+
* configuration and merges its properties, dependencies, and actions into
|
|
1903
|
+
* the current configuration.
|
|
1904
|
+
*
|
|
1905
|
+
* Processing steps:
|
|
1906
|
+
*
|
|
1907
|
+
* <ol>
|
|
1908
|
+
* <li>Detect circular references by checking the inherited names set.</li>
|
|
1909
|
+
* <li>Initialise the inherited configuration recursively.</li>
|
|
1910
|
+
* <li>Merge properties, dependencies, and devDependencies using spread
|
|
1911
|
+
* operator (later values override earlier ones).</li>
|
|
1912
|
+
* <li>Collect inherited actions into the provided map.</li>
|
|
1913
|
+
* </ol>
|
|
1914
|
+
*
|
|
1915
|
+
* @param inheritedBuildConfigurationName - Name of the configuration to
|
|
1916
|
+
* inherit from.
|
|
1917
|
+
* @param inheritedActionsMap - Map to accumulate inherited actions.
|
|
1918
|
+
* @returns A promise that resolves when processing is complete.
|
|
1919
|
+
*
|
|
1920
|
+
* @throws {@link InputError}
|
|
1921
|
+
* If a circular inheritance reference is detected.
|
|
1922
|
+
*
|
|
1923
|
+
* @throws {@link InputError}
|
|
1924
|
+
* If the inherited configuration name does not exist.
|
|
1925
|
+
*/
|
|
1926
|
+
private async _processInheritedConfiguration(
|
|
1927
|
+
inheritedBuildConfigurationName: string,
|
|
1928
|
+
inheritedActionsMap: Map<string, Action>
|
|
1929
|
+
): Promise<void> {
|
|
1930
|
+
if (inheritedBuildConfigurationName.trim() === '') {
|
|
1931
|
+
return
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
if (
|
|
1935
|
+
!this.parentBuildConfigurations.hasJson(inheritedBuildConfigurationName)
|
|
1936
|
+
) {
|
|
1937
|
+
throw new ConfigurationError(
|
|
1938
|
+
`buildConfiguration "${this.name}" ` +
|
|
1939
|
+
`inherits from missing "${inheritedBuildConfigurationName}"`
|
|
1940
|
+
)
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
if (this._inheritedNamesSet.has(inheritedBuildConfigurationName)) {
|
|
1944
|
+
throw new ConfigurationError(
|
|
1945
|
+
`buildConfiguration "${this.name}" ` +
|
|
1946
|
+
`inherits from circular reference ` +
|
|
1947
|
+
`"${inheritedBuildConfigurationName}"`
|
|
1948
|
+
)
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
this._inheritedNamesSet.add(inheritedBuildConfigurationName)
|
|
1952
|
+
|
|
1953
|
+
const inheritedBuildConfiguration = this.parentBuildConfigurations.get(
|
|
1954
|
+
inheritedBuildConfigurationName
|
|
1955
|
+
)
|
|
1956
|
+
|
|
1957
|
+
await inheritedBuildConfiguration.initialise()
|
|
1958
|
+
|
|
1959
|
+
// Merge properties, dependencies, devDependencies.
|
|
1960
|
+
// Later ones override earlier ones.
|
|
1961
|
+
this.properties = {
|
|
1962
|
+
...this.properties,
|
|
1963
|
+
...inheritedBuildConfiguration.properties,
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
this.dependencies = {
|
|
1967
|
+
...this.dependencies,
|
|
1968
|
+
...inheritedBuildConfiguration.dependencies,
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
this.devDependencies = {
|
|
1972
|
+
...this.devDependencies,
|
|
1973
|
+
...inheritedBuildConfiguration.devDependencies,
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1976
|
+
await inheritedBuildConfiguration.actions.initialise()
|
|
1977
|
+
for (const actionName of inheritedBuildConfiguration.actions.names) {
|
|
1978
|
+
const action = inheritedBuildConfiguration.actions.get(actionName)
|
|
1979
|
+
inheritedActionsMap.set(actionName, action)
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
/**
|
|
1984
|
+
* Processes inheritance for a build configuration.
|
|
1985
|
+
*
|
|
1986
|
+
* @remarks
|
|
1987
|
+
* This method implements the inheritance resolution mechanism for build
|
|
1988
|
+
* configurations, enabling configurations to share properties, dependencies,
|
|
1989
|
+
* and actions by inheriting from one or more base configurations,
|
|
1990
|
+
* recursively.
|
|
1991
|
+
*
|
|
1992
|
+
* Processing workflow:
|
|
1993
|
+
*
|
|
1994
|
+
* <ol>
|
|
1995
|
+
* <li>Extract inheritance specification from the configuration:
|
|
1996
|
+
* <ul>
|
|
1997
|
+
* <li>Supports both the current <code>inherits</code> field and the
|
|
1998
|
+
* deprecated <code>inherit</code> field for backwards
|
|
1999
|
+
* compatibility.</li>
|
|
2000
|
+
* <li>Handles both string format (single parent) and array format
|
|
2001
|
+
* (multiple parents).</li>
|
|
2002
|
+
* <li>Normalises line-separated names within array elements to support
|
|
2003
|
+
* multi-line specifications.</li>
|
|
2004
|
+
* </ul>
|
|
2005
|
+
* </li>
|
|
2006
|
+
* <li>Process each inherited configuration sequentially:
|
|
2007
|
+
* <ul>
|
|
2008
|
+
* <li>Skip empty names from the inheritance list.</li>
|
|
2009
|
+
* <li>Validate that the inherited configuration exists in the parent
|
|
2010
|
+
* collection.</li>
|
|
2011
|
+
* <li>Detect circular references by checking
|
|
2012
|
+
* <code>_inheritedNamesSet</code>.</li>
|
|
2013
|
+
* <li>Recursively initialise the inherited configuration (which may
|
|
2014
|
+
* itself have inheritance).</li>
|
|
2015
|
+
* </ul>
|
|
2016
|
+
* </li>
|
|
2017
|
+
* <li>Merge inherited content into the current configuration:
|
|
2018
|
+
* <ul>
|
|
2019
|
+
* <li>Properties: Later inherited configurations override earlier ones,
|
|
2020
|
+
* local properties override all inherited.</li>
|
|
2021
|
+
* <li>Dependencies and devDependencies: Same override behaviour as
|
|
2022
|
+
* properties.</li>
|
|
2023
|
+
* <li>Actions: Collected into a map where later definitions override
|
|
2024
|
+
* earlier ones with the same name.</li>
|
|
2025
|
+
* </ul>
|
|
2026
|
+
* </li>
|
|
2027
|
+
* </ol>
|
|
2028
|
+
*
|
|
2029
|
+
* The inheritance chain is processed depth-first, ensuring that transitive
|
|
2030
|
+
* inheritance (A inherits B, B inherits C) is fully resolved before merging
|
|
2031
|
+
* properties. Circular references are detected to prevent infinite recursion.
|
|
2032
|
+
*
|
|
2033
|
+
* @param localJsonBuildConfiguration - The JSON configuration content after
|
|
2034
|
+
* template or inherits field substitution.
|
|
2035
|
+
* @returns A promise that resolves to a map of inherited actions, where
|
|
2036
|
+
* keys are action names and values are action instances from all inherited
|
|
2037
|
+
* configurations.
|
|
2038
|
+
*
|
|
2039
|
+
* @throws {@link InputError}
|
|
2040
|
+
* If an inherited configuration name does not exist in the parent collection.
|
|
2041
|
+
*
|
|
2042
|
+
* @throws {@link InputError}
|
|
2043
|
+
* If a circular inheritance reference is detected.
|
|
2044
|
+
*/
|
|
2045
|
+
protected async _processInherits(
|
|
2046
|
+
localJsonBuildConfiguration: JsonBuildConfigurationContent
|
|
2047
|
+
): Promise<Map<string, Action>> {
|
|
2048
|
+
const log = this._log
|
|
2049
|
+
|
|
2050
|
+
const inheritsNames = this._parseInheritsField(localJsonBuildConfiguration)
|
|
2051
|
+
this.inheritsNames = inheritsNames
|
|
2052
|
+
|
|
2053
|
+
log.trace(this.name, 'inherits from', this.inheritsNames)
|
|
2054
|
+
|
|
2055
|
+
const inheritedActionsMap: Map<string, Action> = new Map<string, Action>()
|
|
2056
|
+
|
|
2057
|
+
for (const inheritedBuildConfigurationName of inheritsNames) {
|
|
2058
|
+
await this._processInheritedConfiguration(
|
|
2059
|
+
inheritedBuildConfigurationName,
|
|
2060
|
+
inheritedActionsMap
|
|
2061
|
+
)
|
|
2062
|
+
}
|
|
2063
|
+
|
|
2064
|
+
return inheritedActionsMap
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
/**
|
|
2068
|
+
* Computes the build folder relative path for this configuration.
|
|
2069
|
+
*
|
|
2070
|
+
* @remarks
|
|
2071
|
+
* This method resolves the build folder relative path property when
|
|
2072
|
+
* provided and uses a default value based on the configuration name
|
|
2073
|
+
* otherwise.
|
|
2074
|
+
*
|
|
2075
|
+
* Resolution strategy:
|
|
2076
|
+
*
|
|
2077
|
+
* <ol>
|
|
2078
|
+
* <li>Check if buildFolderRelativePath property exists in configuration
|
|
2079
|
+
* properties.</li>
|
|
2080
|
+
* <li>If present and non-empty, perform Liquid substitutions with the
|
|
2081
|
+
* full configuration context.</li>
|
|
2082
|
+
* <li>If substitution fails or property is empty/missing, generate a
|
|
2083
|
+
* default path: `build/{filtered-configuration-name}` where the
|
|
2084
|
+
* configuration name is sanitized for filesystem compatibility.</li>
|
|
2085
|
+
* </ol>
|
|
2086
|
+
*
|
|
2087
|
+
* The computed path is added back to the properties as
|
|
2088
|
+
* `buildFolderRelativePath` for use in subsequent substitutions.
|
|
2089
|
+
*
|
|
2090
|
+
* @returns A promise that resolves to the build folder relative path.
|
|
2091
|
+
*/
|
|
2092
|
+
protected async _getBuildFolderRelativePath(): Promise<string> {
|
|
2093
|
+
const log = this._log
|
|
2094
|
+
|
|
2095
|
+
let folderPath: string
|
|
2096
|
+
if (
|
|
2097
|
+
buildFolderRelativePathPropertyName in
|
|
2098
|
+
this._substitutionsVariables.properties
|
|
2099
|
+
) {
|
|
2100
|
+
folderPath = this._substitutionsVariables.properties[
|
|
2101
|
+
buildFolderRelativePathPropertyName
|
|
2102
|
+
] as string
|
|
2103
|
+
if (folderPath !== '') {
|
|
2104
|
+
try {
|
|
2105
|
+
// log.trace(this.#substitutionsVariables.configuration)
|
|
2106
|
+
const substitutedFolderPath = await performSubstitutions({
|
|
2107
|
+
log,
|
|
2108
|
+
engine: this.parentBuildConfigurations.engine,
|
|
2109
|
+
input: folderPath,
|
|
2110
|
+
substitutionsVariables: this._substitutionsVariables,
|
|
2111
|
+
})
|
|
2112
|
+
return substitutedFolderPath
|
|
2113
|
+
} catch (error) {
|
|
2114
|
+
const message =
|
|
2115
|
+
getErrorMessage(error) + ` in buildConfiguration "${this.name}"`
|
|
2116
|
+
throw new ConfigurationError(message)
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
|
|
2121
|
+
// Provide a default value, based on the name.
|
|
2122
|
+
const defaultFolderPath = path.join('build', filterPath(this.name))
|
|
2123
|
+
return defaultFolderPath
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
|
|
2127
|
+
// ----------------------------------------------------------------------------
|