@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
@@ -1,947 +0,0 @@
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 * as fs from 'node:fs/promises'
18
- import * as os from 'node:os'
19
- import * as path from 'node:path'
20
- import * as util from 'node:util'
21
- import * as stream from 'node:stream'
22
-
23
- // https://www.npmjs.com/package/@npmcli/arborist
24
- import { Arborist } from '@npmcli/arborist'
25
-
26
- // https://www.npmjs.com/package/pacote
27
- import * as pacote from 'pacote'
28
- import { AbbreviatedManifest, ManifestResult } from 'pacote'
29
-
30
- // https://www.npmjs.com/package/cacache
31
- import cacache, { put } from 'cacache'
32
-
33
- // https://www.npmjs.com/package/decompress
34
- import decompress from 'decompress'
35
-
36
- // https://www.npmjs.com/package/semver
37
- import * as semver from 'semver'
38
-
39
- // https://www.npmjs.com/package/del
40
- import { deleteAsync } from 'del'
41
-
42
- // https://www.npmjs.com/package/proxy-from-env
43
- import { getProxyForUrl } from 'proxy-from-env'
44
-
45
- // https://www.npmjs.com/package/https-proxy-agent
46
- import { HttpsProxyAgent } from 'https-proxy-agent'
47
-
48
- // https://www.npmjs.com/package/node-fetch
49
- import fetch, { Response } from 'node-fetch'
50
-
51
- // https://www.npmjs.com/package/@xpack/logger
52
- import { Logger } from '@xpack/logger'
53
-
54
- // ----------------------------------------------------------------------------
55
-
56
- import {
57
- JsonBuildConfiguration,
58
- // JsonNpmPackage,
59
- JsonXpmPackage,
60
- XpmConfig,
61
- } from './types.js'
62
- import { chmodRecursive } from './functions/chmod-recursive.js'
63
- import { XpmPolicies } from './policies.js'
64
- import { XpmError, XpmInputError, XpmPrerequisitesError } from './errors.js'
65
-
66
- // ----------------------------------------------------------------------------
67
-
68
- export interface XpmPackageSpecifier {
69
- scope?: string
70
- name?: string
71
- version?: string
72
- }
73
-
74
- export class XpmPackage {
75
- // --------------------------------------------------------------------------
76
- // Members.
77
-
78
- packageFolderPath: string
79
- jsonPackage?: JsonXpmPackage
80
-
81
- readonly #log: Logger
82
-
83
- // --------------------------------------------------------------------------
84
- // Constructor.
85
-
86
- constructor({
87
- log,
88
- packageFolderPath,
89
- }: {
90
- log: Logger
91
- packageFolderPath: string
92
- }) {
93
- this.#log = log
94
- this.packageFolderPath = packageFolderPath
95
-
96
- log.trace(`${XpmPackage.name}(${packageFolderPath})`)
97
- }
98
-
99
- // --------------------------------------------------------------------------
100
- // Methods.
101
-
102
- async readPackageDotJson({
103
- withThrow = false,
104
- }: {
105
- withThrow?: boolean
106
- } = {}): Promise<JsonXpmPackage | undefined> {
107
- const jsonFilePath = path.join(this.packageFolderPath, 'package.json')
108
-
109
- let fileContent: string | Buffer
110
- try {
111
- fileContent = await fs.readFile(jsonFilePath)
112
- } catch (err) {
113
- if (withThrow) {
114
- if (err instanceof Error) {
115
- this.#log.trace(err.message)
116
- }
117
- throw new XpmInputError(
118
- `no package.json in folder ‘${this.packageFolderPath}’`
119
- )
120
- } else {
121
- return undefined
122
- }
123
- }
124
-
125
- try {
126
- this.jsonPackage = JSON.parse(fileContent.toString()) as JsonXpmPackage
127
- } catch (err) {
128
- if (withThrow) {
129
- this.jsonPackage = undefined
130
- if (err instanceof Error) {
131
- this.#log.trace(err.message)
132
- }
133
- throw new XpmInputError(
134
- `invalid package.json in folder ‘${this.packageFolderPath}’`
135
- )
136
- } else {
137
- return undefined
138
- }
139
- }
140
- return this.jsonPackage
141
- }
142
-
143
- // Note: the json is explicitly passed.
144
- async rewritePackageDotJson(jsonPackage: JsonXpmPackage): Promise<void> {
145
- const log = this.#log
146
-
147
- assert(jsonPackage)
148
- const jsonString = JSON.stringify(jsonPackage, null, 2) + '\n'
149
-
150
- const jsonFilePath = path.join(this.packageFolderPath, 'package.json')
151
- log.trace(`write filePath: '${jsonFilePath}'`)
152
- await fs.writeFile(jsonFilePath, jsonString)
153
- }
154
-
155
- isNpmPackage(): boolean {
156
- const jsonPackage = this.jsonPackage
157
- if (jsonPackage?.name === undefined || jsonPackage.version === undefined) {
158
- return false
159
- }
160
- const name = jsonPackage.name.trim()
161
- if (name.length === 0) {
162
- return false
163
- }
164
- const version = jsonPackage.version.trim()
165
- if (version.length === 0) {
166
- return false
167
- }
168
- return true
169
- }
170
-
171
- isXpmPackage(): boolean {
172
- const jsonPackage = this.jsonPackage
173
- if (!this.isNpmPackage()) {
174
- return false
175
- }
176
- if (jsonPackage?.xpack === undefined) {
177
- return false
178
- }
179
- return true
180
- }
181
-
182
- // Binary packages must have both executables and binaries, but
183
- // the presence of one implies the other, so validate.
184
- isBinaryXpmPackage() {
185
- const jsonPackage = this.jsonPackage
186
- if (!this.isXpmPackage()) {
187
- return false
188
- }
189
- // Since Nov. 2024, `executables` is preferred to `bin`.
190
- if (jsonPackage?.xpack.executables ?? jsonPackage?.xpack.bin) {
191
- if (!jsonPackage.xpack.binaries) {
192
- throw new XpmInputError(
193
- "doesn't look like a proper binary xpm package, " +
194
- 'package.json has no "xpack.binaries"'
195
- )
196
- }
197
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
198
- if (!jsonPackage.xpack.binaries.platforms) {
199
- throw new XpmInputError(
200
- "doesn't look like a proper binary xpm package, " +
201
- 'package.json has no "xpack.binaries.platforms"'
202
- )
203
- }
204
- return true
205
- }
206
- if (jsonPackage?.xpack.binaries) {
207
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
208
- if (!jsonPackage.xpack.binaries.platforms) {
209
- throw new XpmInputError(
210
- "doesn't look like a proper binary xpm package, " +
211
- 'package.json has no "xpack.binaries.platforms"'
212
- )
213
- }
214
- if (!(jsonPackage.xpack.executables ?? jsonPackage.xpack.bin)) {
215
- throw new XpmInputError(
216
- "doesn't look like a proper binary xpm package, " +
217
- 'package.json has no "xpack.executables"'
218
- )
219
- }
220
- return true
221
- }
222
- return false
223
- }
224
-
225
- isNodeModule() {
226
- const jsonPackage = this.jsonPackage
227
- return !!jsonPackage && !jsonPackage.xpack
228
- }
229
-
230
- isBinaryNodeModule() {
231
- const jsonPackage = this.jsonPackage
232
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
233
- return this.isNodeModule() && !!jsonPackage?.bin
234
- }
235
-
236
- hasNpmScripts(): boolean {
237
- const jsonPackage = this.jsonPackage
238
- if (
239
- jsonPackage?.scripts !== undefined &&
240
- Object.keys(jsonPackage.scripts).length > 0
241
- ) {
242
- return true
243
- }
244
-
245
- return false
246
- }
247
-
248
- hasXpmActions(): boolean {
249
- const json = this.jsonPackage
250
- if (!this.isXpmPackage()) {
251
- return false
252
- }
253
- try {
254
- if (
255
- json?.xpack.actions !== undefined &&
256
- Object.keys(json.xpack.actions).length > 0
257
- ) {
258
- return true
259
- }
260
- if (
261
- json?.xpack.buildConfigurations !== undefined &&
262
- Object.keys(json.xpack.buildConfigurations).length > 0
263
- ) {
264
- // Don't use a lambda, to return directly from the loop.
265
- for (const name of Object.keys(json.xpack.buildConfigurations)) {
266
- const buildConfiguration: JsonBuildConfiguration =
267
- json.xpack.buildConfigurations[name]
268
- if (
269
- buildConfiguration.actions !== undefined &&
270
- Object.keys(buildConfiguration.actions).length > 0
271
- ) {
272
- return true
273
- }
274
- }
275
- }
276
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
277
- } catch (err) {
278
- // In case xpack is not an option to get its properties.
279
- }
280
-
281
- return false
282
- }
283
-
284
- getMinimumXpmRequired(): string | undefined {
285
- const log = this.#log
286
- const jsonPackage = this.jsonPackage
287
-
288
- log.trace(`${XpmPackage.name}.getMinimumXpmRequired()`)
289
-
290
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
291
- const version = jsonPackage?.xpack?.minimumXpmRequired
292
- if (version === undefined) {
293
- return undefined
294
- }
295
- // Remove the pre-release part.
296
- return version.replace(/-.*$/, '')
297
- }
298
-
299
- async checkMinimumXpmRequired({
300
- xpmRootFolderPath,
301
- }: {
302
- xpmRootFolderPath: string
303
- }): Promise<string | undefined> {
304
- const log = this.#log
305
- const jsonPackage = this.jsonPackage
306
-
307
- log.trace(`${XpmPackage.name}.checkMinimumXpmRequired()`)
308
-
309
- if (!jsonPackage) {
310
- // Not in a package.
311
- return undefined
312
- }
313
-
314
- if (!this.isXpmPackage() || !jsonPackage.xpack.minimumXpmRequired) {
315
- log.trace('minimumXpmRequired not used, no checks')
316
- return undefined
317
- }
318
- // Remove the pre-release part.
319
- const cleanedVersion = semver.clean(
320
- jsonPackage.xpack.minimumXpmRequired.replace(/-.*$/, '')
321
- )
322
- if (!cleanedVersion) {
323
- return undefined
324
- }
325
- const minimumXpmRequired: string = cleanedVersion
326
-
327
- log.trace(`minimumXpmRequired: ${minimumXpmRequired}`)
328
-
329
- let jsonXpmCliPackage: JsonXpmPackage | undefined
330
- try {
331
- const cliXpmPackage = new XpmPackage({
332
- log,
333
- packageFolderPath: xpmRootFolderPath,
334
- })
335
- jsonXpmCliPackage = await cliXpmPackage.readPackageDotJson({
336
- withThrow: true,
337
- })
338
- } catch (err) {
339
- if (err instanceof Error) {
340
- log.trace(err.message)
341
- } else {
342
- log.trace(err)
343
- }
344
- return undefined
345
- }
346
- assert(jsonXpmCliPackage)
347
- log.trace(jsonXpmCliPackage.version)
348
-
349
- if (!jsonXpmCliPackage.version) {
350
- return undefined
351
- }
352
-
353
- // Remove the pre-release part.
354
- const xpmVersion = semver.clean(
355
- jsonXpmCliPackage.version.replace(/-.*$/, '')
356
- )
357
- if (!xpmVersion) {
358
- return undefined
359
- }
360
- if (semver.lt(xpmVersion, minimumXpmRequired)) {
361
- throw new XpmPrerequisitesError(
362
- 'package ' +
363
- (jsonPackage.name ? `'${jsonPackage.name}' ` : '') +
364
- `requires xpm v${minimumXpmRequired} or later, please upgrade`
365
- )
366
- }
367
- // Check passed.
368
- return minimumXpmRequired
369
- }
370
-
371
- parsePackageSpecifier({
372
- npmPackageSpecifier,
373
- }: {
374
- npmPackageSpecifier: string
375
- }): XpmPackageSpecifier {
376
- assert(npmPackageSpecifier)
377
-
378
- const log = this.#log
379
-
380
- let scope
381
- let name
382
- let version
383
-
384
- if (npmPackageSpecifier.startsWith('@')) {
385
- const arr = npmPackageSpecifier.split('/')
386
- if (arr.length > 2) {
387
- throw new XpmInputError(`'${npmPackageSpecifier}' not a package name`)
388
- }
389
- scope = arr[0]
390
- if (arr.length > 1) {
391
- const arr2 = arr[1].split('@')
392
- name = arr2[0]
393
- if (arr2.length > 1) {
394
- version = arr2[1]
395
- }
396
- }
397
- } else {
398
- const arr2 = npmPackageSpecifier.split('@')
399
- name = arr2[0]
400
- if (arr2.length > 1) {
401
- version = arr2[1]
402
- }
403
- }
404
- log.trace(
405
- `${npmPackageSpecifier} => ` +
406
- `${scope ?? '?'} ${name ?? '?'} ${version ?? '?'}`
407
- )
408
-
409
- return { scope, name, version }
410
- }
411
-
412
- getPlatformKey({
413
- doForce32bit = false,
414
- }: {
415
- doForce32bit?: boolean
416
- } = {}): string {
417
- const log = this.#log
418
-
419
- const platform = process.platform
420
- let arch = process.arch
421
- if (doForce32bit) {
422
- if (platform === 'win32' && arch === 'x64') {
423
- arch = 'ia32'
424
- } else if (platform === 'linux' && arch === 'x64') {
425
- arch = 'ia32'
426
- } else if (platform === 'linux' && arch === 'arm64') {
427
- arch = 'arm'
428
- }
429
- }
430
- const key = `${platform}-${arch}`
431
- log.trace(`platform key: ${key}`)
432
- return key
433
- }
434
-
435
- async pacoteCreateManifest({
436
- specifier,
437
- cacheFolderPath,
438
- }: {
439
- specifier: string
440
- cacheFolderPath: string
441
- }): Promise<AbbreviatedManifest & ManifestResult> {
442
- const log = this.#log
443
- log.trace(`${XpmPackage.name}.pacoteCreateManifest('${specifier}')`)
444
- const manifest = await pacote.manifest(specifier, {
445
- cache: cacheFolderPath,
446
- })
447
-
448
- return manifest
449
- }
450
-
451
- async pacoteExtractPackage({
452
- packFullName,
453
- specifier,
454
- destinationFolderPath,
455
- cacheFolderPath,
456
- setReadOnly,
457
- verboseMessage,
458
- config,
459
- policies,
460
- }: {
461
- packFullName: string
462
- specifier: string
463
- destinationFolderPath: string
464
- cacheFolderPath: string
465
- setReadOnly: boolean
466
- verboseMessage: string
467
- config: XpmConfig
468
- policies: XpmPolicies
469
- }): Promise<void> {
470
- assert(packFullName)
471
- assert(specifier)
472
- assert(destinationFolderPath)
473
- assert(cacheFolderPath)
474
- assert(verboseMessage)
475
- assert(config)
476
- assert(policies)
477
-
478
- const log = this.#log
479
- log.trace(`${XpmPackage.name}.pacoteExtractContent('${specifier}')`)
480
-
481
- let destinationXpmPackage = new XpmPackage({
482
- log,
483
- packageFolderPath: destinationFolderPath,
484
- })
485
- const jsonDestination = await destinationXpmPackage.readPackageDotJson()
486
- if (jsonDestination) {
487
- // The package is already present in the destination folder.
488
- if (!config.doForce) {
489
- if (!config.doSkipIfInstalled) {
490
- log.warn(
491
- `package ${packFullName} already installed, ` +
492
- 'use --force to overwrite'
493
- )
494
- }
495
- return // Not an error, proceed to other packages.
496
- }
497
-
498
- if (setReadOnly) {
499
- if (config.isDryRun) {
500
- log.verbose('Pretend changing permissions to read-write...')
501
- log.verbose(
502
- 'Pretend removing existing package from ' +
503
- `'${destinationFolderPath}'...`
504
- )
505
- } else {
506
- log.verbose('Changing permissions to read-write...')
507
- await chmodRecursive({
508
- inputPath: destinationFolderPath,
509
- readOnly: false,
510
- log,
511
- })
512
-
513
- log.verbose(
514
- `Removing existing package from '${destinationFolderPath}'...`
515
- )
516
- await deleteAsync(destinationFolderPath, { force: true })
517
- }
518
- }
519
- }
520
-
521
- const destinationTmpFolderPath = destinationFolderPath + '.tmp'
522
- log.trace(`del(${destinationTmpFolderPath})`)
523
- await deleteAsync(destinationTmpFolderPath, { force: true })
524
-
525
- if (log.isVerbose && verboseMessage) {
526
- log.verbose(verboseMessage)
527
- }
528
-
529
- if (config.isDryRun) {
530
- if (!log.isVerbose) {
531
- log.info(`${packFullName} => '${destinationFolderPath}' (dry run)`)
532
- }
533
- } else {
534
- await this.pacoteExtract({
535
- specifier: specifier,
536
- destinationFolderPath: destinationTmpFolderPath,
537
- cacheFolderPath,
538
- })
539
- if (!log.isVerbose) {
540
- log.info(`${packFullName} => '${destinationFolderPath}'`)
541
- }
542
- destinationXpmPackage = new XpmPackage({
543
- log,
544
- packageFolderPath: destinationTmpFolderPath,
545
- })
546
- }
547
-
548
- await destinationXpmPackage.readPackageDotJson()
549
- if (!destinationXpmPackage.isXpmPackage()) {
550
- if (!policies.shareNpmDependencies) {
551
- log.trace(`del(${destinationTmpFolderPath})`)
552
- await deleteAsync(destinationTmpFolderPath, { force: true })
553
- throw new XpmInputError(
554
- `${packFullName} is not an xpm package, use npm to install it`
555
- )
556
- }
557
- log.debug(
558
- `'${destinationFolderPath}' doesn't look like an ` +
559
- 'xpm package, package.json has no "xpack"'
560
- )
561
- return
562
- }
563
-
564
- if (config.isDryRun) {
565
- if (setReadOnly) {
566
- log.verbose('Pretend changing permissions to read-only...')
567
- }
568
- } else {
569
- await this.#downloadBinaries({
570
- destinationXpmPackage,
571
- destinationFolderPath,
572
- cacheFolderPath,
573
- config,
574
- })
575
-
576
- // When everything is ready, rename the folder to the desired name.
577
- await fs.rename(destinationTmpFolderPath, destinationFolderPath)
578
- log.trace(`rename(${destinationTmpFolderPath}, ${destinationFolderPath})`)
579
-
580
- log.trace(`in '${destinationFolderPath}'`)
581
- if (setReadOnly) {
582
- log.verbose('Changing permissions to read-only...')
583
- await chmodRecursive({
584
- inputPath: destinationFolderPath,
585
- readOnly: true,
586
- log,
587
- })
588
- }
589
- }
590
- }
591
-
592
- async pacoteExtract({
593
- specifier,
594
- destinationFolderPath,
595
- cacheFolderPath,
596
- }: {
597
- specifier: string
598
- destinationFolderPath: string
599
- cacheFolderPath: string
600
- }): Promise<void> {
601
- assert(specifier)
602
- assert(destinationFolderPath)
603
- assert(cacheFolderPath)
604
-
605
- const log = this.#log
606
- log.trace(`${XpmPackage.name}.pacoteExtract(${specifier})`)
607
-
608
- try {
609
- log.trace(`pacote.extract(${specifier})`)
610
- const fetchResult = await pacote.extract(
611
- specifier,
612
- destinationFolderPath,
613
- { cache: cacheFolderPath, Arborist }
614
- )
615
- log.trace(`fetchResult: ${util.inspect(fetchResult)}`)
616
- } catch (err) {
617
- log.trace(util.inspect(err))
618
- throw new XpmInputError(`Package ${specifier} not found`)
619
- }
620
- }
621
-
622
- async #downloadBinaries({
623
- destinationXpmPackage,
624
- destinationFolderPath,
625
- cacheFolderPath,
626
- config,
627
- }: {
628
- destinationXpmPackage: XpmPackage
629
- destinationFolderPath: string
630
- cacheFolderPath: string
631
- config: XpmConfig
632
- }): Promise<void> {
633
- assert(destinationXpmPackage)
634
- assert(destinationFolderPath)
635
- assert(cacheFolderPath)
636
- assert(config)
637
-
638
- const log = this.#log
639
- const packageFolderPath = destinationXpmPackage.packageFolderPath
640
- const jsonPackage = destinationXpmPackage.jsonPackage
641
- assert(jsonPackage)
642
-
643
- log.trace(`${XpmPackage.name}.downloadBinaries(${packageFolderPath})`)
644
- if (!destinationXpmPackage.isXpmPackage()) {
645
- log.debug(
646
- "doesn't look like an xpm package, " + 'package.json has no "xpack"'
647
- )
648
- return
649
- }
650
- if (!destinationXpmPackage.isBinaryXpmPackage()) {
651
- log.debug(
652
- "doesn't look like an xpm package, " +
653
- 'package.json has no "xpack.executables" and "xpack.binaries"'
654
- )
655
- return
656
- }
657
-
658
- const platformKey = this.getPlatformKey()
659
- const platformKeyAliases = new Set<string>()
660
-
661
- if (['linux-x32', 'linux-x86', 'linux-ia32'].includes(platformKey)) {
662
- platformKeyAliases.add('linux-x32')
663
- platformKeyAliases.add('linux-x86')
664
- platformKeyAliases.add('linux-ia32') // official
665
- } else if (['win32-x32', 'win32-x86', 'win32-ia32'].includes(platformKey)) {
666
- platformKeyAliases.add('win32-x32')
667
- platformKeyAliases.add('win32-x86')
668
- platformKeyAliases.add('win32-ia32') // official
669
- } else {
670
- platformKeyAliases.add(platformKey)
671
- }
672
-
673
- assert(jsonPackage.xpack.binaries)
674
- const platforms = jsonPackage.xpack.binaries.platforms
675
-
676
- let platform
677
- for (const item of platformKeyAliases) {
678
- if (Object.prototype.hasOwnProperty.call(platforms, item)) {
679
- platform = platforms[item]
680
- break
681
- }
682
- }
683
- if (!platform) {
684
- throw new XpmInputError(`platform ${platformKey} not supported`)
685
- }
686
-
687
- if (!jsonPackage.xpack.binaries.baseUrl) {
688
- throw new XpmInputError(
689
- 'missing "xpack.binaries.baseUrl" in package.json'
690
- )
691
- }
692
-
693
- if (platform.skip) {
694
- log.warn('no binaries are available for this platform, command ignored')
695
- return
696
- }
697
-
698
- if (!platform.fileName) {
699
- throw new XpmInputError(
700
- `missing xpack.binaries.platform[${platformKey}].fileName`
701
- )
702
- }
703
-
704
- // Prefer the platform specific URL, if available, otherwise
705
- // use the common URL.
706
- let fileUrl = platform.baseUrl ?? jsonPackage.xpack.binaries.baseUrl
707
- if (!fileUrl.endsWith('/')) {
708
- fileUrl += '/'
709
- }
710
-
711
- fileUrl += platform.fileName
712
-
713
- let hashAlgorithm = '?'
714
- let hexSum = '?'
715
- if (platform.sha256) {
716
- hashAlgorithm = 'sha256'
717
- hexSum = platform.sha256
718
- } else if (platform.sha512) {
719
- hashAlgorithm = 'sha512'
720
- hexSum = platform.sha512
721
- }
722
-
723
- let integrityDigest = '?'
724
- if (hexSum) {
725
- const buff = Buffer.from(hexSum, 'hex')
726
- integrityDigest = `${hashAlgorithm}-${buff.toString('base64')}`
727
- }
728
- log.trace(`expected integrity digest ${integrityDigest} for ${hexSum}`)
729
-
730
- if (config.isDryRun) {
731
- log.info(`Pretend downloading ${fileUrl}...`)
732
- log.info(`Pretend extracting '${platform.fileName}'...`)
733
- return
734
- }
735
-
736
- const cacheKey = `xpm:binaries:${platform.fileName}`
737
- log.trace(`getting cacache info(${cacheFolderPath}, ${cacheKey})...`)
738
- // Debug only, to force the downloads.
739
- // await cacache.rm.entry(cacheFolderPath, cacheKey)
740
- let cacheInfo = await cacache.get.info(cacheFolderPath, cacheKey)
741
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
742
- if (!cacheInfo) {
743
- // If the cache has no idea of the desired file, proceed with
744
- // the download.
745
- log.info(`Downloading ${fileUrl}...`)
746
- const opts: { integrity?: string } = {}
747
- if (integrityDigest) {
748
- // Enable hash checking.
749
- opts.integrity = integrityDigest
750
- }
751
- try {
752
- await this.cacheArchive({
753
- url: fileUrl,
754
- cacheFolderPath,
755
- key: cacheKey,
756
- opts,
757
- })
758
- log.trace(`cache written for ${fileUrl}`)
759
- } catch (err) {
760
- log.trace(util.inspect(err))
761
- // Do not throw yet, only display the error.
762
- if (err instanceof Error) {
763
- log.info(err.message)
764
- } else {
765
- log.info(String(err))
766
- }
767
- if (os.platform() === 'win32') {
768
- log.info(
769
- 'If you have an aggressive antivirus, try to ' +
770
- 'reconfigure it, or temporarily disable it'
771
- )
772
- }
773
- throw new XpmError('download failed, quit')
774
- }
775
- // Update the cache info after downloading the file.
776
- cacheInfo = await cacache.get.info(cacheFolderPath, cacheKey)
777
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
778
- if (!cacheInfo) {
779
- throw new XpmError('download failed, quit')
780
- }
781
- }
782
-
783
- log.trace(`cache path ${cacheInfo.path} for ${fileUrl}`)
784
-
785
- // The number of initial folder levels to skip.
786
- let skip = 0
787
- if (jsonPackage.xpack.binaries.skip) {
788
- try {
789
- skip = jsonPackage.xpack.binaries.skip
790
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
791
- } catch (err) {
792
- // Ignore invalid skip value, use default
793
- }
794
- }
795
- log.trace(`skip ${skip.toString()} levels`)
796
-
797
- const contentFolderRelativePath =
798
- jsonPackage.xpack.binaries.destination || '.content'
799
- const contentFolderPath = path.join(
800
- packageFolderPath,
801
- contentFolderRelativePath
802
- )
803
- const destinationContentFolderPath = path.join(
804
- destinationFolderPath,
805
- contentFolderRelativePath
806
- )
807
-
808
- log.trace(`del ${contentFolderPath}`)
809
- await deleteAsync(contentFolderPath, { force: true })
810
-
811
- const cacheInfoPath = cacheInfo.path
812
- log.trace(`cacheInfoPath ${cacheInfoPath}`)
813
- let res: decompress.File[] = []
814
- // Currently this includes decompressTar(), decompressTarbz2(),
815
- // decompressTargz(), decompressUnzip().
816
- log.info(`Extracting '${platform.fileName}'...`)
817
-
818
- res = await decompress(cacheInfoPath, contentFolderPath, {
819
- strip: skip,
820
- })
821
-
822
- if (log.isVerbose) {
823
- // The common value is self relative ./.content; remove the folder.
824
- const shownFolderRelativePath = contentFolderRelativePath.replace(
825
- /^\.\//,
826
- ''
827
- )
828
- assert(jsonPackage.version)
829
- log.verbose(
830
- `${res.length.toString()} files extracted in ` +
831
- `'${jsonPackage.version}/${shownFolderRelativePath}'`
832
- )
833
- } else {
834
- log.info(
835
- `${res.length.toString()} files => '${destinationContentFolderPath}'`
836
- )
837
- }
838
- }
839
-
840
- // Returns nothing. Used by downloadBinaries().
841
- async cacheArchive({
842
- url,
843
- cacheFolderPath,
844
- key,
845
- opts,
846
- }: {
847
- url: string
848
- cacheFolderPath: string
849
- key: string
850
-
851
- opts: put.Options
852
- }): Promise<void> {
853
- assert(url)
854
- assert(cacheFolderPath)
855
- assert(key)
856
- assert(opts)
857
- const log = this.#log
858
-
859
- // https://github.com/node-fetch/node-fetch/blob/main/docs/ERROR-HANDLING.md
860
- // https://github.com/node-fetch/node-fetch/blob/main/test/main.js
861
- // https://www.scrapingbee.com/blog/proxy-node-fetch/
862
- // https://iproyal.com/blog/how-do-i-use-a-node-fetch-proxy/
863
-
864
- let response: Response | undefined
865
- let timeoutMillis = 1000
866
- // If no proxy is set, an empty string is returned.
867
-
868
- const proxyUrl: string = getProxyForUrl(url)
869
- log.trace(`proxyUrl ${proxyUrl}`)
870
- const maxRetry = 5
871
- for (let retry = 0; retry < maxRetry; ++retry) {
872
- try {
873
- if (proxyUrl.length > 0) {
874
- const proxyAgent = new HttpsProxyAgent(proxyUrl)
875
- log.trace(`proxyAgent ${util.inspect(proxyAgent)} for ${url}`)
876
- response = await fetch(url, { agent: proxyAgent })
877
- } else {
878
- response = await fetch(url)
879
- }
880
- } catch (err) {
881
- log.trace(util.inspect(err))
882
- const errorMessage = err instanceof Error ? err.message : String(err)
883
- throw new XpmError(`${errorMessage} in fetch ${url}`)
884
- }
885
-
886
- log.debug(`fetch.status ${response.status.toString()} ${url}`)
887
- log.trace(`fetch.statusText ${response.statusText} ${url}`)
888
-
889
- if (!response.ok) {
890
- break
891
- }
892
-
893
- // the HTTP response status was [200, 300).
894
- // https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#2xx_success
895
-
896
- const pipelinePromise = util.promisify(stream.pipeline)
897
-
898
- log.trace(`create write stream for ${key}`)
899
-
900
- const cacacheWriteStream = cacache.put.stream(cacheFolderPath, key, opts)
901
- log.trace(`create pipeline for ${key}`)
902
- try {
903
- assert(response.body)
904
- await pipelinePromise(response.body, cacacheWriteStream)
905
- // If no exception, everything must be ok.
906
- return
907
- } catch (err) {
908
- log.trace(util.inspect(err))
909
- const errorMessage = err instanceof Error ? err.message : String(err)
910
- if (retry >= maxRetry) {
911
- throw new XpmError(`${errorMessage} in pipeline ${url}`)
912
- }
913
- // For now retry on all errors during download.
914
- // TODO: identify non recoverable and quit.
915
- log.warn(`${errorMessage} while downloading ${url}, retrying...`)
916
- const tenPercent = timeoutMillis * 0.1
917
- // +/- 10%
918
- // Math.random() * (max - min) + min
919
- const jitter = Math.floor(
920
- Math.random() * (tenPercent - -tenPercent) + -tenPercent
921
- )
922
- timeoutMillis = timeoutMillis + jitter
923
- log.debug(`timeoutMillis: ${timeoutMillis.toString()}`)
924
- const sleep = (ms: number) =>
925
- new Promise((resolve) => setTimeout(resolve, ms))
926
- await sleep(timeoutMillis)
927
-
928
- // 1 2 4 8 16... seconds
929
- timeoutMillis = timeoutMillis * 2
930
- }
931
- }
932
-
933
- // res.status < 200 || res.status >= 300 (4xx, 5xx)
934
- // 1xx informational
935
- // 3xx: redirection messages
936
- // 4xx: client error
937
- // 5xx: server error
938
- // TODO: detect cases that can be retried.
939
- assert(response)
940
- throw new XpmError(
941
- `server returned ${response.status.toString()}: ` +
942
- `${response.statusText} for ${key}`
943
- )
944
- }
945
- }
946
-
947
- // ----------------------------------------------------------------------------