@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.
Files changed (202) hide show
  1. package/README.md +16 -212
  2. package/dist/classes/actions.d.ts +58 -0
  3. package/dist/classes/actions.d.ts.map +1 -0
  4. package/dist/classes/actions.js +250 -0
  5. package/dist/classes/actions.js.map +1 -0
  6. package/dist/classes/build-configurations.d.ts +78 -0
  7. package/dist/classes/build-configurations.d.ts.map +1 -0
  8. package/dist/classes/build-configurations.js +489 -0
  9. package/dist/classes/build-configurations.js.map +1 -0
  10. package/dist/classes/combinations-generator.d.ts +19 -0
  11. package/dist/classes/combinations-generator.d.ts.map +1 -0
  12. package/dist/classes/combinations-generator.js +48 -0
  13. package/dist/classes/combinations-generator.js.map +1 -0
  14. package/dist/classes/data-model.d.ts +21 -0
  15. package/dist/classes/data-model.d.ts.map +1 -0
  16. package/dist/classes/data-model.js +47 -0
  17. package/dist/classes/data-model.js.map +1 -0
  18. package/dist/classes/errors.d.ts +13 -0
  19. package/dist/classes/errors.d.ts.map +1 -0
  20. package/dist/classes/errors.js +13 -0
  21. package/dist/classes/errors.js.map +1 -0
  22. package/dist/classes/init-template-base.d.ts +47 -0
  23. package/dist/classes/init-template-base.d.ts.map +1 -0
  24. package/dist/classes/init-template-base.js +358 -0
  25. package/dist/classes/init-template-base.js.map +1 -0
  26. package/dist/classes/liquid-drop.d.ts +28 -0
  27. package/dist/classes/liquid-drop.d.ts.map +1 -0
  28. package/dist/classes/liquid-drop.js +70 -0
  29. package/dist/classes/liquid-drop.js.map +1 -0
  30. package/dist/classes/liquid-engine.d.ts +7 -0
  31. package/dist/classes/liquid-engine.d.ts.map +1 -0
  32. package/dist/classes/liquid-engine.js +72 -0
  33. package/dist/classes/liquid-engine.js.map +1 -0
  34. package/dist/classes/package.d.ts +31 -0
  35. package/dist/classes/package.d.ts.map +1 -0
  36. package/dist/classes/package.js +268 -0
  37. package/dist/classes/package.js.map +1 -0
  38. package/dist/classes/platform-detector.d.ts +14 -0
  39. package/dist/classes/platform-detector.d.ts.map +1 -0
  40. package/dist/classes/platform-detector.js +26 -0
  41. package/dist/classes/platform-detector.js.map +1 -0
  42. package/dist/classes/policies.d.ts +14 -0
  43. package/dist/classes/policies.d.ts.map +1 -0
  44. package/dist/classes/policies.js +20 -0
  45. package/dist/classes/policies.js.map +1 -0
  46. package/dist/classes/template-expander.d.ts +29 -0
  47. package/dist/classes/template-expander.d.ts.map +1 -0
  48. package/dist/classes/template-expander.js +62 -0
  49. package/dist/classes/template-expander.js.map +1 -0
  50. package/dist/data/substitutions-variables.d.ts +43 -0
  51. package/dist/data/substitutions-variables.d.ts.map +1 -0
  52. package/dist/{lib → data}/substitutions-variables.js +1 -16
  53. package/dist/data/substitutions-variables.js.map +1 -0
  54. package/dist/functions/chmod-recursively.d.ts +9 -0
  55. package/dist/functions/chmod-recursively.d.ts.map +1 -0
  56. package/dist/functions/chmod-recursively.js +66 -0
  57. package/dist/functions/chmod-recursively.js.map +1 -0
  58. package/dist/functions/filter-paths.d.ts +5 -0
  59. package/dist/functions/filter-paths.d.ts.map +1 -0
  60. package/dist/functions/filter-paths.js +16 -0
  61. package/dist/functions/filter-paths.js.map +1 -0
  62. package/dist/functions/is-something.d.ts +9 -0
  63. package/dist/functions/is-something.d.ts.map +1 -0
  64. package/dist/functions/is-something.js +25 -0
  65. package/dist/functions/is-something.js.map +1 -0
  66. package/dist/functions/matrix-expander.d.ts +17 -0
  67. package/dist/functions/matrix-expander.d.ts.map +1 -0
  68. package/dist/functions/matrix-expander.js +52 -0
  69. package/dist/functions/matrix-expander.js.map +1 -0
  70. package/dist/functions/perform-substitutions.d.ts +12 -0
  71. package/dist/functions/perform-substitutions.d.ts.map +1 -0
  72. package/dist/functions/perform-substitutions.js +76 -0
  73. package/dist/functions/perform-substitutions.js.map +1 -0
  74. package/dist/functions/utils.d.ts +8 -0
  75. package/dist/functions/utils.d.ts.map +1 -0
  76. package/dist/functions/utils.js +16 -0
  77. package/dist/functions/utils.js.map +1 -0
  78. package/dist/index.d.ts +22 -15
  79. package/dist/index.d.ts.map +1 -1
  80. package/dist/index.js +22 -29
  81. package/dist/index.js.map +1 -1
  82. package/dist/{lib/types.d.ts → types/json.d.ts} +31 -22
  83. package/dist/types/json.d.ts.map +1 -0
  84. package/dist/types/json.js +2 -0
  85. package/dist/types/json.js.map +1 -0
  86. package/dist/types/xpm-init-template.d.ts +21 -0
  87. package/dist/types/xpm-init-template.d.ts.map +1 -0
  88. package/dist/types/xpm-init-template.js +2 -0
  89. package/dist/types/xpm-init-template.js.map +1 -0
  90. package/dist/types/xpm.d.ts +16 -0
  91. package/dist/types/xpm.d.ts.map +1 -0
  92. package/dist/types/xpm.js +2 -0
  93. package/dist/types/xpm.js.map +1 -0
  94. package/package.json +53 -44
  95. package/src/CODE-REVIEW.md +2167 -0
  96. package/src/README.md +393 -6
  97. package/src/classes/actions.ts +1157 -0
  98. package/src/classes/build-configurations.ts +2127 -0
  99. package/src/classes/combinations-generator.ts +331 -0
  100. package/src/classes/data-model.ts +337 -0
  101. package/src/classes/errors.ts +105 -0
  102. package/src/classes/init-template-base.ts +1028 -0
  103. package/src/classes/liquid-drop.ts +376 -0
  104. package/src/classes/liquid-engine.ts +249 -0
  105. package/src/classes/package.ts +765 -0
  106. package/src/classes/platform-detector.ts +237 -0
  107. package/src/classes/policies.ts +200 -0
  108. package/src/classes/template-expander.ts +330 -0
  109. package/src/data/substitutions-variables.ts +390 -0
  110. package/src/functions/chmod-recursively.ts +195 -0
  111. package/src/functions/filter-paths.ts +126 -0
  112. package/src/functions/is-something.ts +223 -0
  113. package/src/functions/matrix-expander.ts +172 -0
  114. package/src/functions/perform-substitutions.ts +253 -0
  115. package/src/functions/utils.ts +151 -0
  116. package/src/index.ts +72 -19
  117. package/src/types/json.ts +519 -0
  118. package/src/types/xpm-init-template.ts +282 -0
  119. package/src/types/xpm.ts +162 -0
  120. package/dist/lib/chmod-recursive.d.ts +0 -7
  121. package/dist/lib/chmod-recursive.d.ts.map +0 -1
  122. package/dist/lib/chmod-recursive.js +0 -81
  123. package/dist/lib/chmod-recursive.js.map +0 -1
  124. package/dist/lib/errors.d.ts +0 -11
  125. package/dist/lib/errors.d.ts.map +0 -1
  126. package/dist/lib/errors.js +0 -26
  127. package/dist/lib/errors.js.map +0 -1
  128. package/dist/lib/functions/chmod-recursive.d.ts +0 -7
  129. package/dist/lib/functions/chmod-recursive.d.ts.map +0 -1
  130. package/dist/lib/functions/chmod-recursive.js +0 -81
  131. package/dist/lib/functions/chmod-recursive.js.map +0 -1
  132. package/dist/lib/functions/perform-substitutions.d.ts +0 -20
  133. package/dist/lib/functions/perform-substitutions.d.ts.map +0 -1
  134. package/dist/lib/functions/perform-substitutions.js +0 -85
  135. package/dist/lib/functions/perform-substitutions.js.map +0 -1
  136. package/dist/lib/functions/utils.d.ts +0 -30
  137. package/dist/lib/functions/utils.d.ts.map +0 -1
  138. package/dist/lib/functions/utils.js +0 -70
  139. package/dist/lib/functions/utils.js.map +0 -1
  140. package/dist/lib/init-template-base.d.ts +0 -46
  141. package/dist/lib/init-template-base.d.ts.map +0 -1
  142. package/dist/lib/init-template-base.js +0 -281
  143. package/dist/lib/init-template-base.js.map +0 -1
  144. package/dist/lib/liquid-actions.d.ts +0 -37
  145. package/dist/lib/liquid-actions.d.ts.map +0 -1
  146. package/dist/lib/liquid-actions.js +0 -148
  147. package/dist/lib/liquid-actions.js.map +0 -1
  148. package/dist/lib/liquid-build-configurations.d.ts +0 -47
  149. package/dist/lib/liquid-build-configurations.d.ts.map +0 -1
  150. package/dist/lib/liquid-build-configurations.js +0 -282
  151. package/dist/lib/liquid-build-configurations.js.map +0 -1
  152. package/dist/lib/liquid-drop.d.ts +0 -13
  153. package/dist/lib/liquid-drop.d.ts.map +0 -1
  154. package/dist/lib/liquid-drop.js +0 -56
  155. package/dist/lib/liquid-drop.js.map +0 -1
  156. package/dist/lib/liquid-engine.d.ts +0 -5
  157. package/dist/lib/liquid-engine.d.ts.map +0 -1
  158. package/dist/lib/liquid-engine.js +0 -85
  159. package/dist/lib/liquid-engine.js.map +0 -1
  160. package/dist/lib/liquid-package.d.ts +0 -17
  161. package/dist/lib/liquid-package.d.ts.map +0 -1
  162. package/dist/lib/liquid-package.js +0 -70
  163. package/dist/lib/liquid-package.js.map +0 -1
  164. package/dist/lib/package.d.ts +0 -66
  165. package/dist/lib/package.d.ts.map +0 -1
  166. package/dist/lib/package.js +0 -700
  167. package/dist/lib/package.js.map +0 -1
  168. package/dist/lib/perform-substitutions.d.ts +0 -20
  169. package/dist/lib/perform-substitutions.d.ts.map +0 -1
  170. package/dist/lib/perform-substitutions.js +0 -85
  171. package/dist/lib/perform-substitutions.js.map +0 -1
  172. package/dist/lib/policies.d.ts +0 -14
  173. package/dist/lib/policies.d.ts.map +0 -1
  174. package/dist/lib/policies.js +0 -33
  175. package/dist/lib/policies.js.map +0 -1
  176. package/dist/lib/substitutions-variables.d.ts +0 -117
  177. package/dist/lib/substitutions-variables.d.ts.map +0 -1
  178. package/dist/lib/substitutions-variables.js.map +0 -1
  179. package/dist/lib/types.d.ts.map +0 -1
  180. package/dist/lib/types.js +0 -13
  181. package/dist/lib/types.js.map +0 -1
  182. package/dist/lib/utils.d.ts +0 -30
  183. package/dist/lib/utils.d.ts.map +0 -1
  184. package/dist/lib/utils.js +0 -70
  185. package/dist/lib/utils.js.map +0 -1
  186. package/dist/tsconfig.tsbuildinfo +0 -1
  187. package/src/lib/errors.ts +0 -29
  188. package/src/lib/functions/chmod-recursive.ts +0 -103
  189. package/src/lib/functions/perform-substitutions.ts +0 -116
  190. package/src/lib/functions/utils.ts +0 -88
  191. package/src/lib/init-template-base.ts +0 -408
  192. package/src/lib/liquid-actions.ts +0 -223
  193. package/src/lib/liquid-build-configurations.ts +0 -433
  194. package/src/lib/liquid-drop.ts +0 -99
  195. package/src/lib/liquid-engine.ts +0 -135
  196. package/src/lib/liquid-package.ts +0 -108
  197. package/src/lib/package.ts +0 -947
  198. package/src/lib/policies.ts +0 -51
  199. package/src/lib/substitutions-variables.ts +0 -177
  200. package/src/lib/types.ts +0 -109
  201. package/src/package.json +0 -3
  202. package/src/tsconfig.json +0 -10
@@ -0,0 +1,331 @@
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 { Logger } from '@xpack/logger'
15
+ import { ConfigurationError } from './errors.js'
16
+
17
+ // ============================================================================
18
+
19
+ /**
20
+ * The default maximum number of combinations that can be generated.
21
+ *
22
+ * @remarks
23
+ * This constant defines the default limit for the total number of
24
+ * combinations that a {@link CombinationsGenerator} instance can produce.
25
+ * The limit exists to prevent performance issues and memory exhaustion when
26
+ * working with large matrices.
27
+ *
28
+ * This value can be overridden by providing a custom `maxCombinations`
29
+ * parameter when constructing a {@link CombinationsGenerator} instance.
30
+ *
31
+ * The limit is intentionally low (42 * 10).
32
+ */
33
+ export const COMBINATIONS_GENERATOR_MAX_COMBINATIONS_LIMIT = 42 * 10
34
+
35
+ /**
36
+ * A matrix combination mapping parameter names to their values.
37
+ *
38
+ * @remarks
39
+ * This type represents a single combination generated from a matrix of
40
+ * parameters, where each key is a parameter name and each value is a
41
+ * string from that parameter's array of possible values.
42
+ *
43
+ * Example: `{ arch: 'x64', platform: 'linux', optimize: 'speed' }`
44
+ */
45
+ export type MatrixCombination = Record<string, string>
46
+
47
+ // ============================================================================
48
+
49
+ /**
50
+ * Configuration parameters for constructing a combinations generator instance.
51
+ *
52
+ * @remarks
53
+ * This interface defines the required configuration for creating an
54
+ * instance of {@link CombinationsGenerator}. All properties are mandatory.
55
+ *
56
+ * The parameters provide the matrix parameter names, their corresponding
57
+ * value arrays for Cartesian product computation, and the logger for
58
+ * diagnostic output during combination generation.
59
+ */
60
+ export interface CombinationsGeneratorConstructorParameters {
61
+ /**
62
+ * The array of parameter names.
63
+ */
64
+ matrixKeys: string[]
65
+
66
+ /**
67
+ * The array of value arrays for each parameter.
68
+ */
69
+ matrixValues: string[][]
70
+
71
+ /**
72
+ * The logger instance for output and diagnostics.
73
+ */
74
+ log: Logger
75
+
76
+ /**
77
+ * Optional maximum combinations limit to prevent excessive generation.
78
+ *
79
+ * @remarks
80
+ * This parameter allows configuring a custom limit on the total number of
81
+ * combinations that can be generated. If the calculated total exceeds this
82
+ * limit, a {@link ConfigurationError} is thrown to prevent performance
83
+ * issues or memory exhaustion. The default value is 10,000 combinations.
84
+ */
85
+ maxCombinations?: number
86
+ }
87
+
88
+ /**
89
+ * Generates all possible combinations from a matrix of parameters.
90
+ *
91
+ * @remarks
92
+ * This class computes the Cartesian product of multiple parameter arrays,
93
+ * producing all possible combinations of parameter values using a
94
+ * memory-efficient generator pattern. It uses a recursive algorithm to
95
+ * systematically explore all combinations one at a time.
96
+ *
97
+ * The generation process:
98
+ *
99
+ * <ol>
100
+ * <li>Takes arrays of parameter names (keys) and their corresponding value
101
+ * arrays.</li>
102
+ * <li>Recursively iterates through each parameter, selecting one value at a
103
+ * time.</li>
104
+ * <li>Yields complete combinations one at a time without storing them all
105
+ * in memory.</li>
106
+ * <li>Backtracks to explore other value combinations.</li>
107
+ * </ol>
108
+ *
109
+ * Example usage:
110
+ * ```typescript
111
+ * const generator = new CombinationsGenerator({
112
+ * matrixKeys: ['arch', 'optimize'],
113
+ * matrixValues: [['x64', 'arm'], ['speed', 'size']],
114
+ * log
115
+ * });
116
+ * for (const combo of generator.generate()) {
117
+ * // Process one combination at a time without storing all in memory
118
+ * // Results:
119
+ * // { arch: 'x64', optimize: 'speed' }
120
+ * // { arch: 'x64', optimize: 'size' }
121
+ * // { arch: 'arm', optimize: 'speed' }
122
+ * // { arch: 'arm', optimize: 'size' }
123
+ * await processConfiguration(combo);
124
+ * }
125
+ * ```
126
+ */
127
+ export class CombinationsGenerator {
128
+ // --------------------------------------------------------------------------
129
+ // Protected Members.
130
+
131
+ /**
132
+ * The logger instance for output and diagnostics.
133
+ *
134
+ * @remarks
135
+ * This logger provides trace-level diagnostics during combination
136
+ * generation, enabling visibility into the recursive exploration of the
137
+ * parameter space without impacting performance when tracing is disabled.
138
+ */
139
+ protected readonly _log: Logger
140
+
141
+ /**
142
+ * The array of parameter names.
143
+ *
144
+ * @remarks
145
+ * This array contains the names of all matrix parameters in the order
146
+ * they should be processed during combination generation. Each key
147
+ * corresponds to a parameter that will appear in the generated
148
+ * combinations.
149
+ */
150
+ protected readonly _matrixKeys: string[]
151
+
152
+ /**
153
+ * The array of value arrays for each parameter.
154
+ *
155
+ * @remarks
156
+ * This two-dimensional array contains the possible values for each
157
+ * parameter. The outer array corresponds one-to-one with
158
+ * <code>matrixKeys</code>, where <code>matrixValues[i]</code> contains
159
+ * all possible values for the parameter <code>matrixKeys[i]</code>.
160
+ *
161
+ * The Cartesian product of all these value arrays produces the complete
162
+ * set of combinations.
163
+ */
164
+ protected readonly _matrixValues: string[][]
165
+
166
+ /**
167
+ * The maximum number of combinations allowed.
168
+ *
169
+ * @remarks
170
+ * This limit prevents excessive generation of combinations, protecting
171
+ * against performance issues and memory exhaustion.
172
+ */
173
+ protected readonly _maxCombinations: number
174
+
175
+ // --------------------------------------------------------------------------
176
+ // Constructor.
177
+
178
+ /**
179
+ * Constructs a combinations generator instance.
180
+ *
181
+ * @param matrixKeys - The array of parameter names.
182
+ * @param matrixValues - The array of value arrays for each parameter.
183
+ * @param log - The logger instance for output and diagnostics.
184
+ *
185
+ * @remarks
186
+ * The constructor validates that the structure of keys and values is
187
+ * correct and prepares the generator for combination generation. The
188
+ * actual generation is performed by calling `*generate`.
189
+ */
190
+ constructor({
191
+ matrixKeys,
192
+ matrixValues,
193
+ maxCombinations = COMBINATIONS_GENERATOR_MAX_COMBINATIONS_LIMIT,
194
+ log,
195
+ }: CombinationsGeneratorConstructorParameters) {
196
+ this._log = log
197
+ this._matrixKeys = matrixKeys
198
+ this._matrixValues = matrixValues
199
+ this._maxCombinations = maxCombinations
200
+
201
+ log.trace(
202
+ `${CombinationsGenerator.name}.constructor: ` +
203
+ `matrixKeys=${JSON.stringify(this._matrixKeys)} ` +
204
+ `matrixValues=${JSON.stringify(this._matrixValues)}`
205
+ )
206
+ }
207
+
208
+ // --------------------------------------------------------------------------
209
+ // Public Methods.
210
+
211
+ /**
212
+ * Generates combinations one at a time using a generator pattern.
213
+ *
214
+ * @remarks
215
+ * This method yields combinations one at a time instead of building the
216
+ * entire array in memory. This is particularly useful for large matrices
217
+ * where storing all combinations would be impractical.
218
+ *
219
+ * Memory efficiency:
220
+ *
221
+ * <ul>
222
+ * <li>For a matrix with 10 parameters and 5 values each (9,765,625
223
+ * combinations), only one combination object exists in memory at a
224
+ * time, regardless of matrix size.</li>
225
+ * <li>No array allocation or storage required.</li>
226
+ * <li>Immediate processing of each combination without waiting for all to
227
+ * be generated.</li>
228
+ * </ul>
229
+ *
230
+ * Example usage:
231
+ * ```typescript
232
+ * for (const combo of generator.generate()) {
233
+ * // Process one combination at a time
234
+ * await processConfiguration(combo)
235
+ * }
236
+ * ```
237
+ *
238
+ * <b>Yields:</b> Individual matrix combinations one at a time.
239
+ */
240
+ *generate(): Generator<MatrixCombination> {
241
+ // Empty matrix produces no combinations
242
+ if (this._matrixKeys.length === 0) {
243
+ return
244
+ }
245
+
246
+ // Calculate total combinations
247
+ const totalCombinations = this._matrixValues.reduce(
248
+ (product, values) => product * values.length,
249
+ 1
250
+ )
251
+
252
+ if (totalCombinations > this._maxCombinations) {
253
+ throw new ConfigurationError(
254
+ `Matrix would generate ${String(totalCombinations)} combinations, ` +
255
+ `exceeding limit of ${String(this._maxCombinations)}. ` +
256
+ `Consider using fewer parameters or values.`
257
+ )
258
+ }
259
+
260
+ yield* this._generateRecursively(0, {})
261
+ }
262
+
263
+ // --------------------------------------------------------------------------
264
+ // Private Methods.
265
+
266
+ /**
267
+ * Recursively generates combinations as a generator, yielding one at
268
+ * a time.
269
+ *
270
+ * @remarks
271
+ * This method implements the recursive algorithm for generating the
272
+ * Cartesian product of parameter values using the generator pattern.
273
+ *
274
+ * Algorithm steps:
275
+ *
276
+ * <ol>
277
+ * <li><b>Base case:</b> If all parameters have been assigned values
278
+ * (<code>index === matrixKeys.length</code>), yield a copy of the
279
+ * current combination and return.</li>
280
+ * <li><b>Recursive case:</b> For the parameter at the current index:
281
+ * <ul>
282
+ * <li>Iterate through all possible values for this parameter.</li>
283
+ * <li>Assign each value to the combination object.</li>
284
+ * <li>Recursively yield combinations for the next parameter using
285
+ * <code>yield*</code>.</li>
286
+ * <li>Remove the assigned value (backtrack) before trying the next
287
+ * value.</li>
288
+ * </ul>
289
+ * </li>
290
+ * </ol>
291
+ *
292
+ * The combination object is reused and modified during traversal, with
293
+ * only copies of complete combinations being yielded. This approach
294
+ * minimises memory allocation whilst maintaining correctness.
295
+ *
296
+ * @param index - The current parameter index being processed.
297
+ * @param combination - The partial combination being built.
298
+ *
299
+ * <b>Yields:</b> Complete combinations one at a time.
300
+ */
301
+ protected *_generateRecursively(
302
+ index: number,
303
+ combination: Record<string, string>
304
+ ): Generator<MatrixCombination> {
305
+ const log = this._log
306
+ log.trace(
307
+ `${CombinationsGenerator.name}.` +
308
+ `_generateRecursively(${String(index)},` +
309
+ `${JSON.stringify(combination)})`
310
+ )
311
+
312
+ if (index === this._matrixKeys.length) {
313
+ log.trace('combination complete =>', combination)
314
+ yield { ...combination }
315
+
316
+ return
317
+ }
318
+
319
+ const key = this._matrixKeys[index]
320
+ const values = this._matrixValues[index]
321
+
322
+ for (const value of values) {
323
+ combination[key] = value
324
+ yield* this._generateRecursively(index + 1, combination)
325
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
326
+ delete combination[key]
327
+ }
328
+ }
329
+ }
330
+
331
+ // ----------------------------------------------------------------------------
@@ -0,0 +1,337 @@
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
+ liquidSubstitutionsVariablesBase,
24
+ } from '../data/substitutions-variables.js'
25
+ import { isJsonObject } from '../functions/is-something.js'
26
+ import { Actions } from './actions.js'
27
+ import { BuildConfigurations } from './build-configurations.js'
28
+ import { LiquidEngine } from './liquid-engine.js'
29
+ import { JsonXpmPackage } from '../types/json.js'
30
+
31
+ // ============================================================================
32
+
33
+ /**
34
+ * The property name used for the build folder relative path.
35
+ */
36
+ export const buildFolderRelativePathPropertyName = 'buildFolderRelativePath'
37
+
38
+ // ============================================================================
39
+
40
+ /**
41
+ * Configuration parameters for constructing a data model instance.
42
+ *
43
+ * @remarks
44
+ * This interface defines the required configuration for creating an
45
+ * instance of {@link DataModel}. Both properties are mandatory.
46
+ *
47
+ * The parameters provide the parsed <code>package.json</code> content
48
+ * containing package metadata and <b>xpm</b>-specific configuration, along
49
+ * with the logger for diagnostic output during data model initialization
50
+ * and template processing.
51
+ */
52
+ export interface DataModelConstructorParameters {
53
+ /**
54
+ * The JSON package definition.
55
+ */
56
+ jsonPackage: JsonXpmPackage
57
+
58
+ /**
59
+ * The logger instance for output and diagnostics.
60
+ */
61
+ log: Logger
62
+ }
63
+
64
+ /**
65
+ * Represents a lazy-loading data model for an <b>xpm</b> package.
66
+ *
67
+ * @remarks
68
+ * This class prepares substitution variables, creates the Liquid
69
+ * engine, and exposes actions and build configurations defined in the package.
70
+ *
71
+ * The package processor serves as the top-level coordinator for all
72
+ * Liquid-based template processing in an <b>xpm</b> package. It establishes the
73
+ * foundation for variable substitution throughout the package hierarchy:
74
+ *
75
+ * <ol>
76
+ * <li>Initializes base substitution variables (platform detection, system
77
+ * information, etc.).</li>
78
+ * <li>Adds package-specific variables from <code>package.json</code>
79
+ * metadata.</li>
80
+ * <li>Merges user-defined properties from <code>xpack.properties</code>.</li>
81
+ * <li>Creates package-level actions accessible across all contexts.</li>
82
+ * <li>Creates build configurations, each inheriting the base substitution
83
+ * context and adding configuration-specific variables.</li>
84
+ * </ol>
85
+ *
86
+ * This hierarchical structure ensures that templates at any level have
87
+ * access to appropriate variables while maintaining clear scoping rules.
88
+ * Package-level actions are available globally, while configuration-level
89
+ * actions are scoped to their respective configurations.
90
+ */
91
+ export class DataModel {
92
+ // --------------------------------------------------------------------------
93
+ // Public Members.
94
+
95
+ /**
96
+ * The variables available for Liquid substitutions.
97
+ *
98
+ * @remarks
99
+ * This sealed object provides the base substitution context inherited by
100
+ * all actions and build configurations within the package.
101
+ *
102
+ * Variable hierarchy:
103
+ *
104
+ * <ol>
105
+ * <li><b>Base variables (xpmLiquidSubstitutionsVariablesBase):</b>
106
+ * <ul>
107
+ * <li><code>env</code>: Environment variables from process.env</li>
108
+ * <li><code>os</code>: Platform detection (platform, arch, endianness,
109
+ * version)</li>
110
+ * <li><code>path</code>: Path utilities (sep, delimiter, cwd)</li>
111
+ * </ul>
112
+ * </li>
113
+ * <li><b>Package metadata:</b>
114
+ * <ul>
115
+ * <li><code>package</code>: Complete <code>package.json</code> content
116
+ * (name, version,
117
+ * dependencies, etc.)</li>
118
+ * </ul>
119
+ * </li>
120
+ * <li><b>User-defined properties:</b>
121
+ * <ul>
122
+ * <li><code>properties</code>: Merged from
123
+ * <code>xpack.properties</code> if present</li>
124
+ * </ul>
125
+ * </li>
126
+ * </ol>
127
+ *
128
+ * The object is sealed after initialization to prevent accidental
129
+ * modification. Child components (actions and configurations) extend this
130
+ * context with their own scoped variables (configuration, matrix) without
131
+ * modifying the original sealed object.
132
+ */
133
+ readonly substitutionsVariables: LiquidSubstitutionsVariables
134
+
135
+ /**
136
+ * The actions collection for this package.
137
+ *
138
+ * @remarks
139
+ * This collection manages package-level actions defined in
140
+ * `xpack.actions`, which are globally accessible and not tied to specific
141
+ * build configurations.
142
+ *
143
+ * Package-level actions characteristics:
144
+ *
145
+ * <ol>
146
+ * <li>Created during construction but initially unpopulated.</li>
147
+ * <li>Populated during the collection's own initialisation when
148
+ * <code>Actions.initialise()</code> is called.</li>
149
+ * <li>Have access to package-level substitution variables but not
150
+ * configuration-specific variables.</li>
151
+ * <li>Suitable for package-wide tasks like testing, documentation
152
+ * generation, or global cleanup.</li>
153
+ * <li>Can be used alongside configuration-specific actions, which inherit
154
+ * from package-level actions.</li>
155
+ * </ol>
156
+ */
157
+ readonly actions: Actions
158
+
159
+ /**
160
+ * The build configurations collection for this package.
161
+ *
162
+ * @remarks
163
+ * This collection manages all build configurations defined in
164
+ * `xpack.buildConfigurations`, supporting inheritance, template expansion,
165
+ * and configuration-specific properties and dependencies.
166
+ *
167
+ * Build configurations characteristics:
168
+ *
169
+ * <ol>
170
+ * <li>Created during construction but initially unpopulated.</li>
171
+ * <li>Populated during the collection's own initialisation when
172
+ * <code>BuildConfigurations.initialise()</code> is called.</li>
173
+ * <li>Each configuration inherits the package-level substitution variables
174
+ * and extends them with configuration-specific context.</li>
175
+ * <li>Support complex inheritance chains where configurations can inherit
176
+ * properties, dependencies, and actions from other configurations.</li>
177
+ * <li>Can be generated from templates with matrix expansion for
178
+ * multi-platform or multi-variant builds.</li>
179
+ * <li>Each configuration maintains its own actions collection, inheriting
180
+ * package-level actions and adding configuration-specific ones.</li>
181
+ * </ol>
182
+ */
183
+ readonly buildConfigurations: BuildConfigurations
184
+
185
+ /**
186
+ * The logger instance for output and diagnostics.
187
+ *
188
+ * @remarks
189
+ * This logger provides trace-level diagnostics for the entire package
190
+ * processing hierarchy, including Liquid engine creation, variable
191
+ * initialization, action collection setup, and build configuration
192
+ * preparation. It's passed down to child components (actions and build
193
+ * configurations) to maintain consistent logging throughout the package
194
+ * lifecycle.
195
+ */
196
+ protected readonly _log: Logger
197
+
198
+ // --------------------------------------------------------------------------
199
+ // Private Members.
200
+
201
+ /**
202
+ * The Liquid engine used for substitutions.
203
+ *
204
+ * @remarks
205
+ * This LiquidEngine instance is configured with strict mode and custom
206
+ * filters for xpm-specific operations. It's shared across all actions and
207
+ * build configurations within the package, ensuring consistent template
208
+ * processing behavior.
209
+ *
210
+ * Engine characteristics:
211
+ *
212
+ * <ol>
213
+ * <li>Strict mode enabled to catch undefined variable references.</li>
214
+ * <li>Custom filters for platform detection (<code>isPlatform</code>,
215
+ * <code>isArch</code>).</li>
216
+ * <li>Custom filters for path sanitization (<code>filterPath</code>,
217
+ * <code>filterPosixPath</code>,
218
+ * <code>filterWin32Path</code>).</li>
219
+ * <li>Shared instance reduces memory overhead and ensures consistent
220
+ * template evaluation across all package components.</li>
221
+ * </ol>
222
+ */
223
+ protected readonly _engine: LiquidEngine
224
+
225
+ /**
226
+ * The JSON package definition.
227
+ *
228
+ * @remarks
229
+ * This object contains the complete `package.json` content, including both
230
+ * standard npm fields and xpm-specific extensions in the `xpack` section.
231
+ *
232
+ * Required structure:
233
+ *
234
+ * <ol>
235
+ * <li>Standard npm fields: name, version, dependencies, devDependencies.</li>
236
+ * <li>Required <code>xpack</code> section containing xpm-specific
237
+ * configuration.</li>
238
+ * <li>Optional <code>xpack.properties</code> for user-defined
239
+ * substitution variables.</li>
240
+ * <li>Optional <code>xpack.actions</code> for package-level executable
241
+ * actions.</li>
242
+ * <li>Optional <code>xpack.buildConfigurations</code> for build configuration
243
+ * definitions.</li>
244
+ * </ol>
245
+ *
246
+ * The package definition is validated during construction, requiring the
247
+ * `xpack` section to be present and be a valid JSON object.
248
+ */
249
+ protected readonly _jsonPackage: JsonXpmPackage
250
+
251
+ // --------------------------------------------------------------------------
252
+ // Constructor.
253
+
254
+ /**
255
+ * Constructs a Liquid package processor.
256
+ *
257
+ * @remarks
258
+ * The constructor initializes the Liquid engine and prepares the
259
+ * substitution variables context that will be inherited by all actions
260
+ * and build configurations.
261
+ *
262
+ * Initialization sequence:
263
+ *
264
+ * <ol>
265
+ * <li>Create <code>LiquidEngine</code> with custom filters and strict
266
+ * configuration.</li>
267
+ * <li>Validate <code>xpack</code> section exists in
268
+ * <code>package.json</code>.</li>
269
+ * <li>Initialize base substitution variables (os, platform, arch, etc.).</li>
270
+ * <li>Add package metadata to substitution context.</li>
271
+ * <li>Merge <code>xpack.properties</code> if defined, allowing user-defined
272
+ * variables.</li>
273
+ * <li>Seal substitution variables to prevent accidental modification.</li>
274
+ * <li>Create package-level actions collection (initially empty, populated
275
+ * during initialisation).</li>
276
+ * <li>Create build configurations collection (initially empty, populated
277
+ * during initialisation).</li>
278
+ * </ol>
279
+ *
280
+ * The substitution variables object is sealed to ensure immutability of
281
+ * the base context. Individual actions and configurations will extend this
282
+ * context with their own scoped variables without modifying the original.
283
+ *
284
+ * @param jsonPackage - The JSON package definition.
285
+ * @param log - The logger instance for output and diagnostics.
286
+ */
287
+ constructor({ jsonPackage, log }: DataModelConstructorParameters) {
288
+ log.trace(`${DataModel.name}()`)
289
+
290
+ this._log = log
291
+ this._engine = new LiquidEngine()
292
+
293
+ assert(
294
+ isJsonObject(jsonPackage.xpack),
295
+ 'xpack section missing in package.json'
296
+ )
297
+ this._jsonPackage = jsonPackage
298
+
299
+ // os.version() available since 12.x
300
+ assert(
301
+ typeof os.version === 'function',
302
+ 'Mandatory os.version available only since 12.x'
303
+ )
304
+
305
+ this.substitutionsVariables = {
306
+ ...liquidSubstitutionsVariablesBase,
307
+ package: jsonPackage,
308
+ }
309
+
310
+ if (isJsonObject(jsonPackage.xpack.properties)) {
311
+ this.substitutionsVariables.properties = {
312
+ ...jsonPackage.xpack.properties,
313
+ }
314
+ }
315
+
316
+ // Prevent adding/removing properties.
317
+ Object.seal(this.substitutionsVariables)
318
+
319
+ // Empty actions.
320
+ this.actions = new Actions({
321
+ log: this._log,
322
+ engine: this._engine,
323
+ substitutionsVariables: this.substitutionsVariables,
324
+ jsonActions: this._jsonPackage.xpack.actions,
325
+ })
326
+
327
+ // Empty build configurations.
328
+ this.buildConfigurations = new BuildConfigurations({
329
+ log: this._log,
330
+ engine: this._engine,
331
+ substitutionsVariables: this.substitutionsVariables,
332
+ jsonBuildConfigurations: this._jsonPackage.xpack.buildConfigurations,
333
+ })
334
+ }
335
+ }
336
+
337
+ // ----------------------------------------------------------------------------