@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.
- package/LICENSE +21 -0
- package/README.md +223 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/chmod-recursive.d.ts +7 -0
- package/dist/lib/chmod-recursive.d.ts.map +1 -0
- package/dist/lib/chmod-recursive.js +81 -0
- package/dist/lib/chmod-recursive.js.map +1 -0
- package/dist/lib/errors.d.ts +11 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +26 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/functions/chmod-recursive.d.ts +7 -0
- package/dist/lib/functions/chmod-recursive.d.ts.map +1 -0
- package/dist/lib/functions/chmod-recursive.js +81 -0
- package/dist/lib/functions/chmod-recursive.js.map +1 -0
- package/dist/lib/functions/perform-substitutions.d.ts +20 -0
- package/dist/lib/functions/perform-substitutions.d.ts.map +1 -0
- package/dist/lib/functions/perform-substitutions.js +85 -0
- package/dist/lib/functions/perform-substitutions.js.map +1 -0
- package/dist/lib/functions/utils.d.ts +30 -0
- package/dist/lib/functions/utils.d.ts.map +1 -0
- package/dist/lib/functions/utils.js +70 -0
- package/dist/lib/functions/utils.js.map +1 -0
- package/dist/lib/init-template-base.d.ts +46 -0
- package/dist/lib/init-template-base.d.ts.map +1 -0
- package/dist/lib/init-template-base.js +275 -0
- package/dist/lib/init-template-base.js.map +1 -0
- package/dist/lib/liquid-actions.d.ts +32 -0
- package/dist/lib/liquid-actions.d.ts.map +1 -0
- package/dist/lib/liquid-actions.js +113 -0
- package/dist/lib/liquid-actions.js.map +1 -0
- package/dist/lib/liquid-build-configurations.d.ts +49 -0
- package/dist/lib/liquid-build-configurations.d.ts.map +1 -0
- package/dist/lib/liquid-build-configurations.js +267 -0
- package/dist/lib/liquid-build-configurations.js.map +1 -0
- package/dist/lib/liquid-drop.d.ts +13 -0
- package/dist/lib/liquid-drop.d.ts.map +1 -0
- package/dist/lib/liquid-drop.js +56 -0
- package/dist/lib/liquid-drop.js.map +1 -0
- package/dist/lib/liquid-engine.d.ts +5 -0
- package/dist/lib/liquid-engine.d.ts.map +1 -0
- package/dist/lib/liquid-engine.js +85 -0
- package/dist/lib/liquid-engine.js.map +1 -0
- package/dist/lib/liquid-package.d.ts +17 -0
- package/dist/lib/liquid-package.d.ts.map +1 -0
- package/dist/lib/liquid-package.js +70 -0
- package/dist/lib/liquid-package.js.map +1 -0
- package/dist/lib/package.d.ts +66 -0
- package/dist/lib/package.d.ts.map +1 -0
- package/dist/lib/package.js +700 -0
- package/dist/lib/package.js.map +1 -0
- package/dist/lib/perform-substitutions.d.ts +20 -0
- package/dist/lib/perform-substitutions.d.ts.map +1 -0
- package/dist/lib/perform-substitutions.js +85 -0
- package/dist/lib/perform-substitutions.js.map +1 -0
- package/dist/lib/policies.d.ts +13 -0
- package/dist/lib/policies.d.ts.map +1 -0
- package/dist/lib/policies.js +31 -0
- package/dist/lib/policies.js.map +1 -0
- package/dist/lib/substitutions-variables.d.ts +117 -0
- package/dist/lib/substitutions-variables.d.ts.map +1 -0
- package/dist/lib/substitutions-variables.js +51 -0
- package/dist/lib/substitutions-variables.js.map +1 -0
- package/dist/lib/types.d.ts +70 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +13 -0
- package/dist/lib/types.js.map +1 -0
- package/dist/lib/utils.d.ts +30 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +70 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +102 -0
- package/src/README.md +10 -0
- package/src/index.ts +35 -0
- package/src/lib/errors.ts +29 -0
- package/src/lib/functions/chmod-recursive.ts +103 -0
- package/src/lib/functions/perform-substitutions.ts +116 -0
- package/src/lib/functions/utils.ts +88 -0
- package/src/lib/init-template-base.ts +401 -0
- package/src/lib/liquid-actions.ts +179 -0
- package/src/lib/liquid-build-configurations.ts +410 -0
- package/src/lib/liquid-drop.ts +99 -0
- package/src/lib/liquid-engine.ts +135 -0
- package/src/lib/liquid-package.ts +108 -0
- package/src/lib/package.ts +946 -0
- package/src/lib/policies.ts +49 -0
- package/src/lib/substitutions-variables.ts +177 -0
- package/src/lib/types.ts +109 -0
- package/src/package.json +3 -0
- 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
|
+
// ----------------------------------------------------------------------------
|