@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,401 @@
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 util from 'node:util'
17
+ import * as readline from 'node:readline/promises'
18
+ import * as path from 'node:path'
19
+ import * as fs from 'node:fs/promises'
20
+
21
+ // https://www.npmjs.com/package/make-dir
22
+ import { makeDirectory } from 'make-dir'
23
+
24
+ // https://www.npmjs.com/package/cp-file
25
+ import { copyFile } from 'cp-file'
26
+
27
+ // https://www.npmjs.com/package/liquidjs
28
+ import { Liquid } from 'liquidjs'
29
+
30
+ import { Logger } from '@xpack/logger'
31
+
32
+ import { XpmContext } from './types.js'
33
+ import { XpmOutputError, XpmSyntaxError } from './errors.js'
34
+ import assert from 'node:assert'
35
+
36
+ // ----------------------------------------------------------------------------
37
+
38
+ export type XpmInitTemplatePropertiesDefinitions = Record<
39
+ string,
40
+ XpmInitTemplatePropertiesDefinition
41
+ >
42
+
43
+ export interface XpmInitTemplatePropertiesDefinition {
44
+ label: string
45
+ description: string
46
+ type: 'select' | 'string' | 'number' | 'boolean'
47
+ items: Record<string, string | XpmInitTemplateItemValue>
48
+ isMandatory?: boolean
49
+ default?: string | number | boolean
50
+ }
51
+
52
+ export type XpmInitTemplatePlatform =
53
+ | 'linux'
54
+ | 'linux-x64'
55
+ | 'linux-arm64'
56
+ | 'win32'
57
+ | 'darwin'
58
+ | 'darwin-x64'
59
+ | 'darwin-arm64'
60
+
61
+ export interface XpmInitTemplateItemValue {
62
+ // 'linux', 'win32', 'darwin'
63
+ platforms: XpmInitTemplatePlatform[]
64
+ message: string
65
+ }
66
+
67
+ export interface XpmInitTemplateSubstitutionsVariables {
68
+ properties: Record<string, string | boolean | number>
69
+ [key: string]: unknown
70
+ }
71
+
72
+ // ----------------------------------------------------------------------------
73
+
74
+ export abstract class XpmInitTemplateBase {
75
+ // --------------------------------------------------------------------------
76
+ // Members.
77
+
78
+ context: XpmContext
79
+ log: Logger
80
+
81
+ propertiesDefinitions: XpmInitTemplatePropertiesDefinitions = {}
82
+ __dirname: string
83
+ templatesPath: string
84
+ engine: Liquid
85
+ substitutionsVariables?: XpmInitTemplateSubstitutionsVariables
86
+
87
+ // --------------------------------------------------------------------------
88
+ // Constructor.
89
+
90
+ constructor({
91
+ context,
92
+ propertiesDefinitions,
93
+ __dirname,
94
+ templatesPath,
95
+ }: {
96
+ context: XpmContext
97
+ propertiesDefinitions: XpmInitTemplatePropertiesDefinitions
98
+ __dirname: string
99
+ templatesPath: string
100
+ }) {
101
+ this.context = context
102
+ this.log = context.log
103
+ this.propertiesDefinitions = propertiesDefinitions
104
+ this.__dirname = __dirname
105
+ this.templatesPath = templatesPath
106
+
107
+ // https://liquidjs.com
108
+ this.engine = new Liquid({
109
+ root: this.templatesPath,
110
+ cache: false,
111
+ strictFilters: true, // default: false
112
+ strictVariables: true, // default: false
113
+ trimTagRight: false, // default: false
114
+ trimTagLeft: false, // default: false
115
+ greedy: false,
116
+ })
117
+ }
118
+
119
+ async run(): Promise<void> {
120
+ const log = this.log
121
+ log.trace(`${this.constructor.name}.run()`)
122
+
123
+ log.info()
124
+
125
+ const context = this.context
126
+ const config = context.config
127
+
128
+ assert(config.properties)
129
+
130
+ let isError = false
131
+ for (const [key, val] of Object.entries(config.properties)) {
132
+ try {
133
+ config.properties[key] = this.validateValue(key, val as string)
134
+ } catch (err) {
135
+ if (err instanceof Error) {
136
+ log.error(err.message)
137
+ }
138
+ isError = true
139
+ }
140
+ }
141
+ if (isError) {
142
+ throw new XpmSyntaxError()
143
+ }
144
+
145
+ // Properties set by `--property name=value` are in `config.properties`.
146
+
147
+ // If there is at least one mandatory property without an explicit value,
148
+ // enter the interactive mode and ask for the missing values.
149
+
150
+ const mustAsk = Object.keys(this.propertiesDefinitions).some((key) => {
151
+ return (
152
+ this.propertiesDefinitions[key].isMandatory && !config.properties?.[key]
153
+ )
154
+ })
155
+
156
+ let isInteractive
157
+ if (mustAsk) {
158
+ // Need to ask for more values.
159
+ if (!(process.stdin.isTTY && process.stdout.isTTY)) {
160
+ throw new XpmSyntaxError('Interactive mode not possible without a TTY.')
161
+ }
162
+
163
+ await this.askForMoreValues()
164
+ log.trace(util.inspect(config.properties))
165
+
166
+ // Reset start time to skip interactive time.
167
+ context.startTime = Date.now()
168
+ isInteractive = true
169
+ } else {
170
+ // Properties without explicit values get their defaults.
171
+ Object.entries(this.propertiesDefinitions).forEach(([key, val]) => {
172
+ assert(config.properties)
173
+ if (!config.properties[key] && val.default) {
174
+ config.properties[key] = val.default
175
+ }
176
+ })
177
+ isInteractive = false
178
+ }
179
+
180
+ const currentTime = new Date()
181
+
182
+ const substitutionsVariables: XpmInitTemplateSubstitutionsVariables = {
183
+ // Spread all config properties.
184
+ ...config.properties,
185
+ // Also pass the properties grouped.
186
+ properties: config.properties,
187
+ propertiesNames: Object.keys(config.properties),
188
+ projectName: config.projectName,
189
+ year: currentTime.getFullYear().toString(),
190
+ }
191
+
192
+ this.substitutionsVariables = substitutionsVariables
193
+ await this.generate(isInteractive)
194
+ }
195
+
196
+ abstract generate(isInteractive: boolean): Promise<void>
197
+
198
+ validateValue(name: string, value: string): string | boolean | number {
199
+ const propDef = this.propertiesDefinitions[name]
200
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
201
+ if (propDef === undefined) {
202
+ throw new Error(`Unsupported property '${name}'`)
203
+ }
204
+ if (propDef.type === 'select') {
205
+ if (propDef.items[value]) {
206
+ if (typeof propDef.items[value] === 'string') {
207
+ return value
208
+ } else if (
209
+ typeof propDef.items[value] === 'object' &&
210
+ this.isPlatformSupported(propDef.items[value].platforms)
211
+ ) {
212
+ return value
213
+ }
214
+ }
215
+ } else if (propDef.type === 'boolean') {
216
+ if (value === 'true') {
217
+ return true
218
+ } else if (value === 'false') {
219
+ return false
220
+ }
221
+ } else if (propDef.type === 'number') {
222
+ return Number(value)
223
+ }
224
+
225
+ if (value === '' && propDef.default !== undefined) {
226
+ return propDef.default
227
+ }
228
+
229
+ throw new Error(`Unsupported value '${value}' for property '${name}'`)
230
+ }
231
+
232
+ async askForMoreValues() {
233
+ const context = this.context
234
+ const config = context.config
235
+
236
+ assert(config.properties)
237
+
238
+ const rl = readline.createInterface({
239
+ input: process.stdin,
240
+ output: process.stdout,
241
+ })
242
+
243
+ for (const name of Object.keys(this.propertiesDefinitions)) {
244
+ if (config.properties[name]) {
245
+ continue
246
+ }
247
+ const definition = this.propertiesDefinitions[name]
248
+ let prompt = `${definition.label}?`
249
+ if (definition.type === 'select') {
250
+ prompt += ' ('
251
+ const validItems = []
252
+ for (const [ikey, ival] of Object.entries(definition.items)) {
253
+ if (typeof ival === 'string') {
254
+ validItems.push(ikey)
255
+ } else if (
256
+ typeof ival === 'object' &&
257
+ this.isPlatformSupported(ival.platforms)
258
+ ) {
259
+ validItems.push(ikey)
260
+ }
261
+ }
262
+ prompt += validItems.join(', ')
263
+ prompt += ', ?)'
264
+ } else if (definition.type === 'boolean') {
265
+ prompt += ' (true, false, ?)'
266
+ }
267
+ if (definition.default !== undefined) {
268
+ prompt += ` [${String(definition.default)}]`
269
+ }
270
+ prompt += ': '
271
+
272
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
273
+ while (true) {
274
+ const answer = (await rl.question(prompt)).trim()
275
+ try {
276
+ const value = this.validateValue(name, answer)
277
+ config.properties[name] = value
278
+ break
279
+ } catch (err) {
280
+ if (err instanceof Error) {
281
+ this.log.trace(err.message)
282
+ }
283
+ console.log(definition.description)
284
+ if (definition.type === 'select') {
285
+ for (const [ikey, ival] of Object.entries(definition.items)) {
286
+ if (typeof ival === 'string') {
287
+ console.log(`- ${ikey}: ${ival}`)
288
+ } else if (
289
+ typeof ival === 'object' &&
290
+ this.isPlatformSupported(ival.platforms)
291
+ ) {
292
+ console.log(`- ${ikey}: ${ival.message}`)
293
+ }
294
+ }
295
+ }
296
+ }
297
+ }
298
+ }
299
+ }
300
+
301
+ isPlatformSupported(platforms: string[] | undefined): boolean {
302
+ if (!platforms || platforms.length === 0) {
303
+ return false
304
+ }
305
+
306
+ if (platforms.includes(`${process.platform}-${process.arch}`)) {
307
+ return true
308
+ }
309
+
310
+ if (platforms.includes(process.platform)) {
311
+ return true
312
+ }
313
+
314
+ return false
315
+ }
316
+
317
+ async copyFile(
318
+ sourceFileRelativePath: string,
319
+ destinationFilePath = sourceFileRelativePath
320
+ ): Promise<void> {
321
+ const log = this.log
322
+
323
+ await makeDirectory(path.dirname(destinationFilePath))
324
+
325
+ const sourceFileAbsolutePath = path.resolve(
326
+ this.templatesPath,
327
+ sourceFileRelativePath
328
+ )
329
+ await copyFile(sourceFileAbsolutePath, destinationFilePath)
330
+ log.info(`File '${destinationFilePath}' copied.`)
331
+ }
332
+
333
+ async copyFolder(source: string, destination = source): Promise<void> {
334
+ const log = this.log
335
+
336
+ await this.copyFolderRecursive(
337
+ path.resolve(this.templatesPath, source),
338
+ path.resolve(destination)
339
+ )
340
+ log.info(`Folder '${destination}' copied.`)
341
+ }
342
+
343
+ async copyFolderRecursive(
344
+ sourceFolderPath: string,
345
+ destinationFolderPath: string
346
+ ): Promise<void> {
347
+ // const log = this.log
348
+
349
+ await makeDirectory(path.dirname(destinationFolderPath))
350
+
351
+ const dirents = await fs.readdir(sourceFolderPath, {
352
+ withFileTypes: true,
353
+ })
354
+
355
+ for (const dirent of dirents) {
356
+ // log.trace(dirent.name)
357
+
358
+ if (dirent.isDirectory()) {
359
+ await this.copyFolderRecursive(
360
+ path.join(sourceFolderPath, dirent.name),
361
+ path.join(destinationFolderPath, dirent.name)
362
+ )
363
+ } else {
364
+ await copyFile(
365
+ path.join(sourceFolderPath, dirent.name),
366
+ path.join(destinationFolderPath, dirent.name)
367
+ )
368
+ }
369
+ }
370
+ }
371
+
372
+ async render(
373
+ inputFileRelativePath: string,
374
+ outputFileRelativePath: string,
375
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
376
+ substitutionsVariables = this.substitutionsVariables!
377
+ ): Promise<void> {
378
+ const log = this.log
379
+
380
+ log.trace(`render(${inputFileRelativePath}, ${outputFileRelativePath})`)
381
+
382
+ await makeDirectory(path.dirname(outputFileRelativePath))
383
+
384
+ // const headerPath = path.resolve(codePath, `${pnam}.h`)
385
+ try {
386
+ const fileContent = (await this.engine.renderFile(
387
+ inputFileRelativePath,
388
+ substitutionsVariables
389
+ )) as string
390
+
391
+ await fs.writeFile(outputFileRelativePath, fileContent, 'utf8')
392
+ } catch (err) {
393
+ if (err instanceof Error) {
394
+ throw new XpmOutputError(err.message)
395
+ }
396
+ }
397
+ log.info(`File '${outputFileRelativePath}' generated.`)
398
+ }
399
+ }
400
+
401
+ // ----------------------------------------------------------------------------
@@ -0,0 +1,179 @@
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 'assert'
17
+ import os from 'node:os'
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 { JsonActions } from './types.js'
27
+ import { performSubstitutions } from './functions/perform-substitutions.js'
28
+
29
+ // ----------------------------------------------------------------------------
30
+
31
+ // A collection of actions.
32
+ export class XpmLiquidActions {
33
+ // --------------------------------------------------------------------------
34
+ // Members.
35
+
36
+ readonly log: Logger
37
+ readonly engine: XpmLiquidEngine
38
+ readonly substitutionsVariables: XpmLiquidSubstitutionsVariables
39
+ readonly jsonActions: JsonActions
40
+
41
+ readonly #map: Map<string, XpmLiquidAction | undefined>
42
+
43
+ #isInitialised = false
44
+
45
+ // --------------------------------------------------------------------------
46
+ // Constructor and async initialiser.
47
+
48
+ constructor({
49
+ log,
50
+ engine,
51
+ substitutionsVariables,
52
+ jsonActions,
53
+ }: {
54
+ log: Logger
55
+ engine: XpmLiquidEngine
56
+ substitutionsVariables: XpmLiquidSubstitutionsVariables
57
+ jsonActions: JsonActions | undefined
58
+ }) {
59
+ log.trace(`${XpmLiquidActions.name}()`)
60
+
61
+ this.log = log
62
+ this.engine = engine
63
+ this.substitutionsVariables = substitutionsVariables
64
+ this.jsonActions = jsonActions ?? {}
65
+
66
+ // Possibly empty if there are no actions.
67
+ this.#map = new Map<string, XpmLiquidAction | undefined>()
68
+
69
+ // log.trace('substitutionsVariables => ', this.substitutionsVariables)
70
+ // The rest of the initialisation is done in the async initialiser.
71
+ }
72
+
73
+ // eslint-disable-next-line @typescript-eslint/require-await
74
+ async initialise(): Promise<void> {
75
+ if (this.#isInitialised) {
76
+ return
77
+ }
78
+ for (const actionName of Object.keys(this.jsonActions)) {
79
+ // TODO: expand templates in names
80
+ this.#map.set(actionName, undefined)
81
+ }
82
+
83
+ this.log.trace(
84
+ `${XpmLiquidActions.name}.initialise() =>`,
85
+ Array.from(this.#map.keys())
86
+ )
87
+
88
+ this.#isInitialised = true
89
+ }
90
+
91
+ // --------------------------------------------------------------------------
92
+ // Methods.
93
+
94
+ empty(): boolean {
95
+ return this.#map.size === 0
96
+ }
97
+
98
+ names(): string[] {
99
+ const actionNames = Array.from(this.#map.keys())
100
+
101
+ this.log.trace(`${XpmLiquidActions.name}.names() =>`, actionNames)
102
+ return actionNames
103
+ }
104
+
105
+ has(actionName: string): boolean {
106
+ return this.#map.has(actionName)
107
+ }
108
+
109
+ get(actionName: string): XpmLiquidAction {
110
+ let action = this.#map.get(actionName)
111
+ if (action === undefined) {
112
+ action = new XpmLiquidAction({ actionName, parentActions: this })
113
+ this.#map.set(actionName, action)
114
+ }
115
+
116
+ return action
117
+ }
118
+ }
119
+
120
+ // ----------------------------------------------------------------------------
121
+
122
+ // An individual action.
123
+ export class XpmLiquidAction {
124
+ // --------------------------------------------------------------------------
125
+ // Members.
126
+
127
+ readonly #actionName: string
128
+ readonly #parentActions: XpmLiquidActions
129
+
130
+ // For templates, the actual values.
131
+ matrixParameters?: XpmLiquidSubstitutionsStrings
132
+ #commands?: string[]
133
+
134
+ // --------------------------------------------------------------------------
135
+ // Constructor.
136
+
137
+ constructor({
138
+ actionName,
139
+ parentActions,
140
+ }: {
141
+ actionName: string
142
+ parentActions: XpmLiquidActions
143
+ }) {
144
+ parentActions.log.trace(`${XpmLiquidAction.name}(${actionName})`)
145
+
146
+ this.#actionName = actionName
147
+ this.#parentActions = parentActions
148
+ }
149
+
150
+ // --------------------------------------------------------------------------
151
+ // Methods.
152
+
153
+ async getCommands(): Promise<string[]> {
154
+ if (this.#commands === undefined) {
155
+ // Silently accept empty or non-existing actions.
156
+ const jsonAction = this.#parentActions.jsonActions[this.#actionName] ?? ''
157
+ const input = Array.isArray(jsonAction)
158
+ ? jsonAction.join(os.EOL)
159
+ : jsonAction
160
+
161
+ const substituted = await performSubstitutions({
162
+ input,
163
+ engine: this.#parentActions.engine,
164
+ substitutionsVariables: this.#parentActions.substitutionsVariables,
165
+ log: this.#parentActions.log,
166
+ })
167
+
168
+ this.#commands = substituted.split(os.EOL)
169
+ }
170
+
171
+ this.#parentActions.log.trace(
172
+ `${XpmLiquidAction.name}.commands() =>`,
173
+ this.#commands
174
+ )
175
+ return this.#commands
176
+ }
177
+ }
178
+
179
+ // ----------------------------------------------------------------------------