@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,376 @@
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
+ // https://www.npmjs.com/package/liquidjs
15
+ import * as liquidjs from 'liquidjs'
16
+
17
+ // https://www.npmjs.com/package/@xpack/logger
18
+ import { Logger } from '@xpack/logger'
19
+
20
+ // ----------------------------------------------------------------------------
21
+
22
+ import { LiquidSubstitutionsStrings } from '../data/substitutions-variables.js'
23
+ import { isJsonObject } from '../functions/is-something.js'
24
+ import { TemplateError } from './errors.js'
25
+
26
+ // ============================================================================
27
+
28
+ // https://liquidjs.com/
29
+
30
+ /**
31
+ * Configuration parameters for constructing a properties drop instance.
32
+ *
33
+ * @remarks
34
+ * This interface defines the required configuration for creating an
35
+ * instance of {@link LiquidPropertiesDrop}. All properties are mandatory.
36
+ *
37
+ * The parameters provide the Liquid engine for nested template evaluation,
38
+ * the properties map for value lookups, and the logger for diagnostic
39
+ * output during property resolution and substitution.
40
+ */
41
+ export interface LiquidPropertiesDropConstructorParameters {
42
+ /**
43
+ * The Liquid engine used to render nested substitutions.
44
+ */
45
+ engine: liquidjs.Liquid
46
+
47
+ /**
48
+ * The properties map used for substitutions.
49
+ */
50
+ properties: LiquidSubstitutionsStrings
51
+
52
+ /**
53
+ * The logger instance for output and diagnostics.
54
+ */
55
+ log: Logger
56
+ }
57
+
58
+ /**
59
+ * Liquid drop that resolves `property` values for template substitutions.
60
+ *
61
+ * @remarks
62
+ * This drop exposes properties to the Liquid engine and performs
63
+ * additional substitutions when a property value itself contains Liquid syntax.
64
+ *
65
+ * Implements the Liquid Drop pattern to provide lazy property resolution and
66
+ * recursive template evaluation. When a template accesses `properties.foo`,
67
+ * the Liquid engine calls {@link LiquidPropertiesDrop.liquidMethodMissing}
68
+ * which:
69
+ *
70
+ * <ol>
71
+ * <li>Looks up the property value in the properties map.</li>
72
+ * <li>Checks if the value contains Liquid syntax
73
+ * (<code>\{\{</code> or <code>\{%</code>).</li>
74
+ * <li>If yes, recursively evaluates the value as a Liquid template.</li>
75
+ * <li>Returns the final resolved value.</li>
76
+ * </ol>
77
+ *
78
+ * This enables multi-level property references where one property can
79
+ * reference another, which can reference yet another, with the engine
80
+ * automatically resolving the entire chain.
81
+ */
82
+ export class LiquidPropertiesDrop extends liquidjs.Drop {
83
+ // --------------------------------------------------------------------------
84
+ // Public Members.
85
+
86
+ // --------------------------------------------------------------------------
87
+ // Protected Members.
88
+
89
+ /**
90
+ * The logger instance for output and diagnostics.
91
+ */
92
+ protected readonly _log: Logger
93
+
94
+ /**
95
+ * The properties map used for substitutions.
96
+ */
97
+ protected readonly _properties: LiquidSubstitutionsStrings
98
+
99
+ /**
100
+ * The Liquid engine used to render nested substitutions.
101
+ */
102
+ protected readonly _engine: liquidjs.Liquid
103
+
104
+ // --------------------------------------------------------------------------
105
+ // Constructor.
106
+
107
+ /**
108
+ * Constructs a properties drop.
109
+ *
110
+ * @param engine - The Liquid engine used to render nested substitutions.
111
+ * @param properties - The properties map used for substitutions.
112
+ * @param log - The logger instance for output and diagnostics.
113
+ */
114
+ constructor({
115
+ engine,
116
+ properties,
117
+ log,
118
+ }: LiquidPropertiesDropConstructorParameters) {
119
+ super()
120
+
121
+ log.trace(`${LiquidPropertiesDrop.name}()`)
122
+
123
+ this._log = log
124
+ this._engine = engine
125
+ this._properties = properties
126
+ }
127
+
128
+ // --------------------------------------------------------------------------
129
+ // Public Methods.
130
+
131
+ /**
132
+ * Resolves a missing property and performs nested substitutions.
133
+ *
134
+ * @remarks
135
+ * Called by the Liquid engine when a property is accessed that doesn't
136
+ * exist as a regular method. This implements the Drop pattern for dynamic
137
+ * property resolution.
138
+ *
139
+ * Resolution process:
140
+ *
141
+ * <ol>
142
+ * <li>Validate the property exists, throw <code>InputError</code> if
143
+ * not.</li>
144
+ * <li>Retrieve the property value (string, array, or object).</li>
145
+ * <li>If object, return as-is for Liquid to access nested properties.</li>
146
+ * <li>If array, join elements into a single string for processing.</li>
147
+ * <li>If the value contains Liquid syntax, recursively render it with the
148
+ * current context to resolve nested references.</li>
149
+ * <li>Return the final resolved value.</li>
150
+ * </ol>
151
+ *
152
+ * Array values are concatenated without separators, allowing properties
153
+ * to span multiple lines in JSON while producing a single string output.
154
+ *
155
+ * @param key - The property name requested by the template.
156
+ * @param context - The Liquid rendering context.
157
+ * @returns The resolved property value.
158
+ *
159
+ * @throws {@link TemplateError}
160
+ * If the property is not defined.
161
+ */
162
+ override async liquidMethodMissing(
163
+ key: string,
164
+ context: liquidjs.Context
165
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
166
+ ): Promise<any> {
167
+ // console.log(key)
168
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
169
+ if (this._properties[key] === undefined) {
170
+ throw new TemplateError(`"properties.${key}" not defined`)
171
+ }
172
+
173
+ const log = this._log
174
+
175
+ const value = this._properties[key]
176
+ log.trace(
177
+ `${LiquidPropertiesDrop.name}.liquidMethodMissing('${key}') in (`,
178
+ value,
179
+ ')'
180
+ )
181
+
182
+ let result: string | string[]
183
+
184
+ if (isJsonObject(value)) {
185
+ return value
186
+ }
187
+
188
+ // If the property value is an array, merge them into a single string.
189
+ const valueString = Array.isArray(value) ? value.join('') : value
190
+ if (valueString.includes('{{') || valueString.includes('{%')) {
191
+ result = (await this._engine.parseAndRender(
192
+ valueString,
193
+ context
194
+ )) as string
195
+ } else {
196
+ result = value
197
+ }
198
+ log.trace(
199
+ `${LiquidPropertiesDrop.name}.liquidMethodMissing('${key}')` + ` => (`,
200
+ result,
201
+ ')'
202
+ )
203
+ return result
204
+ }
205
+ }
206
+
207
+ // ============================================================================
208
+
209
+ /**
210
+ * Configuration parameters for constructing a matrix drop instance.
211
+ *
212
+ * @remarks
213
+ * This interface defines the required configuration for creating an
214
+ * instance of {@link LiquidMatrixDrop}. All properties are mandatory.
215
+ *
216
+ * The parameters provide the Liquid engine for nested template evaluation,
217
+ * the matrix parameters map for value lookups, and the logger for diagnostic
218
+ * output during matrix parameter resolution and substitution.
219
+ */
220
+ export interface LiquidMatrixDropConstructorParameters {
221
+ /**
222
+ * The Liquid engine used to render nested substitutions.
223
+ */
224
+ engine: liquidjs.Liquid
225
+
226
+ /**
227
+ * The matrix parameters map used for substitutions.
228
+ */
229
+ matrix: LiquidSubstitutionsStrings
230
+
231
+ /**
232
+ * The logger instance for output and diagnostics.
233
+ */
234
+ log: Logger
235
+ }
236
+
237
+ /**
238
+ * Liquid drop that resolves `matrix` parameter values for templates.
239
+ *
240
+ * @remarks
241
+ * This drop exposes matrix values to the Liquid engine and performs
242
+ * nested substitutions when a matrix value contains Liquid syntax.
243
+ *
244
+ * Matrix parameters are used during template expansion to generate multiple
245
+ * actions or build configurations from a single template definition. Each
246
+ * expanded instance receives a specific combination of matrix values.
247
+ *
248
+ * Implements the same Drop pattern as {@link LiquidPropertiesDrop} but
249
+ * for matrix-scoped variables. When a template accesses `matrix.arch`, the
250
+ * engine calls {@link LiquidMatrixDrop.liquidMethodMissing} which resolves
251
+ * the parameter value and recursively evaluates any nested Liquid syntax.
252
+ *
253
+ * Matrix parameters are isolated per template instance, ensuring that each
254
+ * generated action or configuration has access only to its specific matrix
255
+ * combination.
256
+ */
257
+ export class LiquidMatrixDrop extends liquidjs.Drop {
258
+ // --------------------------------------------------------------------------
259
+ // Protected Members.
260
+
261
+ /**
262
+ * The logger instance for output and diagnostics.
263
+ */
264
+ protected readonly _log: Logger
265
+
266
+ /**
267
+ * The matrix parameters map used for substitutions.
268
+ */
269
+ protected readonly _matrix: LiquidSubstitutionsStrings
270
+
271
+ /**
272
+ * The Liquid engine used to render nested substitutions.
273
+ */
274
+ protected readonly _engine: liquidjs.Liquid
275
+
276
+ // --------------------------------------------------------------------------
277
+ // Constructor.
278
+
279
+ /**
280
+ * Constructs a matrix drop.
281
+ *
282
+ * @param engine - The Liquid engine used to render nested substitutions.
283
+ * @param matrix - The matrix parameters map used for substitutions.
284
+ * @param log - The logger instance for output and diagnostics.
285
+ */
286
+ constructor({ engine, matrix, log }: LiquidMatrixDropConstructorParameters) {
287
+ super()
288
+
289
+ log.trace(`${LiquidMatrixDrop.name}()`)
290
+
291
+ this._log = log
292
+ this._engine = engine
293
+ this._matrix = matrix
294
+ }
295
+
296
+ // --------------------------------------------------------------------------
297
+ // Public Methods.
298
+
299
+ /**
300
+ * Resolves a missing matrix parameter and performs nested substitutions.
301
+ *
302
+ * @remarks
303
+ * Called by the Liquid engine when a matrix parameter is accessed that
304
+ * doesn't exist as a regular method. This implements the Drop pattern for
305
+ * dynamic matrix parameter resolution.
306
+ *
307
+ * Resolution process:
308
+ *
309
+ * <ol>
310
+ * <li>Validate the parameter exists, throw <code>InputError</code> if
311
+ * not.</li>
312
+ * <li>Retrieve the parameter value (string, array, or object).</li>
313
+ * <li>If object, return as-is for Liquid to access nested properties.</li>
314
+ * <li>If array, join elements into a single string for processing.</li>
315
+ * <li>If the value contains Liquid syntax, recursively render it with the
316
+ * current context to resolve nested references.</li>
317
+ * <li>Return the final resolved value.</li>
318
+ * </ol>
319
+ *
320
+ * This mirrors the behavior of {@link LiquidPropertiesDrop} but operates
321
+ * on matrix parameters instead of properties. Matrix values can reference
322
+ * other substitution variables, enabling complex template expansions.
323
+ *
324
+ * @param key - The matrix parameter name requested by the template.
325
+ * @param context - The Liquid rendering context.
326
+ * @returns The resolved matrix parameter value.
327
+ *
328
+ * @throws {@link TemplateError}
329
+ * If the matrix parameter is not defined.
330
+ */
331
+ override async liquidMethodMissing(
332
+ key: string,
333
+ context: liquidjs.Context
334
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
335
+ ): Promise<any> {
336
+ // console.log(key)
337
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
338
+ if (this._matrix[key] === undefined) {
339
+ throw new TemplateError(`"matrix.${key}" not defined`)
340
+ }
341
+
342
+ const log = this._log
343
+
344
+ const value = this._matrix[key]
345
+ log.trace(
346
+ `${LiquidMatrixDrop.name}.liquidMethodMissing('${key}') in (`,
347
+ value,
348
+ ')'
349
+ )
350
+
351
+ let result: string | string[]
352
+
353
+ if (isJsonObject(value)) {
354
+ return value
355
+ }
356
+
357
+ // If the property value is an array, merge them into a single string.
358
+ const valueString = Array.isArray(value) ? value.join('') : value
359
+ if (valueString.includes('{{') || valueString.includes('{%')) {
360
+ result = (await this._engine.parseAndRender(
361
+ valueString,
362
+ context
363
+ )) as string
364
+ } else {
365
+ result = value
366
+ }
367
+ log.trace(
368
+ `${LiquidMatrixDrop.name}.liquidMethodMissing('${key}')` + ` => (`,
369
+ result,
370
+ ')'
371
+ )
372
+ return result
373
+ }
374
+ }
375
+
376
+ // ----------------------------------------------------------------------------
@@ -0,0 +1,249 @@
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 * as os from 'node:os'
15
+ import * as path from 'node:path'
16
+ import * as util from 'node:util'
17
+
18
+ // https://www.npmjs.com/package/liquidjs
19
+ import * as liquidjs from 'liquidjs'
20
+
21
+ // ----------------------------------------------------------------------------
22
+
23
+ import { isJsonObject } from '../functions/is-something.js'
24
+ import { PlatformDetector } from './platform-detector.js'
25
+
26
+ // ============================================================================
27
+
28
+ /**
29
+ * Liquid engine configured for <b>xpm</b> templates.
30
+ *
31
+ * @remarks
32
+ * This class extends the Liquid engine and registers custom filters
33
+ * for path manipulation, string formatting, and convenience helpers used across
34
+ * <b>xpm</b> templates.
35
+ *
36
+ * The engine is configured with strict parsing options to catch template
37
+ * errors early during development. Custom filters are organized into
38
+ * categories:
39
+ *
40
+ * <ol>
41
+ * <li><b>Path manipulation:</b> Platform-specific and cross-platform path
42
+ * operations
43
+ * (<code>basename</code>, <code>dirname</code>, <code>join</code>,
44
+ * <code>relative</code>, <code>normalize</code>) for default, POSIX, and
45
+ * Win32 paths.</li>
46
+ * <li><b>String formatting:</b> Utilities for printf-style formatting and
47
+ * filename
48
+ * sanitization.</li>
49
+ * <li><b>Array/string conversion:</b> Filters for joining and splitting
50
+ * lines.</li>
51
+ * <li><b>Object introspection:</b> Filters for extracting object keys.</li>
52
+ * </ol>
53
+ *
54
+ * These filters enable templates to perform complex path manipulations and
55
+ * string transformations without requiring external dependencies or custom
56
+ * template tags.
57
+ */
58
+ export class LiquidEngine extends liquidjs.Liquid {
59
+ // --------------------------------------------------------------------------
60
+ // Private Members.
61
+
62
+ /**
63
+ * The platform detector instance for platform-specific behaviour.
64
+ */
65
+ private readonly platformDetector: PlatformDetector
66
+
67
+ // --------------------------------------------------------------------------
68
+ // Constructor.
69
+
70
+ /**
71
+ * Constructs a Liquid engine instance with xpm-specific settings and
72
+ * filters.
73
+ *
74
+ * @remarks
75
+ * The constructor configures strict parsing options and registers
76
+ * filters for path handling, formatting, and list operations.
77
+ *
78
+ * Configuration options:
79
+ *
80
+ * <ul>
81
+ * <li><b>strictFilters:</b> Throw errors for undefined filters rather than
82
+ * silently ignoring them.</li>
83
+ * <li><b>strictVariables:</b> Throw errors for undefined variables rather
84
+ * than
85
+ * rendering empty strings.</li>
86
+ * <li><b>trimTagLeft/Right:</b> Preserve whitespace around template
87
+ * tags.</li>
88
+ * <li><b>trimOutputLeft/Right:</b> Preserve whitespace around output
89
+ * expressions.</li>
90
+ * <li><b>greedy:</b> Use non-greedy matching for better template
91
+ * compatibility.</li>
92
+ * <li><b>lenientIf:</b> Allow flexible truthiness in conditional
93
+ * expressions.</li>
94
+ * </ul>
95
+ *
96
+ * Filter registration:
97
+ *
98
+ * <ul>
99
+ * <li><b>Platform-aware path filters (default, posix, win32):</b> delegate to
100
+ * Node.js path module for consistent cross-platform behavior.</li>
101
+ * <li><b>Custom filters (to_filename, join_lines, split_lines, keys):</b>
102
+ * provide
103
+ * template-specific functionality not available in standard Liquid.</li>
104
+ * <li>All filters are registered during construction for immediate
105
+ * availability in templates.</li>
106
+ * </ul>
107
+ *
108
+ * @param platformDetector - The platform detector instance for
109
+ * platform-specific behaviour. Defaults to a new {@link PlatformDetector}
110
+ * instance.
111
+ */
112
+ constructor(platformDetector: PlatformDetector = new PlatformDetector()) {
113
+ super({
114
+ strictFilters: true,
115
+ strictVariables: true,
116
+ trimTagLeft: false,
117
+ trimTagRight: false,
118
+ trimOutputLeft: false,
119
+ trimOutputRight: false,
120
+ greedy: false,
121
+ lenientIf: true,
122
+ })
123
+
124
+ this.platformDetector = platformDetector
125
+
126
+ // https://liquidjs.com/api/classes/liquid_.liquid.html#registerFilter
127
+ // https://nodejs.org/dist/latest-v16.x/docs/api/path.html
128
+
129
+ // Add the main path manipulation functions.
130
+ this.registerFilter('path_basename', (p: string, ...arg) =>
131
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
132
+ path.basename(p, ...arg)
133
+ )
134
+
135
+ this.registerFilter('path_dirname', (p: string) => path.dirname(p))
136
+
137
+ this.registerFilter('path_normalize', (p: string) => path.normalize(p))
138
+
139
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
140
+ this.registerFilter('path_join', (p, ...args) => path.join(p, ...args))
141
+
142
+ this.registerFilter('path_relative', (from: string, to: string) =>
143
+ path.relative(from, to)
144
+ )
145
+
146
+ this.registerFilter('path_posix_basename', (p: string, ...arg) =>
147
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
148
+ path.posix.basename(p, ...arg)
149
+ )
150
+
151
+ this.registerFilter('path_posix_dirname', (p: string) =>
152
+ path.posix.dirname(p)
153
+ )
154
+
155
+ this.registerFilter('path_posix_normalize', (p: string) =>
156
+ path.posix.normalize(p)
157
+ )
158
+
159
+ this.registerFilter('path_posix_join', (p, ...args) =>
160
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
161
+ path.posix.join(p, ...args)
162
+ )
163
+
164
+ this.registerFilter('path_posix_relative', (from: string, to: string) =>
165
+ path.posix.relative(from, to)
166
+ )
167
+
168
+ this.registerFilter('path_win32_basename', (p: string, ...arg) =>
169
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
170
+ path.win32.basename(p, ...arg)
171
+ )
172
+
173
+ this.registerFilter('path_win32_dirname', (p: string) =>
174
+ path.win32.dirname(p)
175
+ )
176
+
177
+ this.registerFilter('path_win32_normalize', (p: string) =>
178
+ path.win32.normalize(p)
179
+ )
180
+
181
+ this.registerFilter('path_win32_join', (p, ...args) =>
182
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
183
+ path.win32.join(p, ...args)
184
+ )
185
+
186
+ this.registerFilter('path_win32_relative', (from: string, to: string) =>
187
+ path.win32.relative(from, to)
188
+ )
189
+
190
+ // https://nodejs.org/dist/latest-v16.x/docs/api/util.html
191
+
192
+ this.registerFilter('util_format', (format, ...args) => {
193
+ // console.log([...args])
194
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
195
+ return util.format(format, ...args)
196
+ })
197
+
198
+ // Custom action.
199
+ this.registerFilter(
200
+ 'to_filename',
201
+ // Replace non alphanumeric chars with dashes to make the paths
202
+ // comply with filesystem names.
203
+ (input: string): string => {
204
+ /* c8 ignore start - windows specific code cannot be tested
205
+ on other platforms */
206
+ const fixed = this.platformDetector.isWindows()
207
+ ? input.replace(/[^a-zA-Z0-9\\:]+/g, '-')
208
+ : input.replace(/[^a-zA-Z0-9/]+/g, '-')
209
+ /* c8 ignore stop */
210
+
211
+ return fixed.replace(/--/g, '-')
212
+ }
213
+ )
214
+
215
+ this.registerFilter('join_lines', (input: string[]): string => {
216
+ // Convert an array into a string with each element on a separate line.
217
+ if (Array.isArray(input)) {
218
+ return input.join(os.EOL)
219
+ }
220
+ return String(input)
221
+ })
222
+
223
+ // Convert a string with lines into an array.
224
+ this.registerFilter('split_lines', (input: string | string[]): string[] => {
225
+ if (Array.isArray(input)) {
226
+ // If already an array, first flatten it, then split it.
227
+ // This is needed in case any of the lines include EOLs.
228
+ return input.join(os.EOL).split(os.EOL)
229
+ }
230
+ return input.split(os.EOL)
231
+ })
232
+
233
+ this.registerFilter('keys', (input: unknown): string[] | string => {
234
+ if (isJsonObject(input)) {
235
+ const keys = Object.keys(input as object)
236
+ // console.log('input object', input)
237
+ // console.log('input keys', keys)
238
+ return keys
239
+ } else if (Array.isArray(input)) {
240
+ const keys = Object.keys(input)
241
+ return keys
242
+ } else {
243
+ return String(input)
244
+ }
245
+ })
246
+ }
247
+ }
248
+
249
+ // ----------------------------------------------------------------------------