@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,253 @@
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
+
16
+ import { Context } from 'liquidjs'
17
+
18
+ import { Logger } from '@xpack/logger'
19
+
20
+ // ----------------------------------------------------------------------------
21
+
22
+ import { LiquidEngine } from '../classes/liquid-engine.js'
23
+ import { TemplateError } from '../classes/errors.js'
24
+ import {
25
+ LiquidPropertiesDrop,
26
+ LiquidMatrixDrop,
27
+ } from '../classes/liquid-drop.js'
28
+ import {
29
+ LiquidSubstitutionsVariables,
30
+ LiquidSubstitutionsStrings,
31
+ } from '../data/substitutions-variables.js'
32
+
33
+ // ============================================================================
34
+
35
+ // This limit prevents infinite loops from circular template references.
36
+ // Chosen to be high enough for deeply nested legitimate templates
37
+ // (typical nesting is < 10 levels) while catching pathological cases.
38
+ // In practice, most templates resolve in 2-5 iterations.
39
+ const PERFORM_SUBSTITUTION_MAX_ITERATIONS = 42 // Prevent infinite loops
40
+ // This limit prevents memory exhaustion from exponentially expanding
41
+ // templates. Set to 10MB to accommodate legitimate large outputs
42
+ // (e.g., generated code files, documentation) whilst catching
43
+ // pathological cases such as unbounded loops or recursive expansions.
44
+ // Typical template outputs are a few hundred bytes.
45
+ const PERFORM_SUBSTITUTION_MAX_OUTPUT_SIZE = 42 * 1024 // 42KB
46
+
47
+ /**
48
+ * Performs substitutions on an input string using Liquid.
49
+ *
50
+ * @remarks
51
+ * This function processes Liquid template syntax (variables and tags) by
52
+ * repeatedly rendering the input until no more substitutions are detected.
53
+ * The iterative approach supports nested substitutions where one property
54
+ * references another.
55
+ *
56
+ * Processing workflow:
57
+ *
58
+ * <ol>
59
+ * <li>Skip processing for empty strings to avoid unnecessary overhead.</li>
60
+ * <li>Prepare Liquid context with substitution variables.</li>
61
+ * <li>If <code>properties</code> exist, wrap them in
62
+ * <code>LiquidPropertiesDrop</code>
63
+ * for lazy evaluation and nested substitution support.</li>
64
+ * <li>If <code>matrix</code> parameters exist, wrap them in
65
+ * <code>LiquidMatrixDrop</code>
66
+ * for template expansion variable access.</li>
67
+ * <li>Iterate while Liquid syntax (<code>\{\{</code> or <code>\{%</code>)
68
+ * is present:
69
+ * <ul>
70
+ * <li>Parse and render the current string.</li>
71
+ * <li>Break if no changes occur (safety check).</li>
72
+ * <li>Continue with the substituted result.</li>
73
+ * </ul>
74
+ * </li>
75
+ * <li>Return the fully substituted string.</li>
76
+ * </ol>
77
+ *
78
+ * The Drop pattern enables recursive property resolution: when a template
79
+ * accesses `{{ properties.foo }}` and `foo` contains `{{ properties.bar }}`,
80
+ * the next iteration resolves `bar`, and so on until no Liquid syntax
81
+ * remains.
82
+ *
83
+ * Error handling:
84
+ *
85
+ * Liquid rendering errors are caught, stripped of line
86
+ * number information (which can be misleading for nested templates), and
87
+ * re-thrown as {@link ConfigurationError}.
88
+ *
89
+ * @param log - The logger instance for output and diagnostics.
90
+ * @param engine - The Liquid engine used to render substitutions.
91
+ * @param input - The input string, possibly containing substitutions.
92
+ * @param substitutionsVariables - The variables available for substitution.
93
+ * @param maxIterations - Optional maximum number of substitution iterations
94
+ * to prevent infinite loops from circular references. Defaults to 420.
95
+ * @param maxOutputSize - Optional maximum output size in bytes to prevent
96
+ * memory exhaustion from exponentially expanding templates. Defaults to 43008
97
+ * bytes (42KB).
98
+ * @returns The fully substituted string.
99
+ *
100
+ * @throws {@link TemplateError}
101
+ * If Liquid rendering fails, iteration limit is exceeded, or output size
102
+ * limit is exceeded.
103
+ */
104
+ export async function performSubstitutions({
105
+ engine,
106
+ input,
107
+ substitutionsVariables,
108
+ log,
109
+ maxIterations = PERFORM_SUBSTITUTION_MAX_ITERATIONS,
110
+ maxOutputSize = PERFORM_SUBSTITUTION_MAX_OUTPUT_SIZE,
111
+ }: {
112
+ engine: LiquidEngine
113
+ input: string
114
+ substitutionsVariables: LiquidSubstitutionsVariables
115
+ log: Logger
116
+ maxIterations?: number
117
+ maxOutputSize?: number
118
+ }): Promise<string> {
119
+ assert(engine, 'engine is required')
120
+ assert(substitutionsVariables, 'substitutionsVariables is required')
121
+ assert(log, 'log is required')
122
+ assert(maxIterations > 0, 'maxIterations must be a positive integer')
123
+ assert(maxOutputSize > 0, 'maxOutputSize must be a positive integer')
124
+
125
+ if (input.trim() === '') {
126
+ // Spare it the trouble for empty strings.
127
+ return input
128
+ }
129
+
130
+ // Wrap properties into a liquid drop (a mechanism to process
131
+ // substitutions immediately).
132
+ let properties: LiquidSubstitutionsStrings | LiquidPropertiesDrop =
133
+ substitutionsVariables.properties
134
+ let matrix: LiquidSubstitutionsStrings | LiquidMatrixDrop | undefined =
135
+ substitutionsVariables.matrix
136
+
137
+ if (Object.keys(substitutionsVariables.properties).length > 0) {
138
+ properties = new LiquidPropertiesDrop({
139
+ log,
140
+ engine,
141
+ properties: substitutionsVariables.properties,
142
+ })
143
+ }
144
+ if (
145
+ substitutionsVariables.matrix &&
146
+ Object.keys(substitutionsVariables.matrix).length > 0
147
+ ) {
148
+ matrix = new LiquidMatrixDrop({
149
+ log,
150
+ engine,
151
+ matrix: substitutionsVariables.matrix,
152
+ })
153
+ }
154
+
155
+ // Passing the engine options is important, otherwise unknown
156
+ // variables do not trigger exceptions.
157
+ const context = new Context(
158
+ {
159
+ ...substitutionsVariables,
160
+ properties,
161
+ matrix,
162
+ },
163
+ engine.options,
164
+ { sync: false }
165
+ )
166
+
167
+ log.trace(`performSubstitutions('${input}')`)
168
+
169
+ let current: string = input
170
+ let substituted: string = current
171
+ let count = 0
172
+ const LIQUID_SYNTAX_REGEX = /\{\{|\{%/
173
+
174
+ // Iterate until all substitutions are done.
175
+ while (LIQUID_SYNTAX_REGEX.test(current)) {
176
+ // Safety net: This limit prevents infinite loops from circular template
177
+ // references. In normal operation, templates resolve in a few iterations.
178
+ // The check is unlikely to trigger because:
179
+ // 1. Templates are validated during configuration loading
180
+ // 2. Liquid engine throws errors for most invalid references
181
+ // 3. The break below catches non-changing substitutions
182
+ // However, this protects against edge cases like deeply nested context
183
+ // references or malformed template logic that the engine doesn't catch.
184
+ /* c8 ignore start - safety net, normally should not get there. */
185
+ if (++count > maxIterations) {
186
+ throw new TemplateError(
187
+ `Substitution limit exceeded ` +
188
+ `(${String(maxIterations)} iterations). ` +
189
+ `Possible circular reference in template.`
190
+ )
191
+ }
192
+ /* c8 ignore stop */
193
+ // May throw.
194
+ try {
195
+ substituted = (await engine.parseAndRender(current, context)) as string
196
+
197
+ /* c8 ignore start - safety net. */
198
+ if (substituted.length > maxOutputSize) {
199
+ throw new TemplateError(
200
+ `Template expansion exceeded size limit ` +
201
+ `(${String(maxOutputSize)} bytes). ` +
202
+ `Output was ${String(substituted.length)} bytes.`
203
+ )
204
+ } /* c8 ignore stop */
205
+
206
+ // Safety net: This check detects when a substitution pass produces no
207
+ // changes despite template markers being present. This is unlikely
208
+ // because:
209
+ // 1. The while condition checks for markers ({{ or {%)
210
+ // 2. Liquid engine normally processes all markers or throws errors
211
+ // 3. Valid markers always resolve to something (even empty string)
212
+ // However, this catches edge cases like malformed markers that pass the
213
+ // simple includes() check but don't match Liquid's parser, preventing
214
+ // infinite loops when the iteration limit isn't reached.
215
+ /* c8 ignore start - safety net, normally errors throw. */
216
+ if (substituted === current) {
217
+ log.warn(
218
+ `performSubstitutions() step ${String(count)} => (`,
219
+ substituted,
220
+ ') did not change'
221
+ )
222
+
223
+ break
224
+ } /* c8 ignore stop */
225
+ } catch (error) {
226
+ if (error instanceof Error) {
227
+ log.trace(`Liquid error: ${error.message}`)
228
+ const cleanMessage = error.message.replace(/, line:.*/g, '')
229
+ throw new TemplateError(cleanMessage)
230
+ // Safety net: This handles the unlikely case where something other than
231
+ // an Error is thrown. JavaScript/TypeScript allows throwing any value,
232
+ // but Liquid engine and Node.js fs operations consistently throw Error
233
+ // instances. This provides robust error handling for unexpected
234
+ // scenarios.
235
+ /* c8 ignore start - safety net, currently all are Errors */
236
+ } else {
237
+ throw new TemplateError(String(error))
238
+ }
239
+ /* c8 ignore stop */
240
+ }
241
+
242
+ log.trace(
243
+ `performSubstitutions() step ${String(count)} => (`,
244
+ substituted,
245
+ ')'
246
+ )
247
+ current = substituted
248
+ }
249
+
250
+ return substituted
251
+ }
252
+
253
+ // ----------------------------------------------------------------------------
@@ -0,0 +1,151 @@
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 { PlatformDetector } from '../classes/platform-detector.js'
15
+
16
+ // ============================================================================
17
+
18
+ /**
19
+ * Extracts an error message string from an unknown error value.
20
+ *
21
+ * @remarks
22
+ * This utility handles error values of any type, extracting the message
23
+ * property from `Error` instances or converting other types to strings.
24
+ * Useful for consistent error reporting when the error type is unknown.
25
+ *
26
+ * TypeScript's catch clause types errors as `unknown` for safety, since
27
+ * JavaScript allows throwing any value (not just `Error` instances). This
28
+ * function provides a type-safe way to extract a message string:
29
+ *
30
+ * <ul>
31
+ * <li><b>For Error instances:</b> Returns the <code>message</code>
32
+ * property.</li>
33
+ * <li><b>For other types:</b> Converts to string using <code>String()</code>,
34
+ * which handles
35
+ * primitives, objects with <code>toString()</code>, <code>null</code>,
36
+ * and <code>undefined</code> gracefully.</li>
37
+ * </ul>
38
+ *
39
+ * Common usage pattern:
40
+ * ```typescript
41
+ * try {
42
+ * // code that might throw
43
+ * } catch (error) {
44
+ * const message = getErrorMessage(error);
45
+ * log.error(message);
46
+ * }
47
+ * ```
48
+ *
49
+ * @param error - The error value to convert.
50
+ * @returns The error message string.
51
+ */
52
+ export function getErrorMessage(error: unknown): string {
53
+ if (error instanceof Error) {
54
+ return error.message
55
+ }
56
+ return String(error)
57
+ }
58
+
59
+ // ----------------------------------------------------------------------------
60
+
61
+ /**
62
+ * Builds a unique key using the current platform and architecture.
63
+ *
64
+ * @remarks
65
+ * Generates a platform identifier string used for matching binary packages
66
+ * to the current system or for platform-specific configuration.
67
+ *
68
+ * Platform key format: `<platform>-<arch>`
69
+ *
70
+ * Examples:
71
+ *
72
+ * <ul>
73
+ * <li><code>darwin-x64</code> (macOS on Intel)</li>
74
+ * <li><code>darwin-arm64</code> (macOS on Apple Silicon)</li>
75
+ * <li><code>linux-x64</code> (Linux on 64-bit Intel/AMD)</li>
76
+ * <li><code>win32-x64</code> (Windows on 64-bit)</li>
77
+ * </ul>
78
+ *
79
+ * Architecture coercion rules (when doForce32bit is true):
80
+ *
81
+ * <ul>
82
+ * <li><b>x64 → ia32:</b> Coerces 64-bit Intel/AMD architecture to 32-bit
83
+ * on all platforms.</li>
84
+ * <li><b>arm64 → arm:</b> Coerces 64-bit ARM architecture to 32-bit
85
+ * on all platforms.</li>
86
+ * </ul>
87
+ *
88
+ * This coercion is useful for backward compatibility scenarios where only
89
+ * 32-bit binaries are available but can run on 64-bit systems. The
90
+ * platform key matches the naming conventions used in binary xPack
91
+ * distributions.
92
+ *
93
+ * @param doForce32bit - Whether to coerce 64-bit architectures to
94
+ * their 32-bit equivalents.
95
+ * @param platformDetector - The platform detector instance to use. Defaults
96
+ * to a new {@link PlatformDetector} instance.
97
+ * @returns The platform key in the form `platform-arch`.
98
+ */
99
+ export function getPlatformKey({
100
+ doForce32bit = false,
101
+ platformDetector = new PlatformDetector(),
102
+ }: {
103
+ doForce32bit?: boolean
104
+ platformDetector?: PlatformDetector
105
+ } = {}): string {
106
+ const { platform, arch } = platformDetector.getPlatformInfo({ doForce32bit })
107
+ const key = `${platform}-${arch}`
108
+ return key
109
+ }
110
+
111
+ // ----------------------------------------------------------------------------
112
+
113
+ /**
114
+ * Checks whether a string contains Liquid template syntax.
115
+ *
116
+ * @remarks
117
+ * This utility function detects the presence of Liquid template markers
118
+ * in a string, indicating that the string requires template processing.
119
+ *
120
+ * Liquid syntax patterns detected:
121
+ *
122
+ * <ol>
123
+ * <li><b>Variable output:</b> <code>\{\{</code> marks the start of a variable
124
+ * interpolation (e.g., <code>\{\{ package.name \}\}</code>).</li>
125
+ * <li><b>Control flow:</b> <code>\{%</code> marks the start of a tag
126
+ * for logic or iteration (e.g., <code>\{% if condition %\}</code>,
127
+ * <code>\{% for item in array %\}</code>).</li>
128
+ * </ol>
129
+ *
130
+ * The function uses a regular expression to efficiently scan the string
131
+ * for these markers without needing to check for both patterns separately.
132
+ * This is more efficient than calling <code>includes()</code> twice and
133
+ * provides a single point of maintenance for Liquid syntax detection logic.
134
+ *
135
+ * Common usage:
136
+ *
137
+ * <ul>
138
+ * <li>Determine whether to process a value through the Liquid engine.</li>
139
+ * <li>Skip unnecessary template evaluation for static strings.</li>
140
+ * <li>Validate configuration values for template content.</li>
141
+ * </ul>
142
+ *
143
+ * @param value - The string to check for Liquid syntax.
144
+ * @returns <code>true</code> if the string contains Liquid syntax markers,
145
+ * <code>false</code> otherwise.
146
+ */
147
+ export function hasLiquidSyntax(value: string): boolean {
148
+ return /\{\{|\{%/.test(value)
149
+ }
150
+
151
+ // ----------------------------------------------------------------------------
package/src/index.ts CHANGED
@@ -9,27 +9,80 @@
9
9
  * be obtained from https://opensource.org/license/mit.
10
10
  */
11
11
 
12
- /* eslint max-len: [ "error", 80, { "ignoreUrls": true } ] */
12
+ // ----------------------------------------------------------------------------
13
+
14
+ /**
15
+ * A Node.js TypeScript module with the core code for <b>xpm</b> and
16
+ * <b>xpm enabled</b> projects.
17
+ *
18
+ * @remarks
19
+ * This library groups together various classes and functions used in common
20
+ * by <b>xpm</b> and the Visual Studio Code extension.
21
+ *
22
+ * The main functionality is to manage actions and build configurations,
23
+ * especially those defined using Liquid templates.
24
+ *
25
+ * <h3>The Lazy Evaluation Mechanism</h3>
26
+ *
27
+ * Actions ({@link Actions}) and build configurations
28
+ * ({@link BuildConfigurations}) implement a two-step lazy evaluation
29
+ * process to avoid unnecessary operations:
30
+ *
31
+ * <ol>
32
+ * <li><b>Name Expansion:</b> During collection initialisation, only
33
+ * the matrix of
34
+ * options is evaluated for each template, expanding template names into
35
+ * concrete action or configuration names without processing their
36
+ * content.</li>
37
+ * <li><b>Content Evaluation:</b> Later, when an action or build
38
+ * configuration is actually accessed and initialised (via
39
+ * <code>Action.initialise()</code>
40
+ * or <code>BuildConfiguration.initialise()</code>), the template
41
+ * is fully
42
+ * evaluated and Liquid substitutions are performed.</li>
43
+ * </ol>
44
+ *
45
+ * This approach ensures that only items that are actually used incur the cost
46
+ * of template evaluation and variable substitution, significantly improving
47
+ * performance for projects with many actions or configurations.
48
+ *
49
+ * @packageDocumentation
50
+ */
51
+
52
+ // ----------------------------------------------------------------------------
53
+
54
+ // Public entry point for the xpm-lib TypeScript library.
55
+ export * from './functions/chmod-recursively.js'
56
+ export * from './functions/filter-paths.js'
57
+ export * from './functions/is-something.js'
58
+ export * from './functions/matrix-expander.js'
59
+ export * from './functions/perform-substitutions.js'
60
+ export * from './functions/utils.js'
61
+
62
+ export * from './classes/errors.js'
63
+ export * from './classes/init-template-base.js'
64
+ export * from './classes/actions.js'
65
+ export * from './classes/build-configurations.js'
66
+ export * from './classes/combinations-generator.js'
67
+ export * from './classes/liquid-drop.js'
68
+ export * from './classes/liquid-engine.js'
69
+ export * from './classes/data-model.js'
70
+ export * from './classes/package.js'
71
+ export * from './classes/platform-detector.js'
72
+ export * from './classes/policies.js'
73
+ export * from './classes/template-expander.js'
74
+
75
+ export * from './data/substitutions-variables.js'
76
+
77
+ export * from './types/json.js'
78
+ export * from './types/xpm-init-template.js'
79
+ export * from './types/xpm.js'
13
80
 
14
81
  // ----------------------------------------------------------------------------
15
82
 
16
- // Re-export all library definitions.
17
- export * from './lib/functions/chmod-recursive.js'
18
- export * from './lib/functions/perform-substitutions.js'
19
- export * from './lib/functions/utils.js'
20
-
21
- export * from './lib/errors.js'
22
- export * from './lib/init-template-base.js'
23
- export * from './lib/liquid-actions.js'
24
- export * from './lib/liquid-build-configurations.js'
25
- export * from './lib/liquid-drop.js'
26
- export * from './lib/liquid-engine.js'
27
- export * from './lib/liquid-package.js'
28
- export * from './lib/package.js'
29
- export * from './lib/policies.js'
30
- export * from './lib/substitutions-variables.js'
31
- export * from './lib/types.js'
32
-
33
- export * from 'liquidjs'
83
+ // Note: liquidjs is not re-exported to avoid API Extractor conflicts.
84
+ // Consumers should import it directly: import { Liquid } from 'liquidjs'
85
+ // This ensures they use the same version as this library via peer dependencies.
86
+ // export * as liquidjs from 'liquidjs'
34
87
 
35
88
  // ----------------------------------------------------------------------------