@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,1157 @@
|
|
|
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 os from 'node:os'
|
|
16
|
+
|
|
17
|
+
import { Logger } from '@xpack/logger'
|
|
18
|
+
|
|
19
|
+
// ----------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
LiquidSubstitutionsVariables,
|
|
23
|
+
LiquidSubstitutionsStrings,
|
|
24
|
+
} from '../data/substitutions-variables.js'
|
|
25
|
+
import {
|
|
26
|
+
isJsonObject,
|
|
27
|
+
isString,
|
|
28
|
+
isJsonArray,
|
|
29
|
+
} from '../functions/is-something.js'
|
|
30
|
+
import { performSubstitutions } from '../functions/perform-substitutions.js'
|
|
31
|
+
import { getErrorMessage, hasLiquidSyntax } from '../functions/utils.js'
|
|
32
|
+
import {
|
|
33
|
+
JsonActionContent,
|
|
34
|
+
JsonActions,
|
|
35
|
+
JsonActionTemplate,
|
|
36
|
+
} from '../types/json.js'
|
|
37
|
+
import { BuildConfiguration } from './build-configurations.js'
|
|
38
|
+
import { ConfigurationError } from './errors.js'
|
|
39
|
+
import { LiquidEngine } from './liquid-engine.js'
|
|
40
|
+
import { TemplateExpander } from './template-expander.js'
|
|
41
|
+
|
|
42
|
+
// ============================================================================
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Configuration parameters for constructing an actions collection instance.
|
|
46
|
+
*
|
|
47
|
+
* @remarks
|
|
48
|
+
* This interface defines the required configuration for creating an
|
|
49
|
+
* instance of {@link Actions}. Most properties are mandatory except for
|
|
50
|
+
* the optional <code>inheritedActionsMap</code> and
|
|
51
|
+
* <code>buildConfiguration</code> parameters.
|
|
52
|
+
*
|
|
53
|
+
* The parameters provide the actions collection with access to the Liquid
|
|
54
|
+
* templating engine, substitution variables hierarchy, action definitions
|
|
55
|
+
* from the package manifest, optional inherited actions from a parent
|
|
56
|
+
* package, optional build configuration context, and the logger for
|
|
57
|
+
* diagnostic output.
|
|
58
|
+
*/
|
|
59
|
+
export interface ActionsConstructorParameters {
|
|
60
|
+
/**
|
|
61
|
+
* The Liquid templating engine for variable substitution.
|
|
62
|
+
*/
|
|
63
|
+
engine: LiquidEngine
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The variables available for substitution in action definitions.
|
|
67
|
+
*/
|
|
68
|
+
substitutionsVariables: LiquidSubstitutionsVariables
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The JSON object containing action definitions, or undefined if there are
|
|
72
|
+
* no actions.
|
|
73
|
+
*/
|
|
74
|
+
jsonActions: JsonActions | undefined
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Optional map of actions inherited from a parent package.
|
|
78
|
+
*/
|
|
79
|
+
inheritedActionsMap?: Map<string, Action>
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Optional build configuration this actions collection belongs to.
|
|
83
|
+
*/
|
|
84
|
+
buildConfiguration?: BuildConfiguration
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The logger instance for output and diagnostics.
|
|
88
|
+
*/
|
|
89
|
+
log: Logger
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* A collection of <b>xpm</b> actions for a build configuration or
|
|
94
|
+
* the entire package.
|
|
95
|
+
*
|
|
96
|
+
* @remarks
|
|
97
|
+
* This class manages a collection of named actions, each containing one or
|
|
98
|
+
* more commands to be executed. Actions can belong to a package or a build
|
|
99
|
+
* configuration and support template-based definitions with matrix expansion
|
|
100
|
+
* to generate multiple actions from a single template.
|
|
101
|
+
*
|
|
102
|
+
* The collection always exists, even as empty if no actions are defined.
|
|
103
|
+
*
|
|
104
|
+
* Action lifecycle phases:
|
|
105
|
+
*
|
|
106
|
+
* <ol>
|
|
107
|
+
* <li><b>Construction:</b> Basic setup with optional inheritance from parent
|
|
108
|
+
* package.</li>
|
|
109
|
+
* <li><b>Initialisation:</b> Template name expansion without content
|
|
110
|
+
* evaluation.</li>
|
|
111
|
+
* <li><b>Retrieval:</b> On-demand instantiation when accessed via
|
|
112
|
+
* <code>get()</code>.</li>
|
|
113
|
+
* <li><b>Action Initialisation:</b> Liquid template evaluation and
|
|
114
|
+
* substitution.</li>
|
|
115
|
+
* </ol>
|
|
116
|
+
*
|
|
117
|
+
* This multi-phase approach ensures efficient resource usage by deferring
|
|
118
|
+
* expensive operations until actions are actually needed.
|
|
119
|
+
*/
|
|
120
|
+
export class Actions {
|
|
121
|
+
// --------------------------------------------------------------------------
|
|
122
|
+
// Public Members.
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* The logger instance for output and diagnostics.
|
|
126
|
+
*
|
|
127
|
+
* @remarks
|
|
128
|
+
* This logger is used throughout the lifecycle of actions collection to
|
|
129
|
+
* provide trace-level diagnostics for debugging template expansion, action
|
|
130
|
+
* instantiation, and variable substitution. It enables visibility into the
|
|
131
|
+
* lazy evaluation process without impacting runtime performance when tracing
|
|
132
|
+
* is disabled.
|
|
133
|
+
*/
|
|
134
|
+
readonly log: Logger
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* The Liquid templating engine for variable substitution.
|
|
138
|
+
*
|
|
139
|
+
* @remarks
|
|
140
|
+
* This engine instance is shared across all actions in the collection and
|
|
141
|
+
* configured with custom filters for platform detection, path manipulation,
|
|
142
|
+
* and xpm-specific operations. It's used during both template action name
|
|
143
|
+
* expansion and later during individual action command substitution,
|
|
144
|
+
* ensuring consistent template processing throughout the action lifecycle.
|
|
145
|
+
*/
|
|
146
|
+
readonly engine: LiquidEngine
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* The variables available for substitution in action definitions.
|
|
150
|
+
*
|
|
151
|
+
* @remarks
|
|
152
|
+
* This comprehensive variable hierarchy provides context for template
|
|
153
|
+
* evaluation, including package metadata, build configuration properties,
|
|
154
|
+
* environment variables, platform detection, and path utilities.
|
|
155
|
+
*
|
|
156
|
+
* The hierarchy structure:
|
|
157
|
+
*
|
|
158
|
+
* <ol>
|
|
159
|
+
* <li><b>Base variables:</b> <code>env</code>, <code>os</code>,
|
|
160
|
+
* <code>path</code> (always available).</li>
|
|
161
|
+
* <li><b>Package variables:</b> <code>name</code>, <code>version</code>,
|
|
162
|
+
* <code>dependencies</code>,
|
|
163
|
+
* <code>devDependencies</code>.</li>
|
|
164
|
+
* <li><b>Configuration variables:</b> build folder paths, compiler
|
|
165
|
+
* settings.</li>
|
|
166
|
+
* <li><b>Properties:</b> custom key-value pairs from package or
|
|
167
|
+
* configuration.</li>
|
|
168
|
+
* <li><b>Matrix:</b> parameter combinations for template-generated
|
|
169
|
+
* actions (added per action during initialisation).</li>
|
|
170
|
+
* </ol>
|
|
171
|
+
*
|
|
172
|
+
* These variables are accessible in Liquid templates using dot notation
|
|
173
|
+
* (e.g., `{{ package.name }}`,
|
|
174
|
+
* `{{ configuration.buildFolderRelativePath }}`).
|
|
175
|
+
*/
|
|
176
|
+
readonly substitutionsVariables: LiquidSubstitutionsVariables
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* The JSON object containing action definitions from the package manifest.
|
|
180
|
+
*
|
|
181
|
+
* @remarks
|
|
182
|
+
* This object holds the raw action definitions as they appear in the
|
|
183
|
+
* `package.json` `xpack.actions` section or within a build configuration's
|
|
184
|
+
* actions. Action definitions can be:
|
|
185
|
+
*
|
|
186
|
+
* <ol>
|
|
187
|
+
* <li><b>Simple strings:</b> Single command to execute.</li>
|
|
188
|
+
* <li><b>String arrays:</b> Multiple commands executed sequentially.</li>
|
|
189
|
+
* <li><b>Template objects:</b> With <code>matrix</code> and
|
|
190
|
+
* <code>template</code> properties for
|
|
191
|
+
* generating multiple actions from a single definition.</li>
|
|
192
|
+
* </ol>
|
|
193
|
+
*
|
|
194
|
+
* Template action names (containing `{{` markers) trigger matrix expansion
|
|
195
|
+
* during initialisation, creating concrete actions from the Cartesian
|
|
196
|
+
* product of matrix parameter values.
|
|
197
|
+
*/
|
|
198
|
+
readonly jsonActions: JsonActions
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* The build configuration this actions collection belongs to, if any.
|
|
202
|
+
*
|
|
203
|
+
* @remarks
|
|
204
|
+
* This optional reference establishes the hierarchical relationship between
|
|
205
|
+
* actions and build configurations, affecting variable substitution scope
|
|
206
|
+
* and action inheritance.
|
|
207
|
+
*
|
|
208
|
+
* When defined:
|
|
209
|
+
*
|
|
210
|
+
* <ol>
|
|
211
|
+
* <li>Actions inherit configuration-specific variables (build folder paths,
|
|
212
|
+
* compiler settings, toolchain properties).</li>
|
|
213
|
+
* <li>Actions belong to a specific configuration namespace rather than the
|
|
214
|
+
* package root.</li>
|
|
215
|
+
* <li>Logging and diagnostics include the configuration name for
|
|
216
|
+
* context.</li>
|
|
217
|
+
* </ol>
|
|
218
|
+
*
|
|
219
|
+
* When `undefined`:
|
|
220
|
+
*
|
|
221
|
+
* <ol>
|
|
222
|
+
* <li>Actions belong to the package root (<code>xpack.actions</code> in
|
|
223
|
+
* <code>package.json</code>).</li>
|
|
224
|
+
* <li>Only package-level and global variables are available for
|
|
225
|
+
* substitution.</li>
|
|
226
|
+
* </ol>
|
|
227
|
+
*/
|
|
228
|
+
readonly buildConfiguration: BuildConfiguration | undefined
|
|
229
|
+
|
|
230
|
+
// --------------------------------------------------------------------------
|
|
231
|
+
// Protected Members.
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Map of action names to their corresponding action instances.
|
|
235
|
+
*
|
|
236
|
+
* @remarks
|
|
237
|
+
* This map serves as the primary action registry, populated during
|
|
238
|
+
* collection initialisation with entries for all discovered actions.
|
|
239
|
+
*
|
|
240
|
+
* Key characteristics:
|
|
241
|
+
*
|
|
242
|
+
* <ol>
|
|
243
|
+
* <li>Known only after <code>Actions.initialise()</code>
|
|
244
|
+
* completes.</li>
|
|
245
|
+
* <li>Possibly empty if there are no actions defined.</li>
|
|
246
|
+
* <li>Values can be <code>undefined</code> to indicate an action
|
|
247
|
+
* exists but hasn't
|
|
248
|
+
* been instantiated yet (lazy loading).</li>
|
|
249
|
+
* <li>For template actions, contains one entry per expanded combination,
|
|
250
|
+
* not the original template definition.</li>
|
|
251
|
+
* </ol>
|
|
252
|
+
*
|
|
253
|
+
* Actions transition from `undefined` to instantiated when first accessed
|
|
254
|
+
* via {@link Actions.get}, implementing the lazy evaluation
|
|
255
|
+
* pattern.
|
|
256
|
+
*/
|
|
257
|
+
protected readonly _actionsMap: Map<string, Action | undefined> = new Map<
|
|
258
|
+
string,
|
|
259
|
+
Action | undefined
|
|
260
|
+
>()
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Set of all action names for quick lookup.
|
|
264
|
+
*
|
|
265
|
+
* @remarks
|
|
266
|
+
* This set provides O(1) existence checks for action names, enabling
|
|
267
|
+
* efficient validation during template expansion and duplicate detection.
|
|
268
|
+
*
|
|
269
|
+
* Key characteristics:
|
|
270
|
+
*
|
|
271
|
+
* <ol>
|
|
272
|
+
* <li>Known only after <code>Actions.initialise()</code>
|
|
273
|
+
* completes.</li>
|
|
274
|
+
* <li>Contains all action names including those generated from
|
|
275
|
+
* templates.</li>
|
|
276
|
+
* <li>Used to detect duplicate action names that might arise from template
|
|
277
|
+
* expansion conflicts or explicit duplicates in
|
|
278
|
+
* <code>package.json</code>.</li>
|
|
279
|
+
* </ol>
|
|
280
|
+
*
|
|
281
|
+
* This redundant storage (alongside `_actionsMap`) is justified by the
|
|
282
|
+
* performance benefit for name existence checks, especially in packages
|
|
283
|
+
* with many actions.
|
|
284
|
+
*/
|
|
285
|
+
protected readonly _namesSet: Set<string> = new Set<string>()
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Map of expanded action names to their original JSON action names.
|
|
289
|
+
*
|
|
290
|
+
* @remarks
|
|
291
|
+
* This reverse mapping enables retrieving the original action definition
|
|
292
|
+
* from `jsonActions` when lazy-loading action instances.
|
|
293
|
+
*
|
|
294
|
+
* Mapping behavior:
|
|
295
|
+
*
|
|
296
|
+
* <ol>
|
|
297
|
+
* <li><b>For regular actions:</b> Maps action name to itself (identity
|
|
298
|
+
* mapping).</li>
|
|
299
|
+
* <li><b>For template actions:</b> Maps each generated action name back to
|
|
300
|
+
* the original template name (e.g.,
|
|
301
|
+
* <code>test-x64</code> → <code>test-\{\{ matrix.arch \}\}</code>).</li>
|
|
302
|
+
* <li>Enables <code>Actions.get()</code> to locate the correct JSON
|
|
303
|
+
* definition when instantiating an action on demand.</li>
|
|
304
|
+
* </ol>
|
|
305
|
+
*
|
|
306
|
+
* This indirection is essential for the lazy evaluation pattern, allowing
|
|
307
|
+
* deferred instantiation while maintaining the connection to original
|
|
308
|
+
* definitions.
|
|
309
|
+
*/
|
|
310
|
+
protected readonly _jsonActionsNamesMap: Map<string, string> = new Map<
|
|
311
|
+
string,
|
|
312
|
+
string
|
|
313
|
+
>()
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Flag indicating whether the actions collection has been initialised.
|
|
317
|
+
*
|
|
318
|
+
* @remarks
|
|
319
|
+
* This flag prevents redundant initialisation and ensures idempotent
|
|
320
|
+
* behavior when {@link Actions.initialise} is called multiple
|
|
321
|
+
* times.
|
|
322
|
+
*
|
|
323
|
+
* State transitions:
|
|
324
|
+
*
|
|
325
|
+
* <ol>
|
|
326
|
+
* <li>Initially <code>false</code> after construction.</li>
|
|
327
|
+
* <li>Set to <code>true</code> after successful template expansion and
|
|
328
|
+
* action name
|
|
329
|
+
* registration.</li>
|
|
330
|
+
* <li>Checked at the beginning of <code>Actions.initialise()</code> to
|
|
331
|
+
* return early if already initialised.</li>
|
|
332
|
+
* </ol>
|
|
333
|
+
*
|
|
334
|
+
* This pattern supports safe repeated calls during complex initialisation
|
|
335
|
+
* sequences without duplicating work or corrupting internal state.
|
|
336
|
+
*/
|
|
337
|
+
protected _isInitialised = false
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Cached array of all action names in the collection.
|
|
341
|
+
*
|
|
342
|
+
* @remarks
|
|
343
|
+
* This array provides O(1) access to action names without repeatedly
|
|
344
|
+
* creating new arrays from the map keys, improving performance when the
|
|
345
|
+
* names are accessed multiple times.
|
|
346
|
+
*
|
|
347
|
+
* Key characteristics:
|
|
348
|
+
*
|
|
349
|
+
* <ol>
|
|
350
|
+
* <li>Empty initially after construction.</li>
|
|
351
|
+
* <li>Populated during <code>Actions.initialise()</code> after all
|
|
352
|
+
* action names
|
|
353
|
+
* are determined.</li>
|
|
354
|
+
* <li>Contains all action names including those generated from
|
|
355
|
+
* templates.</li>
|
|
356
|
+
* <li>Returned by the <code>names</code> getter for efficient repeated
|
|
357
|
+
* access.</li>
|
|
358
|
+
* </ol>
|
|
359
|
+
*
|
|
360
|
+
* This cached approach avoids the overhead of calling
|
|
361
|
+
* `Array.from(map.keys())` on every access whilst still
|
|
362
|
+
* providing a clean getter interface.
|
|
363
|
+
*/
|
|
364
|
+
protected _names: string[] = []
|
|
365
|
+
|
|
366
|
+
// --------------------------------------------------------------------------
|
|
367
|
+
// Constructor and async initialiser.
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Constructs an actions collection instance.
|
|
371
|
+
*
|
|
372
|
+
* @remarks
|
|
373
|
+
* The constructor performs partial initialisation. Complete initialisation
|
|
374
|
+
* requires calling the `Actions.initialise()` method.
|
|
375
|
+
*
|
|
376
|
+
* @param log - The logger instance for output and diagnostics.
|
|
377
|
+
*/
|
|
378
|
+
constructor({
|
|
379
|
+
engine,
|
|
380
|
+
substitutionsVariables,
|
|
381
|
+
jsonActions,
|
|
382
|
+
inheritedActionsMap,
|
|
383
|
+
buildConfiguration,
|
|
384
|
+
log,
|
|
385
|
+
}: ActionsConstructorParameters) {
|
|
386
|
+
assert(log, 'log is required')
|
|
387
|
+
assert(engine, 'engine is required')
|
|
388
|
+
assert(substitutionsVariables, 'substitutionsVariables is required')
|
|
389
|
+
|
|
390
|
+
if (buildConfiguration !== undefined) {
|
|
391
|
+
log.trace(`${Actions.name}()` + ` @${buildConfiguration.name}`)
|
|
392
|
+
} else {
|
|
393
|
+
log.trace(`${Actions.name}()`)
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
this.log = log
|
|
397
|
+
this.engine = engine
|
|
398
|
+
this.substitutionsVariables = substitutionsVariables
|
|
399
|
+
this.jsonActions = jsonActions ?? {}
|
|
400
|
+
if (buildConfiguration !== undefined) {
|
|
401
|
+
this.buildConfiguration = buildConfiguration
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// If there are inherited actions, add them to the map.
|
|
405
|
+
// They might be overridden by the current definitions.
|
|
406
|
+
if (inheritedActionsMap !== undefined) {
|
|
407
|
+
for (const [
|
|
408
|
+
inheritedActionName,
|
|
409
|
+
inheritedAction,
|
|
410
|
+
] of inheritedActionsMap) {
|
|
411
|
+
// Make copies of the actions, do not alter the inherited ones.
|
|
412
|
+
const action = new Action({
|
|
413
|
+
actionName: inheritedActionName,
|
|
414
|
+
jsonAction: inheritedAction.jsonAction,
|
|
415
|
+
parentActions: this,
|
|
416
|
+
})
|
|
417
|
+
this._actionsMap.set(inheritedActionName, action)
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// The rest of the initialisation is done in the async initialiser.
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Completes the async initialisation of the actions collection.
|
|
426
|
+
*
|
|
427
|
+
* @remarks
|
|
428
|
+
* This method implements the first step of lazy evaluation. It processes
|
|
429
|
+
* all action definitions by expanding template action names based on matrix
|
|
430
|
+
* parameters, but does not evaluate the action content or perform Liquid
|
|
431
|
+
* substitutions. The actual template evaluation and variable substitution
|
|
432
|
+
* occur later when individual actions are initialised via
|
|
433
|
+
* {@link Action.initialise}, and only for actions that are
|
|
434
|
+
* actually used. This approach avoids unnecessary operations on unused
|
|
435
|
+
* actions. The method also validates that all expanded action names are
|
|
436
|
+
* unique.
|
|
437
|
+
*
|
|
438
|
+
* @returns A promise that resolves to `true` if initialisation was
|
|
439
|
+
* performed, or `false` if already initialised.
|
|
440
|
+
*
|
|
441
|
+
* @throws {@link ConfigurationError}
|
|
442
|
+
* If duplicate action names are detected or if template expansion fails.
|
|
443
|
+
*/
|
|
444
|
+
async initialise(): Promise<boolean> {
|
|
445
|
+
const log = this.log
|
|
446
|
+
|
|
447
|
+
if (this._isInitialised) {
|
|
448
|
+
if (this.buildConfiguration !== undefined) {
|
|
449
|
+
log.trace(
|
|
450
|
+
`${Actions.name}.initialise()` +
|
|
451
|
+
` @${this.buildConfiguration.name} again`
|
|
452
|
+
)
|
|
453
|
+
} else {
|
|
454
|
+
log.trace(`${Actions.name}.initialise() again`)
|
|
455
|
+
}
|
|
456
|
+
return false
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (this.buildConfiguration !== undefined) {
|
|
460
|
+
log.trace(
|
|
461
|
+
`${Actions.name}.initialise()` + ` @${this.buildConfiguration.name}`
|
|
462
|
+
)
|
|
463
|
+
} else {
|
|
464
|
+
log.trace(`${Actions.name}.initialise()`)
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
for (const [actionName, jsonAction] of Object.entries(this.jsonActions)) {
|
|
468
|
+
if (hasLiquidSyntax(actionName)) {
|
|
469
|
+
await this._processTemplate({
|
|
470
|
+
actionName,
|
|
471
|
+
jsonActionTemplate: jsonAction as JsonActionTemplate,
|
|
472
|
+
})
|
|
473
|
+
} else {
|
|
474
|
+
if (this._namesSet.has(actionName)) {
|
|
475
|
+
throw new ConfigurationError(
|
|
476
|
+
`action name "${actionName}" already defined`
|
|
477
|
+
)
|
|
478
|
+
} else {
|
|
479
|
+
this._actionsMap.set(actionName, undefined)
|
|
480
|
+
this._jsonActionsNamesMap.set(actionName, actionName)
|
|
481
|
+
this._namesSet.add(actionName)
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
const names = Array.from(this._actionsMap.keys())
|
|
486
|
+
this._names = names
|
|
487
|
+
|
|
488
|
+
this.log.trace(`${Actions.name}.initialise() =>`, names)
|
|
489
|
+
|
|
490
|
+
this._isInitialised = true
|
|
491
|
+
return true
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// --------------------------------------------------------------------------
|
|
495
|
+
// Public Methods.
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* The number of actions in the collection.
|
|
499
|
+
*
|
|
500
|
+
* @remarks
|
|
501
|
+
* This value is known only after `initialise()`.
|
|
502
|
+
*
|
|
503
|
+
* This getter provides direct access to the collection size, enabling
|
|
504
|
+
* callers to check for emptiness or iterate with knowledge of the
|
|
505
|
+
* collection's extent.
|
|
506
|
+
*
|
|
507
|
+
* @returns The number of actions in the collection.
|
|
508
|
+
*/
|
|
509
|
+
get size(): number {
|
|
510
|
+
assert(
|
|
511
|
+
this._isInitialised,
|
|
512
|
+
'Actions collection must be initialised before accessing size'
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
return this._actionsMap.size
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Indicates whether the actions collection is empty.
|
|
520
|
+
*
|
|
521
|
+
* @remarks
|
|
522
|
+
* This value is known only after `initialise()`.
|
|
523
|
+
*
|
|
524
|
+
* @returns `true` if there are no actions, `false` otherwise.
|
|
525
|
+
*/
|
|
526
|
+
get isEmpty(): boolean {
|
|
527
|
+
assert(
|
|
528
|
+
this._isInitialised,
|
|
529
|
+
'Actions collection must be initialised before accessing isEmpty'
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
return this._actionsMap.size === 0
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* The names of all actions in the collection.
|
|
537
|
+
*
|
|
538
|
+
* @remarks
|
|
539
|
+
* This value is known only after `initialise()`.
|
|
540
|
+
*
|
|
541
|
+
* This getter returns the cached array of action names for efficient
|
|
542
|
+
* repeated access without recreating the array.
|
|
543
|
+
*
|
|
544
|
+
* @returns An array of action names.
|
|
545
|
+
*/
|
|
546
|
+
get names(): string[] {
|
|
547
|
+
assert(
|
|
548
|
+
this._isInitialised,
|
|
549
|
+
'Actions collection must be initialised before accessing names'
|
|
550
|
+
)
|
|
551
|
+
return this._names
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Checks whether an action with the specified name exists.
|
|
556
|
+
*
|
|
557
|
+
* @remarks
|
|
558
|
+
* This value is known only after `initialise()`.
|
|
559
|
+
*
|
|
560
|
+
* @param actionName - The name of the action to check.
|
|
561
|
+
* @returns `true` if the action exists, `false` otherwise.
|
|
562
|
+
*/
|
|
563
|
+
has(actionName: string): boolean {
|
|
564
|
+
assert(
|
|
565
|
+
this._isInitialised,
|
|
566
|
+
'Actions collection must be initialised before accessing has()'
|
|
567
|
+
)
|
|
568
|
+
|
|
569
|
+
return this._actionsMap.has(actionName)
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Retrieves an action by name, creating it if not yet instantiated.
|
|
574
|
+
*
|
|
575
|
+
* @remarks
|
|
576
|
+
* This method implements lazy evaluation to avoid unnecessary operations.
|
|
577
|
+
* Actions are instantiated on demand but remain uninitialised until actually
|
|
578
|
+
* used. The two-step process works as follows:
|
|
579
|
+
*
|
|
580
|
+
* <ol>
|
|
581
|
+
* <li>During collection initialisation
|
|
582
|
+
* (<code>Actions.initialise()</code>),
|
|
583
|
+
* only the matrix of options is evaluated for each template, expanding
|
|
584
|
+
* only the action names without processing their content.</li>
|
|
585
|
+
* <li>Later, when an action is accessed via this method and subsequently
|
|
586
|
+
* initialised (<code>Action.initialise()</code>), the template is
|
|
587
|
+
* fully evaluated and Liquid substitutions are performed on the
|
|
588
|
+
* commands.</li>
|
|
589
|
+
* </ol>
|
|
590
|
+
*
|
|
591
|
+
* This approach ensures that only actions that are actually used incur the
|
|
592
|
+
* cost of template evaluation and variable substitution.
|
|
593
|
+
*
|
|
594
|
+
* @param actionName - The name of the action to retrieve.
|
|
595
|
+
* @returns The action instance.
|
|
596
|
+
*
|
|
597
|
+
* @throws {@link ConfigurationError}
|
|
598
|
+
* If an action with that name does not exist.
|
|
599
|
+
*/
|
|
600
|
+
get(actionName: string): Action {
|
|
601
|
+
assert(
|
|
602
|
+
this._isInitialised,
|
|
603
|
+
'Actions collection must be initialised before accessing get()'
|
|
604
|
+
)
|
|
605
|
+
|
|
606
|
+
const log = this.log
|
|
607
|
+
log.trace(`${Actions.name}.get(${actionName})`)
|
|
608
|
+
|
|
609
|
+
let action = this._actionsMap.get(actionName)
|
|
610
|
+
if (action === undefined) {
|
|
611
|
+
const jsonActionName = this._jsonActionsNamesMap.get(actionName)
|
|
612
|
+
if (jsonActionName === undefined) {
|
|
613
|
+
throw new ConfigurationError(`action "${actionName}" does not exist`)
|
|
614
|
+
}
|
|
615
|
+
// Safety net: This fallback to empty string is defensive programming.
|
|
616
|
+
// The jsonActions[jsonActionName] should always be defined because
|
|
617
|
+
// _jsonActionsNamesMap is populated from the jsonActions keys during
|
|
618
|
+
// initialisation. The ?? '' provides protection against unexpected
|
|
619
|
+
// runtime inconsistencies between the map and the object.
|
|
620
|
+
/* c8 ignore start - safety net, action names are not undefined. */
|
|
621
|
+
const jsonAction: JsonActionContent = (this.jsonActions[jsonActionName] ??
|
|
622
|
+
'') as JsonActionContent
|
|
623
|
+
/* c8 ignore stop */
|
|
624
|
+
|
|
625
|
+
action = new Action({
|
|
626
|
+
actionName,
|
|
627
|
+
jsonAction,
|
|
628
|
+
parentActions: this,
|
|
629
|
+
})
|
|
630
|
+
this._actionsMap.set(actionName, action)
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
return action
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// --------------------------------------------------------------------------
|
|
637
|
+
// Private Methods.
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Processes a template action by expanding it and registering the generated
|
|
641
|
+
* actions.
|
|
642
|
+
*
|
|
643
|
+
* @remarks
|
|
644
|
+
* This helper method is called during collection initialisation for each
|
|
645
|
+
* action whose name contains template syntax (<code>\{\{</code> markers).
|
|
646
|
+
*
|
|
647
|
+
* Processing steps:
|
|
648
|
+
*
|
|
649
|
+
* <ol>
|
|
650
|
+
* <li>Calls <code>_expandTemplateActions()</code> to generate all action
|
|
651
|
+
* instances from the template's matrix parameters.</li>
|
|
652
|
+
* <li>Validates that each expanded action name is unique and does not
|
|
653
|
+
* conflict with existing actions.</li>
|
|
654
|
+
* <li>Registers each expanded action in the internal maps:
|
|
655
|
+
* <ul>
|
|
656
|
+
* <li><code>_actionsMap</code>: Maps name to action instance.</li>
|
|
657
|
+
* <li><code>_jsonActionsNamesMap</code>: Maps expanded name back to
|
|
658
|
+
* original template name.</li>
|
|
659
|
+
* <li><code>_namesSet</code>: Tracks all registered names for
|
|
660
|
+
* duplicate detection.</li>
|
|
661
|
+
* </ul>
|
|
662
|
+
* </li>
|
|
663
|
+
* </ol>
|
|
664
|
+
*
|
|
665
|
+
* @param actionName - The template action name containing Liquid variables.
|
|
666
|
+
* @param jsonActionTemplate - The JSON template definition containing matrix
|
|
667
|
+
* parameters and an action template.
|
|
668
|
+
* @returns A promise that resolves when processing is complete.
|
|
669
|
+
*
|
|
670
|
+
* @throws {@link ConfigurationError}
|
|
671
|
+
* If duplicate action names are detected during expansion or if template
|
|
672
|
+
* expansion fails.
|
|
673
|
+
*/
|
|
674
|
+
protected async _processTemplate({
|
|
675
|
+
actionName,
|
|
676
|
+
jsonActionTemplate,
|
|
677
|
+
}: {
|
|
678
|
+
actionName: string
|
|
679
|
+
jsonActionTemplate: JsonActionTemplate
|
|
680
|
+
}): Promise<void> {
|
|
681
|
+
// Expand template and generate multiple actions.
|
|
682
|
+
try {
|
|
683
|
+
const expandedActionsMap = await this._expandTemplateActions({
|
|
684
|
+
actionName,
|
|
685
|
+
jsonActionTemplate,
|
|
686
|
+
})
|
|
687
|
+
for (const [expandedActionName, expandedAction] of expandedActionsMap) {
|
|
688
|
+
if (this._namesSet.has(expandedActionName)) {
|
|
689
|
+
throw new ConfigurationError(
|
|
690
|
+
`duplicate action name "${expandedActionName}" ` +
|
|
691
|
+
`could not be generated from template.`
|
|
692
|
+
)
|
|
693
|
+
} else {
|
|
694
|
+
this._actionsMap.set(expandedActionName, expandedAction)
|
|
695
|
+
this._jsonActionsNamesMap.set(expandedActionName, actionName)
|
|
696
|
+
this._namesSet.add(expandedActionName)
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
} catch (error) {
|
|
700
|
+
const message = getErrorMessage(error) + ` in action "${actionName}"`
|
|
701
|
+
throw new ConfigurationError(message)
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* Expands a template action into multiple concrete actions.
|
|
707
|
+
*
|
|
708
|
+
* @remarks
|
|
709
|
+
* This method uses the {@link TemplateExpander} to compute the Cartesian
|
|
710
|
+
* product of all matrix parameter values and creates a separate action for
|
|
711
|
+
* each combination, substituting matrix values into both the action name
|
|
712
|
+
* and command templates.
|
|
713
|
+
*
|
|
714
|
+
* Processing steps:
|
|
715
|
+
*
|
|
716
|
+
* <ol>
|
|
717
|
+
* <li>Validates matrix and template structure.</li>
|
|
718
|
+
* <li>Delegates to <code>TemplateExpander</code> for matrix processing and
|
|
719
|
+
* name expansion.</li>
|
|
720
|
+
* <li>Creates action instances via factory callback for each
|
|
721
|
+
* combination.</li>
|
|
722
|
+
* </ol>
|
|
723
|
+
*
|
|
724
|
+
* Matrix variables are scoped to individual actions and accessible via
|
|
725
|
+
* the `matrix` namespace during action command evaluation.
|
|
726
|
+
*
|
|
727
|
+
* @param actionName - The template action name containing Liquid variables.
|
|
728
|
+
* @param jsonActionTemplate - The JSON action template definition containing
|
|
729
|
+
* matrix parameters and a template.
|
|
730
|
+
* @returns A promise that resolves to a map of expanded action names to
|
|
731
|
+
* their corresponding action instances.
|
|
732
|
+
*
|
|
733
|
+
* @throws {@link ConfigurationError}
|
|
734
|
+
* If the matrix structure is invalid, template format is incorrect, or
|
|
735
|
+
* substitution fails.
|
|
736
|
+
*/
|
|
737
|
+
protected async _expandTemplateActions({
|
|
738
|
+
actionName,
|
|
739
|
+
jsonActionTemplate,
|
|
740
|
+
}: {
|
|
741
|
+
actionName: string
|
|
742
|
+
jsonActionTemplate: JsonActionTemplate
|
|
743
|
+
}): Promise<Map<string, Action>> {
|
|
744
|
+
const log = this.log
|
|
745
|
+
log.trace(`${Actions.name}.#expandTemplateActions(${actionName})`)
|
|
746
|
+
|
|
747
|
+
// Validate template structure
|
|
748
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
749
|
+
if (jsonActionTemplate.matrix == undefined) {
|
|
750
|
+
throw new ConfigurationError(`action "${actionName}" has no matrix`)
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
if (!isJsonObject(jsonActionTemplate.matrix)) {
|
|
754
|
+
throw new ConfigurationError(
|
|
755
|
+
`action "${actionName}" matrix is not an object`
|
|
756
|
+
)
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
760
|
+
if (jsonActionTemplate.template == undefined) {
|
|
761
|
+
throw new ConfigurationError(`action "${actionName}" has no template`)
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
if (
|
|
765
|
+
!isString(jsonActionTemplate.template) &&
|
|
766
|
+
!isJsonArray(jsonActionTemplate.template)
|
|
767
|
+
) {
|
|
768
|
+
throw new ConfigurationError(
|
|
769
|
+
`action "${actionName}" template is not a string or array`
|
|
770
|
+
)
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// Use TemplateExpander for matrix processing and expansion
|
|
774
|
+
const expander = new TemplateExpander<JsonActionContent, Action>({
|
|
775
|
+
engine: this.engine,
|
|
776
|
+
substitutionsVariables: this.substitutionsVariables,
|
|
777
|
+
log: this.log,
|
|
778
|
+
})
|
|
779
|
+
|
|
780
|
+
return await expander.expandTemplate({
|
|
781
|
+
templateName: actionName,
|
|
782
|
+
matrix: jsonActionTemplate.matrix,
|
|
783
|
+
templateContent: jsonActionTemplate.template,
|
|
784
|
+
templateType: 'action',
|
|
785
|
+
instanceFactory: (
|
|
786
|
+
expandedName: string,
|
|
787
|
+
combination: Record<string, string>,
|
|
788
|
+
templateContent: JsonActionContent
|
|
789
|
+
) =>
|
|
790
|
+
new Action({
|
|
791
|
+
actionName: expandedName,
|
|
792
|
+
jsonAction: templateContent,
|
|
793
|
+
parentActions: this,
|
|
794
|
+
matrixParameters: { ...combination },
|
|
795
|
+
}),
|
|
796
|
+
})
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// ============================================================================
|
|
801
|
+
|
|
802
|
+
/**
|
|
803
|
+
* Configuration parameters for constructing an action instance.
|
|
804
|
+
*
|
|
805
|
+
* @remarks
|
|
806
|
+
* This interface defines the required configuration for creating an
|
|
807
|
+
* instance of {@link Action}. Most properties are mandatory except for
|
|
808
|
+
* the optional <code>matrixParameters</code>, which is only needed for
|
|
809
|
+
* template-generated actions that were created from matrix expansion.
|
|
810
|
+
*
|
|
811
|
+
* The parameters provide the action with its identity (name), command
|
|
812
|
+
* definitions, access to the parent collection for shared resources, and
|
|
813
|
+
* optional matrix parameter values for template-generated actions.
|
|
814
|
+
*/
|
|
815
|
+
export interface ActionConstructorParameters {
|
|
816
|
+
/**
|
|
817
|
+
* The name of the action.
|
|
818
|
+
*/
|
|
819
|
+
actionName: string
|
|
820
|
+
|
|
821
|
+
/**
|
|
822
|
+
* The JSON definition of the action commands.
|
|
823
|
+
*/
|
|
824
|
+
jsonAction: JsonActionContent
|
|
825
|
+
|
|
826
|
+
/**
|
|
827
|
+
* The parent actions collection this action belongs to.
|
|
828
|
+
*/
|
|
829
|
+
parentActions: Actions
|
|
830
|
+
|
|
831
|
+
/**
|
|
832
|
+
* Optional matrix parameter values for template-generated actions.
|
|
833
|
+
*/
|
|
834
|
+
matrixParameters?: LiquidSubstitutionsStrings
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
/**
|
|
838
|
+
* An individual <b>xpm</b> action containing commands to be executed.
|
|
839
|
+
*
|
|
840
|
+
* @remarks
|
|
841
|
+
* Actions are lazily initialised, with variable substitution performed
|
|
842
|
+
* only when the action is first retrieved and initialised. This allows for
|
|
843
|
+
* efficient handling of large numbers of actions generated
|
|
844
|
+
* from templates.
|
|
845
|
+
*
|
|
846
|
+
* An action can exist in three states:
|
|
847
|
+
*
|
|
848
|
+
* <ol>
|
|
849
|
+
* <li><b>Undefined:</b> Name is known but instance not yet created.</li>
|
|
850
|
+
* <li><b>Instantiated:</b> Object exists but commands not yet evaluated.</li>
|
|
851
|
+
* <li><b>Initialised:</b> Commands fully evaluated with Liquid
|
|
852
|
+
* substitutions.</li>
|
|
853
|
+
* </ol>
|
|
854
|
+
*
|
|
855
|
+
* This design minimizes memory usage and computation for actions that are
|
|
856
|
+
* defined but never executed, which is common when using matrix templates
|
|
857
|
+
* to generate platform-specific or configuration-specific actions.
|
|
858
|
+
*/
|
|
859
|
+
export class Action {
|
|
860
|
+
// --------------------------------------------------------------------------
|
|
861
|
+
// Public Members.
|
|
862
|
+
|
|
863
|
+
/**
|
|
864
|
+
* The name of the action.
|
|
865
|
+
*
|
|
866
|
+
* @remarks
|
|
867
|
+
* This is the final, expanded action name used for identification and
|
|
868
|
+
* execution. For template-generated actions, this is the concrete name
|
|
869
|
+
* after matrix substitution (e.g., `test-x64` rather than
|
|
870
|
+
* `test-{{ matrix.arch }}`).
|
|
871
|
+
*
|
|
872
|
+
* The name is used for:
|
|
873
|
+
*
|
|
874
|
+
* <ol>
|
|
875
|
+
* <li>User-facing identification when listing or executing actions.</li>
|
|
876
|
+
* <li>Logging and diagnostic output to track action lifecycle.</li>
|
|
877
|
+
* <li>Creating copies of inherited actions with preserved names.</li>
|
|
878
|
+
* </ol>
|
|
879
|
+
*
|
|
880
|
+
* Names must be unique within the actions collection, enforced during
|
|
881
|
+
* {@link Actions.initialise}.
|
|
882
|
+
*/
|
|
883
|
+
readonly name: string
|
|
884
|
+
|
|
885
|
+
/**
|
|
886
|
+
* The JSON definition of the action commands.
|
|
887
|
+
*
|
|
888
|
+
* @remarks
|
|
889
|
+
* This holds the raw command definition as it appears in `package.json`,
|
|
890
|
+
* before variable substitution. The format can be:
|
|
891
|
+
*
|
|
892
|
+
* <ol>
|
|
893
|
+
* <li><b>Simple string:</b> Single command line.</li>
|
|
894
|
+
* <li><b>String array:</b> Multiple commands for sequential execution.</li>
|
|
895
|
+
* </ol>
|
|
896
|
+
*
|
|
897
|
+
* The definition is preserved in its original form to enable:
|
|
898
|
+
*
|
|
899
|
+
* <ol>
|
|
900
|
+
* <li>Creating copies of inherited actions with identical definitions.</li>
|
|
901
|
+
* <li>Deferred template evaluation during
|
|
902
|
+
* <code>Action.initialise()</code>.</li>
|
|
903
|
+
* <li>Re-evaluation if needed with different variable contexts.</li>
|
|
904
|
+
* </ol>
|
|
905
|
+
*
|
|
906
|
+
* This immutable storage ensures actions can be safely copied and
|
|
907
|
+
* initialised multiple times without side effects.
|
|
908
|
+
*/
|
|
909
|
+
readonly jsonAction: JsonActionContent
|
|
910
|
+
|
|
911
|
+
/**
|
|
912
|
+
* The parent actions collection this action belongs to.
|
|
913
|
+
*
|
|
914
|
+
* @remarks
|
|
915
|
+
* This reference maintains the hierarchical relationship between individual
|
|
916
|
+
* actions and their containing collection, providing essential context for
|
|
917
|
+
* action initialisation and execution.
|
|
918
|
+
*
|
|
919
|
+
* The parent collection provides access to:
|
|
920
|
+
*
|
|
921
|
+
* <ol>
|
|
922
|
+
* <li>Liquid templating engine for variable substitution.</li>
|
|
923
|
+
* <li>Substitution variables hierarchy (package metadata, configuration,
|
|
924
|
+
* environment, platform detection).</li>
|
|
925
|
+
* <li>Logger instance for diagnostic output.</li>
|
|
926
|
+
* <li>Build configuration context when actions belong to a specific
|
|
927
|
+
* configuration rather than the package root.</li>
|
|
928
|
+
* </ol>
|
|
929
|
+
*
|
|
930
|
+
* This design enables actions to access shared resources without duplicating
|
|
931
|
+
* them, while maintaining proper scoping for template evaluation. During
|
|
932
|
+
* initialisation, the action combines parent-level substitution variables
|
|
933
|
+
* with its own matrix parameters to create a complete context for Liquid
|
|
934
|
+
* template processing.
|
|
935
|
+
*/
|
|
936
|
+
readonly parentActions: Actions
|
|
937
|
+
|
|
938
|
+
/**
|
|
939
|
+
* The matrix parameter values for template-generated actions.
|
|
940
|
+
*
|
|
941
|
+
* @remarks
|
|
942
|
+
* For template-generated actions, this object contains the specific matrix
|
|
943
|
+
* parameter values that produced this action instance from the template.
|
|
944
|
+
*
|
|
945
|
+
* Usage pattern:
|
|
946
|
+
*
|
|
947
|
+
* <ol>
|
|
948
|
+
* <li>Undefined for regular (non-template) actions.</li>
|
|
949
|
+
* <li>For template actions, contains key-value pairs from the matrix
|
|
950
|
+
* combination (e.g.,
|
|
951
|
+
* <code>\{ arch: 'x64', platform: 'linux' \}</code>).</li>
|
|
952
|
+
* <li>Merged into substitution variables during
|
|
953
|
+
* <code>Action.initialise()</code>, making values accessible via the
|
|
954
|
+
* <code>matrix</code> namespace in command templates.</li>
|
|
955
|
+
* <li>Enables the same command template to generate different concrete
|
|
956
|
+
* commands for each matrix combination.</li>
|
|
957
|
+
* </ol>
|
|
958
|
+
*
|
|
959
|
+
* Example: A template with `{{ matrix.arch }}` becomes `x64` when this
|
|
960
|
+
* action's matrix parameters include `{ arch: 'x64' }`.
|
|
961
|
+
*/
|
|
962
|
+
protected readonly _matrixParameters?: LiquidSubstitutionsStrings
|
|
963
|
+
|
|
964
|
+
/**
|
|
965
|
+
* The array of command strings after variable substitution.
|
|
966
|
+
*
|
|
967
|
+
* @remarks
|
|
968
|
+
* This array contains the fully evaluated command lines ready for
|
|
969
|
+
* execution, with all Liquid template variables substituted.
|
|
970
|
+
*
|
|
971
|
+
* Lifecycle states:
|
|
972
|
+
*
|
|
973
|
+
* <ol>
|
|
974
|
+
* <li>Undefined initially and until <code>Action.initialise()</code>
|
|
975
|
+
* is called.</li>
|
|
976
|
+
* <li>Populated during initialisation by evaluating
|
|
977
|
+
* <code>jsonAction</code> with the
|
|
978
|
+
* Liquid engine and complete variable context.</li>
|
|
979
|
+
* <li>Array-based JSON definitions are joined, substituted, then split back
|
|
980
|
+
* into individual command lines.</li>
|
|
981
|
+
* <li>Each string represents one command line to be executed
|
|
982
|
+
* sequentially.</li>
|
|
983
|
+
* </ol>
|
|
984
|
+
*
|
|
985
|
+
* Attempting to access via the `commands` getter before initialisation
|
|
986
|
+
* will trigger an assertion error, enforcing correct usage order.
|
|
987
|
+
*/
|
|
988
|
+
protected _commands?: string[]
|
|
989
|
+
|
|
990
|
+
/**
|
|
991
|
+
* Flag indicating whether the action has been initialised.
|
|
992
|
+
*
|
|
993
|
+
* @remarks
|
|
994
|
+
* This flag ensures idempotent initialization and prevents redundant
|
|
995
|
+
* template evaluation when {@link Action.initialise} is called
|
|
996
|
+
* multiple times.
|
|
997
|
+
*
|
|
998
|
+
* State transitions:
|
|
999
|
+
*
|
|
1000
|
+
* <ol>
|
|
1001
|
+
* <li>Initially <code>false</code> after construction.</li>
|
|
1002
|
+
* <li>Set to <code>true</code> after successful command substitution and
|
|
1003
|
+
* evaluation.</li>
|
|
1004
|
+
* <li>Checked at the start of <code>Action.initialise()</code> to
|
|
1005
|
+
* return early if already initialised.</li>
|
|
1006
|
+
* </ol>
|
|
1007
|
+
*
|
|
1008
|
+
* This pattern allows safe repeated calls during complex initialization
|
|
1009
|
+
* sequences or when actions are accessed multiple times, avoiding the
|
|
1010
|
+
* computational cost of re-evaluating templates unnecessarily.
|
|
1011
|
+
*/
|
|
1012
|
+
protected _isInitialised = false
|
|
1013
|
+
|
|
1014
|
+
// --------------------------------------------------------------------------
|
|
1015
|
+
// Constructor and async initialiser.
|
|
1016
|
+
|
|
1017
|
+
/**
|
|
1018
|
+
* Constructs an action instance.
|
|
1019
|
+
*
|
|
1020
|
+
* @remarks
|
|
1021
|
+
* The constructor performs partial initialisation. Variable substitution
|
|
1022
|
+
* requires calling the {@link Action.initialise} method.
|
|
1023
|
+
*
|
|
1024
|
+
* @param actionName - The name of the action.
|
|
1025
|
+
* @param jsonAction - The JSON definition of the action commands.
|
|
1026
|
+
* @param parentActions - The parent actions collection this action belongs
|
|
1027
|
+
* to.
|
|
1028
|
+
* @param matrixParameters - Optional matrix parameter values for
|
|
1029
|
+
* template-generated actions.
|
|
1030
|
+
*/
|
|
1031
|
+
constructor({
|
|
1032
|
+
actionName,
|
|
1033
|
+
jsonAction,
|
|
1034
|
+
parentActions,
|
|
1035
|
+
matrixParameters,
|
|
1036
|
+
}: ActionConstructorParameters) {
|
|
1037
|
+
assert(actionName, 'actionName is required')
|
|
1038
|
+
// assert(jsonAction) // Can be an empty string.
|
|
1039
|
+
assert(parentActions, 'parentActions is required')
|
|
1040
|
+
|
|
1041
|
+
const log = parentActions.log
|
|
1042
|
+
log.trace(`${Action.name}(${actionName})`)
|
|
1043
|
+
|
|
1044
|
+
this.name = actionName
|
|
1045
|
+
this.jsonAction = jsonAction
|
|
1046
|
+
this.parentActions = parentActions
|
|
1047
|
+
if (matrixParameters !== undefined) {
|
|
1048
|
+
this._matrixParameters = matrixParameters
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
/**
|
|
1053
|
+
* Completes the async initialisation of the action.
|
|
1054
|
+
*
|
|
1055
|
+
* @remarks
|
|
1056
|
+
* This method performs variable substitution on the action commands using
|
|
1057
|
+
* the Liquid templating engine and the available substitution variables,
|
|
1058
|
+
* including any matrix parameters for template-generated actions.
|
|
1059
|
+
*
|
|
1060
|
+
* The substitution context includes:
|
|
1061
|
+
*
|
|
1062
|
+
* <ol>
|
|
1063
|
+
* <li>All package-level substitution variables (configuration, package
|
|
1064
|
+
* metadata, platform detection, etc.).</li>
|
|
1065
|
+
* <li>Build configuration variables if this action belongs to a
|
|
1066
|
+
* configuration.</li>
|
|
1067
|
+
* <li>Matrix parameters for template-generated actions, accessible via
|
|
1068
|
+
* the <code>matrix</code> namespace (e.g.,
|
|
1069
|
+
* <code>\{\{ matrix.arch \}\}</code>).</li>
|
|
1070
|
+
* </ol>
|
|
1071
|
+
*
|
|
1072
|
+
* Array-based command definitions are joined with newlines before
|
|
1073
|
+
* substitution, then split back into individual commands after processing.
|
|
1074
|
+
* This allows commands to span multiple array elements while maintaining
|
|
1075
|
+
* clean formatting in the package manifest.
|
|
1076
|
+
*
|
|
1077
|
+
* @returns A promise that resolves to `true` if initialisation was
|
|
1078
|
+
* performed, or `false` if already initialised.
|
|
1079
|
+
*
|
|
1080
|
+
* @throws {@link ConfigurationError}
|
|
1081
|
+
* If command substitution fails.
|
|
1082
|
+
*/
|
|
1083
|
+
async initialise(): Promise<boolean> {
|
|
1084
|
+
const log = this.parentActions.log
|
|
1085
|
+
|
|
1086
|
+
if (this._isInitialised) {
|
|
1087
|
+
log.trace(`${Action.name}.initialise(${this.name}) again`)
|
|
1088
|
+
|
|
1089
|
+
return false
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
log.trace(`${Action.name}.initialise(${this.name})`)
|
|
1093
|
+
|
|
1094
|
+
// Silently accept empty or non-existing actions.
|
|
1095
|
+
const jsonAction = this.jsonAction
|
|
1096
|
+
const inputCommands = Array.isArray(jsonAction)
|
|
1097
|
+
? jsonAction.join(os.EOL)
|
|
1098
|
+
: jsonAction
|
|
1099
|
+
|
|
1100
|
+
let substitutedCommands
|
|
1101
|
+
if (hasLiquidSyntax(inputCommands)) {
|
|
1102
|
+
try {
|
|
1103
|
+
substitutedCommands = await performSubstitutions({
|
|
1104
|
+
input: inputCommands,
|
|
1105
|
+
engine: this.parentActions.engine,
|
|
1106
|
+
substitutionsVariables: {
|
|
1107
|
+
...this.parentActions.substitutionsVariables,
|
|
1108
|
+
matrix: this._matrixParameters ?? {},
|
|
1109
|
+
},
|
|
1110
|
+
log,
|
|
1111
|
+
})
|
|
1112
|
+
} catch (error) {
|
|
1113
|
+
const message =
|
|
1114
|
+
getErrorMessage(error) +
|
|
1115
|
+
` in action "${this.name}" commands substitution`
|
|
1116
|
+
throw new ConfigurationError(message)
|
|
1117
|
+
}
|
|
1118
|
+
} else {
|
|
1119
|
+
substitutedCommands = inputCommands
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
this._commands = substitutedCommands
|
|
1123
|
+
.replace(new RegExp(os.EOL + '$'), '')
|
|
1124
|
+
.split(os.EOL)
|
|
1125
|
+
|
|
1126
|
+
log.trace(`${Action.name}.initialise() =>`, this.name)
|
|
1127
|
+
log.trace(this.name, 'commands =>', this._commands)
|
|
1128
|
+
|
|
1129
|
+
this._isInitialised = true
|
|
1130
|
+
return true
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
// --------------------------------------------------------------------------
|
|
1134
|
+
// Public Methods.
|
|
1135
|
+
|
|
1136
|
+
/**
|
|
1137
|
+
* Retrieves the array of command strings for this action.
|
|
1138
|
+
*
|
|
1139
|
+
* @remarks
|
|
1140
|
+
* The action must be initialised via {@link Action.initialise}
|
|
1141
|
+
* before accessing this property. Attempting to access commands from an
|
|
1142
|
+
* uninitialised action will result in an assertion error.
|
|
1143
|
+
*
|
|
1144
|
+
* @returns The array of command strings after variable substitution.
|
|
1145
|
+
*/
|
|
1146
|
+
get commands(): string[] {
|
|
1147
|
+
assert(
|
|
1148
|
+
this._isInitialised,
|
|
1149
|
+
'Action must be initialised before accessing commands'
|
|
1150
|
+
)
|
|
1151
|
+
|
|
1152
|
+
assert(this._commands, 'Action _commands not initialised')
|
|
1153
|
+
return this._commands
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
// ----------------------------------------------------------------------------
|