@xpack/xpm-lib 3.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 (94) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +223 -0
  3. package/dist/index.d.ts +16 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +30 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/lib/chmod-recursive.d.ts +7 -0
  8. package/dist/lib/chmod-recursive.d.ts.map +1 -0
  9. package/dist/lib/chmod-recursive.js +81 -0
  10. package/dist/lib/chmod-recursive.js.map +1 -0
  11. package/dist/lib/errors.d.ts +11 -0
  12. package/dist/lib/errors.d.ts.map +1 -0
  13. package/dist/lib/errors.js +26 -0
  14. package/dist/lib/errors.js.map +1 -0
  15. package/dist/lib/functions/chmod-recursive.d.ts +7 -0
  16. package/dist/lib/functions/chmod-recursive.d.ts.map +1 -0
  17. package/dist/lib/functions/chmod-recursive.js +81 -0
  18. package/dist/lib/functions/chmod-recursive.js.map +1 -0
  19. package/dist/lib/functions/perform-substitutions.d.ts +20 -0
  20. package/dist/lib/functions/perform-substitutions.d.ts.map +1 -0
  21. package/dist/lib/functions/perform-substitutions.js +85 -0
  22. package/dist/lib/functions/perform-substitutions.js.map +1 -0
  23. package/dist/lib/functions/utils.d.ts +30 -0
  24. package/dist/lib/functions/utils.d.ts.map +1 -0
  25. package/dist/lib/functions/utils.js +70 -0
  26. package/dist/lib/functions/utils.js.map +1 -0
  27. package/dist/lib/init-template-base.d.ts +46 -0
  28. package/dist/lib/init-template-base.d.ts.map +1 -0
  29. package/dist/lib/init-template-base.js +275 -0
  30. package/dist/lib/init-template-base.js.map +1 -0
  31. package/dist/lib/liquid-actions.d.ts +32 -0
  32. package/dist/lib/liquid-actions.d.ts.map +1 -0
  33. package/dist/lib/liquid-actions.js +113 -0
  34. package/dist/lib/liquid-actions.js.map +1 -0
  35. package/dist/lib/liquid-build-configurations.d.ts +49 -0
  36. package/dist/lib/liquid-build-configurations.d.ts.map +1 -0
  37. package/dist/lib/liquid-build-configurations.js +267 -0
  38. package/dist/lib/liquid-build-configurations.js.map +1 -0
  39. package/dist/lib/liquid-drop.d.ts +13 -0
  40. package/dist/lib/liquid-drop.d.ts.map +1 -0
  41. package/dist/lib/liquid-drop.js +56 -0
  42. package/dist/lib/liquid-drop.js.map +1 -0
  43. package/dist/lib/liquid-engine.d.ts +5 -0
  44. package/dist/lib/liquid-engine.d.ts.map +1 -0
  45. package/dist/lib/liquid-engine.js +85 -0
  46. package/dist/lib/liquid-engine.js.map +1 -0
  47. package/dist/lib/liquid-package.d.ts +17 -0
  48. package/dist/lib/liquid-package.d.ts.map +1 -0
  49. package/dist/lib/liquid-package.js +70 -0
  50. package/dist/lib/liquid-package.js.map +1 -0
  51. package/dist/lib/package.d.ts +66 -0
  52. package/dist/lib/package.d.ts.map +1 -0
  53. package/dist/lib/package.js +700 -0
  54. package/dist/lib/package.js.map +1 -0
  55. package/dist/lib/perform-substitutions.d.ts +20 -0
  56. package/dist/lib/perform-substitutions.d.ts.map +1 -0
  57. package/dist/lib/perform-substitutions.js +85 -0
  58. package/dist/lib/perform-substitutions.js.map +1 -0
  59. package/dist/lib/policies.d.ts +13 -0
  60. package/dist/lib/policies.d.ts.map +1 -0
  61. package/dist/lib/policies.js +31 -0
  62. package/dist/lib/policies.js.map +1 -0
  63. package/dist/lib/substitutions-variables.d.ts +117 -0
  64. package/dist/lib/substitutions-variables.d.ts.map +1 -0
  65. package/dist/lib/substitutions-variables.js +51 -0
  66. package/dist/lib/substitutions-variables.js.map +1 -0
  67. package/dist/lib/types.d.ts +70 -0
  68. package/dist/lib/types.d.ts.map +1 -0
  69. package/dist/lib/types.js +13 -0
  70. package/dist/lib/types.js.map +1 -0
  71. package/dist/lib/utils.d.ts +30 -0
  72. package/dist/lib/utils.d.ts.map +1 -0
  73. package/dist/lib/utils.js +70 -0
  74. package/dist/lib/utils.js.map +1 -0
  75. package/dist/tsconfig.tsbuildinfo +1 -0
  76. package/package.json +102 -0
  77. package/src/README.md +10 -0
  78. package/src/index.ts +35 -0
  79. package/src/lib/errors.ts +29 -0
  80. package/src/lib/functions/chmod-recursive.ts +103 -0
  81. package/src/lib/functions/perform-substitutions.ts +116 -0
  82. package/src/lib/functions/utils.ts +88 -0
  83. package/src/lib/init-template-base.ts +401 -0
  84. package/src/lib/liquid-actions.ts +179 -0
  85. package/src/lib/liquid-build-configurations.ts +410 -0
  86. package/src/lib/liquid-drop.ts +99 -0
  87. package/src/lib/liquid-engine.ts +135 -0
  88. package/src/lib/liquid-package.ts +108 -0
  89. package/src/lib/package.ts +946 -0
  90. package/src/lib/policies.ts +49 -0
  91. package/src/lib/substitutions-variables.ts +177 -0
  92. package/src/lib/types.ts +109 -0
  93. package/src/package.json +3 -0
  94. package/src/tsconfig.json +10 -0
@@ -0,0 +1,410 @@
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
+ /* eslint max-len: [ "error", 80, { "ignoreUrls": true } ] */
13
+
14
+ // ----------------------------------------------------------------------------
15
+
16
+ import assert from 'node:assert'
17
+ import path from 'node:path'
18
+
19
+ import { Logger } from '@xpack/logger'
20
+
21
+ import { XpmLiquidEngine } from './liquid-engine.js'
22
+ import {
23
+ XpmLiquidSubstitutionsVariables,
24
+ XpmLiquidSubstitutionsStrings,
25
+ } from './substitutions-variables.js'
26
+ import { buildFolderRelativePathPropertyName } from './liquid-package.js'
27
+ import {
28
+ JsonBuildConfiguration,
29
+ JsonBuildConfigurations,
30
+ JsonDependencies,
31
+ } from './types.js'
32
+ import { performSubstitutions } from './functions/perform-substitutions.js'
33
+ import { XpmLiquidActions } from './liquid-actions.js'
34
+ import { isString } from './functions/utils.js'
35
+ import { filterPath } from './functions/utils.js'
36
+
37
+ // ----------------------------------------------------------------------------
38
+
39
+ // A collection of build configurations.
40
+ export class XpmLiquidBuildConfigurations {
41
+ // --------------------------------------------------------------------------
42
+ // Members.
43
+
44
+ readonly log: Logger
45
+ readonly engine: XpmLiquidEngine
46
+ readonly substitutionsVariables: XpmLiquidSubstitutionsVariables
47
+ readonly jsonBuildConfigurations: JsonBuildConfigurations
48
+
49
+ readonly #buildConfigurationsMap: Map<
50
+ string,
51
+ XpmLiquidBuildConfiguration | undefined
52
+ >
53
+ readonly #jsonBuildConfigurationsNamesMap: Map<string, string>
54
+
55
+ #isInitialised = false
56
+
57
+ // --------------------------------------------------------------------------
58
+ // Constructor and async initialiser.
59
+
60
+ constructor({
61
+ log,
62
+ engine,
63
+ substitutionsVariables,
64
+ jsonBuildConfigurations,
65
+ }: {
66
+ log: Logger
67
+ engine: XpmLiquidEngine
68
+ substitutionsVariables: XpmLiquidSubstitutionsVariables
69
+ jsonBuildConfigurations: JsonBuildConfigurations | undefined
70
+ }) {
71
+ log.trace(`${XpmLiquidBuildConfigurations.name}()`)
72
+
73
+ this.log = log
74
+ this.engine = engine
75
+ this.substitutionsVariables = substitutionsVariables
76
+ this.jsonBuildConfigurations = jsonBuildConfigurations ?? {}
77
+
78
+ // Possibly empty if there are no build configurations.
79
+ this.#buildConfigurationsMap = new Map<
80
+ string,
81
+ XpmLiquidBuildConfiguration | undefined
82
+ >()
83
+ this.#jsonBuildConfigurationsNamesMap = new Map<string, string>()
84
+ // log.trace('substitutionsVariables => ', this.substitutionsVariables)
85
+ }
86
+
87
+ // eslint-disable-next-line @typescript-eslint/require-await
88
+ async initialise(): Promise<void> {
89
+ if (this.#isInitialised) {
90
+ return
91
+ }
92
+
93
+ for (const buildConfigurationName of Object.keys(
94
+ this.jsonBuildConfigurations
95
+ )) {
96
+ if (buildConfigurationName.includes('{{')) {
97
+ // TODO: expand templates and generate multiple build configurations.
98
+ } else {
99
+ this.#buildConfigurationsMap.set(buildConfigurationName, undefined)
100
+ this.#jsonBuildConfigurationsNamesMap.set(
101
+ buildConfigurationName,
102
+ buildConfigurationName
103
+ )
104
+ }
105
+ }
106
+
107
+ this.log.trace(
108
+ `${XpmLiquidBuildConfigurations.name}.initialise() =>`,
109
+ Array.from(this.#buildConfigurationsMap.keys())
110
+ )
111
+
112
+ this.#isInitialised = true
113
+ }
114
+
115
+ // --------------------------------------------------------------------------
116
+ // Methods.
117
+
118
+ empty(): boolean {
119
+ return this.#buildConfigurationsMap.size === 0
120
+ }
121
+
122
+ names(): string[] {
123
+ const buildConfigurationsNames = Array.from(
124
+ this.#buildConfigurationsMap.keys()
125
+ )
126
+
127
+ this.log.trace(
128
+ `${XpmLiquidBuildConfigurations.name}.names() =>`,
129
+ buildConfigurationsNames
130
+ )
131
+ return buildConfigurationsNames
132
+ }
133
+
134
+ hasJson(buildConfigurationName: string): boolean {
135
+ return this.#jsonBuildConfigurationsNamesMap.has(buildConfigurationName)
136
+ }
137
+
138
+ getJson(buildConfigurationName: string): JsonBuildConfiguration {
139
+ return this.jsonBuildConfigurations[
140
+ this.getJsonName(buildConfigurationName)
141
+ ]
142
+ }
143
+
144
+ isHidden(buildConfigurationName: string): boolean {
145
+ return (
146
+ this.jsonBuildConfigurations[this.getJsonName(buildConfigurationName)]
147
+ .hidden ?? false
148
+ )
149
+ }
150
+
151
+ has(buildConfigurationName: string): boolean {
152
+ return this.#buildConfigurationsMap.has(buildConfigurationName)
153
+ }
154
+
155
+ async get(
156
+ buildConfigurationName: string
157
+ ): Promise<XpmLiquidBuildConfiguration> {
158
+ let buildConfiguration = this.#buildConfigurationsMap.get(
159
+ buildConfigurationName
160
+ )
161
+ if (buildConfiguration === undefined) {
162
+ const jsonBuildConfigurationName: string =
163
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
164
+ this.#jsonBuildConfigurationsNamesMap.get(buildConfigurationName)!
165
+
166
+ buildConfiguration = new XpmLiquidBuildConfiguration({
167
+ buildConfigurationName,
168
+ jsonBuildConfigurationName,
169
+ parentBuildConfigurations: this,
170
+ })
171
+ await buildConfiguration.initialise()
172
+ this.#buildConfigurationsMap.set(
173
+ buildConfigurationName,
174
+ buildConfiguration
175
+ )
176
+ }
177
+
178
+ return buildConfiguration
179
+ }
180
+
181
+ getJsonName(buildConfigurationName: string): string {
182
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
183
+ return this.#jsonBuildConfigurationsNamesMap.get(buildConfigurationName)!
184
+ }
185
+ }
186
+
187
+ // ----------------------------------------------------------------------------
188
+
189
+ // An individual build configuration.
190
+ export class XpmLiquidBuildConfiguration {
191
+ // --------------------------------------------------------------------------
192
+ // Members.
193
+
194
+ readonly hidden: boolean
195
+
196
+ // The actual (un-substituted) name from package.json.
197
+ readonly jsonBuildConfigurationName: string
198
+ // Points to the actual buildConfiguration in package.json.
199
+ readonly jsonBuildConfiguration: JsonBuildConfiguration
200
+
201
+ substitutionsVariables: XpmLiquidSubstitutionsVariables
202
+
203
+ properties: XpmLiquidSubstitutionsStrings = {}
204
+
205
+ jsonDependencies: JsonDependencies = {}
206
+ jsonDevDependencies: JsonDependencies = {}
207
+
208
+ // For templates, the actual values.
209
+ matrixParameters?: XpmLiquidSubstitutionsStrings
210
+
211
+ // The name after performing the substitutions.
212
+ readonly #buildConfigurationName: string
213
+
214
+ readonly #parentBuildConfigurations: XpmLiquidBuildConfigurations
215
+
216
+ #actions: XpmLiquidActions | undefined
217
+
218
+ #buildFolderRelativePath?: string
219
+
220
+ #isInitialised = false
221
+
222
+ // --------------------------------------------------------------------------
223
+ // Constructor and async initialiser.
224
+
225
+ constructor({
226
+ buildConfigurationName, // The Liquid-processed name.
227
+ jsonBuildConfigurationName, // The raw name from package.json.
228
+ parentBuildConfigurations,
229
+ }: {
230
+ buildConfigurationName: string
231
+ jsonBuildConfigurationName: string
232
+ parentBuildConfigurations: XpmLiquidBuildConfigurations
233
+ }) {
234
+ parentBuildConfigurations.log.trace(
235
+ `${XpmLiquidBuildConfiguration.name}(${buildConfigurationName})`
236
+ )
237
+
238
+ this.#buildConfigurationName = buildConfigurationName
239
+ this.jsonBuildConfigurationName = jsonBuildConfigurationName
240
+ this.#parentBuildConfigurations = parentBuildConfigurations
241
+
242
+ this.jsonBuildConfiguration =
243
+ parentBuildConfigurations.jsonBuildConfigurations[
244
+ jsonBuildConfigurationName
245
+ ] ?? {}
246
+
247
+ this.substitutionsVariables = {
248
+ ...this.#parentBuildConfigurations.substitutionsVariables,
249
+ }
250
+
251
+ this.hidden = this.jsonBuildConfiguration.hidden ?? false
252
+
253
+ // The rest of the initialisation is done in the async initialiser.
254
+ }
255
+
256
+ async initialise(): Promise<void> {
257
+ if (this.#isInitialised) {
258
+ return
259
+ }
260
+
261
+ const log = this.#parentBuildConfigurations.log
262
+ const jsonBuildConfiguration = this.jsonBuildConfiguration
263
+
264
+ // TODO: add matrixParameters
265
+
266
+ // Process both the new 'inherits' and the deprecated 'inherit'.
267
+ let inherits: string[] = []
268
+ if (isString(jsonBuildConfiguration.inherits)) {
269
+ inherits = [jsonBuildConfiguration.inherits]
270
+ } else if (Array.isArray(jsonBuildConfiguration.inherits)) {
271
+ inherits = jsonBuildConfiguration.inherit as string[]
272
+ } else if (isString(jsonBuildConfiguration.inherit)) {
273
+ inherits = [jsonBuildConfiguration.inherit]
274
+ } else if (Array.isArray(jsonBuildConfiguration.inherit)) {
275
+ inherits = jsonBuildConfiguration.inherit as string[]
276
+ }
277
+
278
+ // Add inherited configuration properties.
279
+ // TODO: detect circular references.
280
+ for (const inheritedBuildConfigurationName of inherits) {
281
+ if (
282
+ this.#parentBuildConfigurations.hasJson(inheritedBuildConfigurationName)
283
+ ) {
284
+ const inheritedBuildConfiguration =
285
+ await this.#parentBuildConfigurations.get(
286
+ inheritedBuildConfigurationName
287
+ )
288
+
289
+ this.properties = {
290
+ ...this.properties,
291
+ ...inheritedBuildConfiguration.properties,
292
+ }
293
+
294
+ this.jsonDependencies = {
295
+ ...this.jsonDependencies,
296
+ ...inheritedBuildConfiguration.jsonDependencies,
297
+ }
298
+ this.jsonDevDependencies = {
299
+ ...this.jsonDevDependencies,
300
+ ...inheritedBuildConfiguration.jsonDevDependencies,
301
+ }
302
+ } else {
303
+ log.warn(
304
+ 'buildConfiguration',
305
+ this.#buildConfigurationName,
306
+ 'inherits from missing',
307
+ inheritedBuildConfigurationName,
308
+ '(ignored)'
309
+ )
310
+ }
311
+ }
312
+
313
+ this.properties = {
314
+ ...this.properties,
315
+ ...jsonBuildConfiguration.properties,
316
+ }
317
+
318
+ this.jsonDependencies = {
319
+ ...this.jsonDependencies,
320
+ ...jsonBuildConfiguration.dependencies,
321
+ }
322
+ this.jsonDevDependencies = {
323
+ ...this.jsonDevDependencies,
324
+ ...jsonBuildConfiguration.devDependencies,
325
+ }
326
+
327
+ this.substitutionsVariables = {
328
+ ...this.#parentBuildConfigurations.substitutionsVariables,
329
+ properties: {
330
+ ...this.substitutionsVariables.properties,
331
+ ...this.properties,
332
+ },
333
+ configuration: {
334
+ ...jsonBuildConfiguration,
335
+ name: this.#buildConfigurationName,
336
+ },
337
+ }
338
+
339
+ // Add the buildFolderRelativePath property.
340
+ // Note: the async initialiser was needed due to this async operation.
341
+ const properties = this.substitutionsVariables.properties
342
+ properties.buildFolderRelativePath = await this.getBuildFolderRelativePath()
343
+
344
+ // Please note that substitutionsVariables is not fully set at this point;
345
+ // it will be in the async initialiser after the constructor returns.
346
+ this.#actions = new XpmLiquidActions({
347
+ log: this.#parentBuildConfigurations.log,
348
+ engine: this.#parentBuildConfigurations.engine,
349
+ substitutionsVariables: this.substitutionsVariables,
350
+ jsonActions: this.jsonBuildConfiguration.actions,
351
+ })
352
+ // Note: this must be done manually by the application.
353
+ // await this.#actions.initialise()
354
+
355
+ log.trace(
356
+ `${XpmLiquidBuildConfiguration.name}.initialise() =>`,
357
+ this.#buildConfigurationName
358
+ )
359
+ log.trace('properties => ', this.properties)
360
+ log.trace('dependencies => ', this.jsonDependencies)
361
+ log.trace('devDependencies => ', this.jsonDevDependencies)
362
+ // log.trace('substitutionsVariables => ', this.substitutionsVariables)
363
+
364
+ this.#isInitialised = true
365
+ }
366
+
367
+ // --------------------------------------------------------------------------
368
+ // Methods.
369
+
370
+ get actions(): XpmLiquidActions {
371
+ assert(this.#actions !== undefined)
372
+ return this.#actions
373
+ }
374
+
375
+ async getBuildFolderRelativePath(): Promise<string> {
376
+ this.#buildFolderRelativePath ??= await this.#getBuildFolderRelativePath()
377
+ return this.#buildFolderRelativePath
378
+ }
379
+
380
+ async #getBuildFolderRelativePath(): Promise<string> {
381
+ const log = this.#parentBuildConfigurations.log
382
+
383
+ let folderPath: string
384
+ if (
385
+ buildFolderRelativePathPropertyName in
386
+ this.substitutionsVariables.properties
387
+ ) {
388
+ folderPath = this.substitutionsVariables.properties[
389
+ buildFolderRelativePathPropertyName
390
+ ] as string
391
+ if (folderPath !== '') {
392
+ try {
393
+ return await performSubstitutions({
394
+ log,
395
+ engine: this.#parentBuildConfigurations.engine,
396
+ input: folderPath,
397
+ substitutionsVariables: this.substitutionsVariables,
398
+ })
399
+ } catch (err) {
400
+ log.trace(err)
401
+ }
402
+ }
403
+ }
404
+
405
+ // Provide a default value, based on the name.
406
+ return path.join('build', filterPath(this.#buildConfigurationName))
407
+ }
408
+ }
409
+
410
+ // ----------------------------------------------------------------------------
@@ -0,0 +1,99 @@
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
+ /* eslint max-len: [ "error", 80, { "ignoreUrls": true } ] */
13
+
14
+ // ----------------------------------------------------------------------------
15
+
16
+ // https://www.npmjs.com/package/liquidjs
17
+ import { Liquid, Context, Drop } from 'liquidjs'
18
+
19
+ // https://www.npmjs.com/package/@xpack/logger
20
+ import { Logger } from '@xpack/logger'
21
+
22
+ import { XpmLiquidSubstitutionsStrings } from './substitutions-variables.js'
23
+
24
+ // ----------------------------------------------------------------------------
25
+
26
+ // https://liquidjs.com/
27
+
28
+ export class XpmLiquidPropertiesDrop extends Drop {
29
+ // --------------------------------------------------------------------------
30
+ // Members.
31
+
32
+ readonly #log: Logger
33
+ readonly #properties: XpmLiquidSubstitutionsStrings
34
+ readonly #engine: Liquid
35
+
36
+ // --------------------------------------------------------------------------
37
+ // Constructor.
38
+
39
+ constructor({
40
+ log,
41
+ engine,
42
+ properties,
43
+ }: {
44
+ log: Logger
45
+ engine: Liquid
46
+ properties: XpmLiquidSubstitutionsStrings
47
+ }) {
48
+ super()
49
+
50
+ log.trace(`${XpmLiquidPropertiesDrop.name}()`)
51
+
52
+ this.#log = log
53
+ this.#engine = engine
54
+ this.#properties = properties
55
+ }
56
+
57
+ // --------------------------------------------------------------------------
58
+ // Methods.
59
+
60
+ override async liquidMethodMissing(
61
+ key: string,
62
+ context: Context
63
+ ): Promise<string | string[]> {
64
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
65
+ if (this.#properties[key] === undefined) {
66
+ throw new Error(`properties have no ${key} key`)
67
+ }
68
+
69
+ const log = this.#log
70
+
71
+ const value = this.#properties[key] ?? ''
72
+ log.trace(
73
+ `${XpmLiquidPropertiesDrop.name}.liquidMethodMissing('${key}') value = |`,
74
+ value,
75
+ '|'
76
+ )
77
+
78
+ let result: string | string[]
79
+
80
+ const valueString = Array.isArray(value) ? value.join('') : value
81
+ if (valueString.includes('{{') || valueString.includes('{%')) {
82
+ result = (await this.#engine.parseAndRender(
83
+ valueString,
84
+ context
85
+ )) as string
86
+ } else {
87
+ result = value
88
+ }
89
+ log.trace(
90
+ `${XpmLiquidPropertiesDrop.name}.liquidMethodMissing('${key}') ` +
91
+ `result = |`,
92
+ result,
93
+ '|'
94
+ )
95
+ return result
96
+ }
97
+ }
98
+
99
+ // ----------------------------------------------------------------------------
@@ -0,0 +1,135 @@
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
+ /* eslint max-len: [ "error", 80, { "ignoreUrls": true } ] */
13
+
14
+ // ----------------------------------------------------------------------------
15
+
16
+ import * as os from 'node:os'
17
+ import * as path from 'node:path'
18
+ import * as util from 'node:util'
19
+
20
+ // https://www.npmjs.com/package/liquidjs
21
+ import { Liquid } from 'liquidjs'
22
+
23
+ // ----------------------------------------------------------------------------
24
+
25
+ export class XpmLiquidEngine extends Liquid {
26
+ // --------------------------------------------------------------------------
27
+ // Members.
28
+
29
+ // --------------------------------------------------------------------------
30
+ // Constructor.
31
+
32
+ constructor() {
33
+ super({
34
+ strictFilters: true,
35
+ strictVariables: true,
36
+ trimTagLeft: false,
37
+ trimTagRight: false,
38
+ trimOutputLeft: false,
39
+ trimOutputRight: false,
40
+ greedy: false,
41
+ lenientIf: true,
42
+ })
43
+
44
+ // https://liquidjs.com/api/classes/liquid_.liquid.html#registerFilter
45
+ // https://nodejs.org/dist/latest-v16.x/docs/api/path.html
46
+
47
+ // Add the main path manipulation functions.
48
+ this.registerFilter('path_basename', (p: string, ...arg) =>
49
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
50
+ path.basename(p, ...arg)
51
+ )
52
+
53
+ this.registerFilter('path_dirname', (p: string) => path.dirname(p))
54
+
55
+ this.registerFilter('path_normalize', (p: string) => path.normalize(p))
56
+
57
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
58
+ this.registerFilter('path_join', (p, ...args) => path.join(p, ...args))
59
+
60
+ this.registerFilter('path_relative', (from: string, to: string) =>
61
+ path.relative(from, to)
62
+ )
63
+
64
+ this.registerFilter('path_posix_basename', (p: string, ...arg) =>
65
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
66
+ path.posix.basename(p, ...arg)
67
+ )
68
+
69
+ this.registerFilter('path_posix_dirname', (p: string) =>
70
+ path.posix.dirname(p)
71
+ )
72
+
73
+ this.registerFilter('path_posix_normalize', (p: string) =>
74
+ path.posix.normalize(p)
75
+ )
76
+
77
+ this.registerFilter('path_posix_join', (p, ...args) =>
78
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
79
+ path.posix.join(p, ...args)
80
+ )
81
+
82
+ this.registerFilter('path_posix_relative', (from: string, to: string) =>
83
+ path.posix.relative(from, to)
84
+ )
85
+
86
+ this.registerFilter('path_win32_basename', (p: string, ...arg) =>
87
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
88
+ path.win32.basename(p, ...arg)
89
+ )
90
+
91
+ this.registerFilter('path_win32_dirname', (p: string) =>
92
+ path.win32.dirname(p)
93
+ )
94
+
95
+ this.registerFilter('path_win32_normalize', (p: string) =>
96
+ path.win32.normalize(p)
97
+ )
98
+
99
+ this.registerFilter('path_win32_join', (p, ...args) =>
100
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
101
+ path.win32.join(p, ...args)
102
+ )
103
+
104
+ this.registerFilter('path_win32_relative', (from: string, to: string) =>
105
+ path.win32.relative(from, to)
106
+ )
107
+
108
+ // https://nodejs.org/dist/latest-v16.x/docs/api/util.html
109
+
110
+ this.registerFilter('util_format', (format, ...args) => {
111
+ // console.log([...args])
112
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
113
+ return util.format(format, ...args)
114
+ })
115
+
116
+ // Custom action.
117
+ this.registerFilter(
118
+ 'to_filename',
119
+ // Replace non alphanumeric chars with dashes to make the paths
120
+ // comply with filesystem names.
121
+ (input: string): string => {
122
+ /* c8 ignore start */ /* istanbul ignore next */
123
+ const fixed =
124
+ os.platform() === 'win32'
125
+ ? input.replace(/[^a-zA-Z0-9\\:]+/g, '-')
126
+ : input.replace(/[^a-zA-Z0-9/]+/g, '-')
127
+ /* c8 ignore stop */
128
+
129
+ return fixed.replace(/--/g, '-')
130
+ }
131
+ )
132
+ }
133
+ }
134
+
135
+ // ----------------------------------------------------------------------------