@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,126 @@
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
+ * Replaces non-alphanumeric characters with dashes to make paths
20
+ * comply with file system names.
21
+ *
22
+ * @remarks
23
+ * This function sanitizes strings to be safely used as file or folder names
24
+ * by removing or replacing problematic characters that could cause issues
25
+ * across different file systems.
26
+ *
27
+ * Platform-specific processing:
28
+ *
29
+ * <ul>
30
+ * <li><b>Windows:</b> Preserves backslashes (<code>\\</code>) and colons
31
+ * (<code>:</code>) for drive letters
32
+ * and path separators (e.g., <code>C:\\path\\to\\file</code>). Replaces all
33
+ * other non-alphanumeric characters with dashes.</li>
34
+ * <li><b>POSIX (Linux, macOS):</b> Preserves forward slashes (<code>/</code>)
35
+ * for path separators. Replaces all other non-alphanumeric characters with
36
+ * dashes.</li>
37
+ * </ul>
38
+ *
39
+ * Post-processing: After character replacement, consecutive dashes are
40
+ * collapsed to a single dash to avoid excessive dashes from adjacent
41
+ * special characters (e.g., "foo--bar" becomes "foo-bar").
42
+ *
43
+ * Common use cases include sanitizing build configuration names,
44
+ * user-provided identifiers, and template-generated path components.
45
+ *
46
+ * @param input - A path candidate.
47
+ * @param platformDetector - The platform detector instance to use. Defaults
48
+ * to a new {@link PlatformDetector} instance.
49
+ * @returns A validated path.
50
+ */
51
+ export function filterPath(
52
+ input: string,
53
+ platformDetector: PlatformDetector = new PlatformDetector()
54
+ ): string {
55
+ const fixed = platformDetector.isWindows()
56
+ ? input.replace(/[^a-zA-Z0-9\\:]+/g, '-')
57
+ : input.replace(/[^a-zA-Z0-9/]+/g, '-')
58
+ return fixed.replace(/--/g, '-')
59
+ }
60
+
61
+ /**
62
+ * Replaces non-alphanumeric characters with dashes to make paths
63
+ * comply with POSIX file system names.
64
+ *
65
+ * @remarks
66
+ * This function provides explicit POSIX path sanitization regardless of the
67
+ * current platform. Useful when generating paths that will be used on
68
+ * Linux or macOS systems, or when consistency across platforms is required.
69
+ *
70
+ * Processing rules:
71
+ *
72
+ * <ul>
73
+ * <li>Preserves forward slashes (<code>/</code>) for path separators.</li>
74
+ * <li>Replaces all non-alphanumeric characters (except <code>/</code>) with
75
+ * dashes.</li>
76
+ * <li>Collapses consecutive dashes to single dashes.</li>
77
+ * </ul>
78
+ *
79
+ * Use this function instead of {@link filterPath} when you need guaranteed
80
+ * POSIX-style sanitization even when running on Windows, such as when
81
+ * generating paths for remote Linux systems or container images.
82
+ *
83
+ * @param input - A path candidate.
84
+ * @returns A validated path.
85
+ */
86
+ export function filterPosixPath(input: string): string {
87
+ /* istanbul ignore next */
88
+ const fixed = input.replace(/[^a-zA-Z0-9/]+/g, '-')
89
+
90
+ return fixed.replace(/--/g, '-')
91
+ }
92
+
93
+ /**
94
+ * Replaces non-alphanumeric characters with dashes to make paths
95
+ * comply with Windows file system names.
96
+ *
97
+ * @remarks
98
+ * This function provides explicit Windows path sanitization regardless of
99
+ * the current platform. Useful when generating paths that will be used on
100
+ * Windows systems, or when consistency across platforms is required.
101
+ *
102
+ * Processing rules:
103
+ *
104
+ * <ul>
105
+ * <li>Preserves backslashes (<code>\\</code>) for path separators.</li>
106
+ * <li>Preserves colons (<code>:</code>) for drive letter designation (e.g.,
107
+ * <code>C:</code>).</li>
108
+ * <li>Replaces all other non-alphanumeric characters with dashes.</li>
109
+ * <li>Collapses consecutive dashes to single dashes.</li>
110
+ * </ul>
111
+ *
112
+ * Use this function instead of {@link filterPath} when you need guaranteed
113
+ * Windows-style sanitization even when running on POSIX systems, such as
114
+ * when generating paths for remote Windows systems or WSL environments.
115
+ *
116
+ * @param input - A path candidate.
117
+ * @returns A validated path.
118
+ */
119
+ export function filterWin32Path(input: string): string {
120
+ /* istanbul ignore next */
121
+ const fixed = input.replace(/[^a-zA-Z0-9\\:]+/g, '-')
122
+
123
+ return fixed.replace(/--/g, '-')
124
+ }
125
+
126
+ // ----------------------------------------------------------------------------
@@ -0,0 +1,223 @@
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
+ /**
15
+ * Determines whether a value is a JavaScript primitive.
16
+ *
17
+ * @remarks
18
+ * In JavaScript, primitives are immutable values that are not objects:
19
+ * `string`, `number`, `bigint`, `boolean`, `undefined`, and `symbol`.
20
+ * This function
21
+ * also treats null as a primitive, following JavaScript's `typeof` behavior
22
+ * despite null being technically an object type.
23
+ *
24
+ * Returns `true` for: `string`, `number`, `bigint`, `boolean`, `undefined`,
25
+ * `symbol`, and `null`.
26
+ *
27
+ * Returns `false` for: objects, arrays, functions, and class instances.
28
+ *
29
+ * Useful for distinguishing between value types and reference types when
30
+ * processing JSON data or validating configuration inputs.
31
+ *
32
+ * @param value - The value to test.
33
+ * @returns `true` if the value is a primitive or `null`, `false` otherwise.
34
+ */
35
+ export function isPrimitive(value: unknown): boolean {
36
+ return (
37
+ (typeof value !== 'object' && typeof value !== 'function') || value === null
38
+ )
39
+ }
40
+
41
+ /**
42
+ * Determines whether a value is a string.
43
+ *
44
+ * @remarks
45
+ * This function acts as a TypeScript type guard, narrowing the type to
46
+ * `string` within conditional blocks. This enables safe string operations
47
+ * without type assertions.
48
+ *
49
+ * Example usage:
50
+ * ```typescript
51
+ * if (isString(value)) {
52
+ * // TypeScript knows value is a string here
53
+ * const length = value.length;
54
+ * }
55
+ * ```
56
+ *
57
+ * Only returns `true` for primitive string values, not String objects
58
+ * created with `new String()`.
59
+ *
60
+ * @param value - The value to test.
61
+ * @returns `true` if the value is a string, `false` otherwise.
62
+ */
63
+ export function isString(value: unknown): boolean {
64
+ return typeof value === 'string'
65
+ }
66
+
67
+ /**
68
+ * Determines whether a value is a finite number.
69
+ *
70
+ * @remarks
71
+ * This function validates that a value is both a number type and
72
+ * mathematically finite, excluding `NaN`, `Infinity`, and `-Infinity`.
73
+ * This is essential when validating numeric configuration values or
74
+ * processing JSON data where numeric fields must contain valid,
75
+ * computable values.
76
+ *
77
+ * Returns `true` for: all finite numeric values, including `0`, negative
78
+ * numbers, and floating-point numbers.
79
+ *
80
+ * Returns `false` for: `NaN`, `Infinity`, `-Infinity`, strings, objects,
81
+ * arrays, and any non-number types.
82
+ *
83
+ * Only returns `true` for primitive number values, not Number objects
84
+ * created with `new Number()`.
85
+ *
86
+ * The finiteness check is crucial for mathematical operations and ensures
87
+ * that numeric properties in `package.json` or configuration files contain
88
+ * usable values rather than special numeric constants that could cause
89
+ * unexpected behaviour in calculations.
90
+ *
91
+ * @param value - The value to test.
92
+ * @returns `true` if the value is a finite number, `false` otherwise.
93
+ */
94
+ export function isNumber(value: unknown): boolean {
95
+ return typeof value === 'number' && isFinite(value)
96
+ }
97
+
98
+ /**
99
+ * Determines whether a value is a boolean.
100
+ *
101
+ * @remarks
102
+ * Tests for primitive boolean values (`true` or `false`). Useful when
103
+ * validating configuration options or parsing JSON where boolean flags
104
+ * need to be distinguished from truthy/falsy values.
105
+ *
106
+ * Only returns `true` for the primitive boolean values `true` and `false`,
107
+ * not for boolean objects created with `new Boolean()`.
108
+ *
109
+ * Note: This does not check for truthy or falsy values - it only returns
110
+ * `true` for actual boolean primitives. Use standard JavaScript truthiness
111
+ * checks for conditional logic.
112
+ *
113
+ * @param value - The value to test.
114
+ * @returns `true` if the value is a boolean, `false` otherwise.
115
+ */
116
+ export function isBoolean(value: unknown): boolean {
117
+ return typeof value === 'boolean'
118
+ }
119
+
120
+ /**
121
+ * Determines whether a value is a non-array object.
122
+ *
123
+ * @remarks
124
+ * This function distinguishes between objects and arrays, which is crucial
125
+ * when processing JSON structures where both use the object type but require
126
+ * different handling.
127
+ *
128
+ * Returns `true` for: plain objects, class instances, null and other
129
+ * object types.
130
+ *
131
+ * Returns `false` for: arrays, primitives, and functions.
132
+ *
133
+ * Note: Arrays in JavaScript are objects, so this function explicitly
134
+ * excludes them using `Array.isArray()`. Use {@link isJsonObject} for
135
+ * stricter JSON object validation that also excludes `undefined` and `null`.
136
+ *
137
+ * @param value - The value to test.
138
+ * @returns `true` if the value is a non-array object, `false` otherwise.
139
+ */
140
+ export function isObject(value: unknown): boolean {
141
+ return typeof value === 'object' && !Array.isArray(value)
142
+ }
143
+
144
+ // ----------------------------------------------------------------------------
145
+
146
+ /**
147
+ * Determines whether a value is a JSON object.
148
+ *
149
+ * @remarks
150
+ * Validates that a value represents a JSON object, which is stricter than
151
+ * JavaScript's general object type. This is essential when working with
152
+ * parsed JSON data or `package.json` structures.
153
+ *
154
+ * Returns `true` for: plain objects (non-null,
155
+ * non-primitive, non-array values).
156
+ *
157
+ * Returns `false` for: undefined, null, primitives (string, number,
158
+ * boolean, etc.), and arrays.
159
+ *
160
+ * This is the primary validation function for JSON objects in the <b>xpm</b>
161
+ * codebase, used extensively when parsing `package.json` sections like
162
+ * `xpack.properties`, `xpack.buildConfigurations`, etc.
163
+ *
164
+ * @param value - The value to test.
165
+ * @returns `true` if the value is a JSON object, `false` otherwise.
166
+ */
167
+ export function isJsonObject(value: unknown): boolean {
168
+ return value !== undefined && !isPrimitive(value) && !Array.isArray(value)
169
+ }
170
+
171
+ /**
172
+ * Determines whether a value is a JSON array.
173
+ *
174
+ * @remarks
175
+ * Validates that a value represents a JSON array, excluding `undefined`.
176
+ * This ensures the value is a proper array that could have been parsed
177
+ * from JSON.
178
+ *
179
+ * Returns `true` for: any array, including empty arrays.
180
+ *
181
+ * Returns `false` for: undefined, null, objects, and primitives.
182
+ *
183
+ * The undefined check is important for distinguishing between optional
184
+ * properties that are missing (undefined) versus properties that are
185
+ * explicitly empty arrays. This is common in package.json where array
186
+ * fields may be absent or present but empty.
187
+ *
188
+ * @param value - The value to test.
189
+ * @returns `true` if the value is a JSON array, `false` otherwise.
190
+ */
191
+ export function isJsonArray(value: unknown): boolean {
192
+ return value !== undefined && Array.isArray(value)
193
+ }
194
+
195
+ /**
196
+ * Determines whether a value is a non-empty JSON object.
197
+ *
198
+ * @remarks
199
+ * Combines JSON object validation with a non-empty check, ensuring the
200
+ * object has at least one property. This is useful for validating
201
+ * configuration sections that must contain data to be meaningful.
202
+ *
203
+ * Returns `true` for: objects with one or more enumerable properties.
204
+ *
205
+ * Returns `false` for: empty objects `{}`, undefined, null, primitives,
206
+ * arrays, and any non-object types.
207
+ *
208
+ * The enumerable properties check uses `Object.keys()`, which only counts
209
+ * own enumerable string-keyed properties, not inherited properties or
210
+ * symbol-keyed properties.
211
+ *
212
+ * Common use cases include validating `xpack.actions`, `xpack.dependencies`,
213
+ * and other `package.json` sections where an empty object would be
214
+ * meaningless.
215
+ *
216
+ * @param value - The value to test.
217
+ * @returns `true` if the value is a non-empty JSON object, `false` otherwise.
218
+ */
219
+ export function isNonEmptyJsonObject(value: unknown): boolean {
220
+ return isJsonObject(value) && Object.keys(value as object).length > 0
221
+ }
222
+
223
+ // ----------------------------------------------------------------------------
@@ -0,0 +1,172 @@
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
+
16
+ import { Logger } from '@xpack/logger'
17
+
18
+ // ----------------------------------------------------------------------------
19
+
20
+ import { LiquidEngine } from '../classes/liquid-engine.js'
21
+ import { LiquidSubstitutionsVariables } from '../data/substitutions-variables.js'
22
+ import { ConfigurationError } from '../classes/errors.js'
23
+ import { isJsonArray, isString } from './is-something.js'
24
+ import { performSubstitutions } from './perform-substitutions.js'
25
+ import { hasLiquidSyntax } from './utils.js'
26
+ import { getErrorMessage } from './utils.js'
27
+ import { JsonTemplateMatrix } from '../types/json.js'
28
+
29
+ // ============================================================================
30
+
31
+ /**
32
+ * Result of matrix validation and processing.
33
+ *
34
+ * @remarks
35
+ * This interface encapsulates the processed matrix data ready for
36
+ * Cartesian product generation.
37
+ */
38
+ export interface ProcessedMatrix {
39
+ /**
40
+ * Array of matrix parameter names.
41
+ */
42
+ matrixKeys: string[]
43
+
44
+ /**
45
+ * Array of value arrays for each parameter.
46
+ */
47
+ matrixValues: string[][]
48
+ }
49
+
50
+ // ============================================================================
51
+
52
+ /**
53
+ * Validates and processes a matrix object for template expansion.
54
+ *
55
+ * @remarks
56
+ * This function extracts common matrix processing logic used by both
57
+ * actions and build configurations template expansion. It validates the
58
+ * matrix structure, performs Liquid substitutions on matrix values if needed,
59
+ * and prepares the data for Cartesian product generation.
60
+ *
61
+ * Processing steps:
62
+ *
63
+ * <ol>
64
+ * <li>Validates that each matrix property is an array of strings.</li>
65
+ * <li>For each matrix parameter:
66
+ * <ul>
67
+ * <li>Collects the parameter name (key).</li>
68
+ * <li>Joins array values with line breaks for substitution.</li>
69
+ * <li>If values contain Liquid syntax, performs substitutions.</li>
70
+ * <li>Splits the result back into individual values.</li>
71
+ * </ul>
72
+ * </li>
73
+ * <li>Returns processed matrix keys and values ready for combination
74
+ * generation.</li>
75
+ * </ol>
76
+ *
77
+ * Matrix value substitution enables dynamic matrix generation where matrix
78
+ * values themselves can reference other substitution variables, enabling
79
+ * flexible configuration without hardcoding platform-specific or
80
+ * environment-specific values.
81
+ *
82
+ * @param matrix - The matrix object from JSON template.
83
+ * @param templateName - The template name for error messages.
84
+ * @param templateType - The template type ('action' or 'buildConfiguration')
85
+ * for error messages.
86
+ * @param engine - The Liquid engine for substitutions.
87
+ * @param substitutionsVariables - The variables available for substitution.
88
+ * @param log - The logger instance for diagnostics.
89
+ * @returns The processed matrix keys and values.
90
+ *
91
+ * @throws {@link ConfigurationError}
92
+ * If the matrix structure is invalid or substitution fails.
93
+ */
94
+ export async function processMatrixForExpansion({
95
+ matrix,
96
+ templateName,
97
+ templateType,
98
+ engine,
99
+ substitutionsVariables,
100
+ log,
101
+ }: {
102
+ matrix: JsonTemplateMatrix
103
+ templateName: string
104
+ templateType: 'action' | 'buildConfiguration'
105
+ engine: LiquidEngine
106
+ substitutionsVariables: LiquidSubstitutionsVariables
107
+ log: Logger
108
+ }): Promise<ProcessedMatrix> {
109
+ const matrixKeys: string[] = []
110
+ const matrixValues: string[][] = []
111
+
112
+ for (const [matrixKey, matrixValueArray] of Object.entries(matrix)) {
113
+ if (!isJsonArray(matrixValueArray)) {
114
+ throw new ConfigurationError(
115
+ `${templateType} "${templateName}" ` +
116
+ `matrix.${matrixKey} is not an array`
117
+ )
118
+ }
119
+
120
+ if (matrixValueArray.length === 0) {
121
+ throw new ConfigurationError(
122
+ `${templateType} "${templateName}" ` +
123
+ `matrix.${matrixKey} cannot be empty`
124
+ )
125
+ }
126
+
127
+ // Type assertion after validation
128
+ const validatedArray = matrixValueArray as unknown[]
129
+
130
+ for (const matrixValue of validatedArray) {
131
+ if (!isString(matrixValue)) {
132
+ throw new ConfigurationError(
133
+ `${templateType} "${templateName}" ` +
134
+ `matrix.${matrixKey} value is not a string`
135
+ )
136
+ }
137
+ }
138
+
139
+ matrixKeys.push(matrixKey)
140
+ const stringValue = (validatedArray as string[]).join(os.EOL)
141
+
142
+ if (hasLiquidSyntax(stringValue)) {
143
+ let substitutedValue
144
+ try {
145
+ substitutedValue = await performSubstitutions({
146
+ input: stringValue,
147
+ engine,
148
+ substitutionsVariables,
149
+ log,
150
+ })
151
+ } catch (error) {
152
+ const message =
153
+ getErrorMessage(error) +
154
+ ` in ${templateType} "${templateName}" ` +
155
+ `matrix substitution`
156
+ throw new ConfigurationError(message)
157
+ }
158
+
159
+ // Split back into array, removing trailing newline if present
160
+ matrixValues.push(
161
+ substitutedValue.replace(new RegExp(os.EOL + '$'), '').split(os.EOL)
162
+ )
163
+ } else {
164
+ // Type assertion safe here after validation
165
+ matrixValues.push(validatedArray as string[])
166
+ }
167
+ }
168
+
169
+ return { matrixKeys, matrixValues }
170
+ }
171
+
172
+ // ----------------------------------------------------------------------------