@ds-sfdc/sfparty 1.3.4 → 1.3.6
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/README.md +8 -1
- package/package.json +3 -2
- package/src/index.js +42 -30
- package/src/lib/checkVersion.js +60 -28
- package/src/lib/fileUtils.js +1 -0
- package/src/lib/gitUtils.js +1 -1
- package/src/lib/packageUtil.js +107 -77
- package/src/party/combine.js +11 -11
- package/src/party/split.js +4 -4
- package/test/checkVersion.spec.js +95 -0
- package/test/lib/git/diff.spec.js +32 -34
- package/test/lib/git/log.spec.js +18 -20
- package/test/lib/package/addMember.spec.js +49 -0
- package/test/lib/package/getPackageXML.spec.js +57 -0
- package/test/lib/package/savePackage.spec.js +57 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @ds-sfdc/sfparty
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/@ds-sfdc/sfparty) [](https://npmjs.org/package/@ds-sfdc/sfparty) [](https://github.com/TimPaulaskasDS/sfparty/blob/main/LICENSE.md)
|
|
3
|
+
[](https://www.npmjs.com/package/@ds-sfdc/sfparty) [](https://npmjs.org/package/@ds-sfdc/sfparty) [](https://github.com/TimPaulaskasDS/sfparty/blob/main/LICENSE.md) 
|
|
4
4
|
|
|
5
5
|
## Why use sfparty?
|
|
6
6
|
|
|
@@ -159,3 +159,10 @@ Optional:
|
|
|
159
159
|
```
|
|
160
160
|
sfparty combine --git=HEAD~1..HEAD --append --delta --package=deploy/package.xml --destructive=deploy/destructiveChanges/destructiveChanges.xml
|
|
161
161
|
```
|
|
162
|
+
#### Previous Commit to Current and output to different target directory
|
|
163
|
+
|
|
164
|
+
The default target is the package file specified in the `sfdx-project.json` file. You can use the `--target` parameter if you want the files to be created in a different location. sfparty will create the /main/default/* directories accordingly.
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
sfparty combine --git=HEAD~1..HEAD --append --delta --package=deploy/package.xml --destructive=deploy/destructiveChanges/destructiveChanges.xml --target=deployDir/force-app
|
|
168
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ds-sfdc/sfparty",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.6",
|
|
4
4
|
"description": "Salesforce metadata XML splitter for CI/CD",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"axios": "^1.2.2",
|
|
27
27
|
"chai-as-promised": "^7.1.1",
|
|
28
|
-
"
|
|
28
|
+
"cli-color": "^2.0.3",
|
|
29
29
|
"cli-spinners": "^2.7.0",
|
|
30
30
|
"convert-hrtime": "^5.0.0",
|
|
31
31
|
"js-yaml": "^4.1.0",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"marked": "^4.2.12",
|
|
34
34
|
"marked-terminal": "^5.1.1",
|
|
35
35
|
"pinst": "^3.0.0",
|
|
36
|
+
"semver": "^7.3.8",
|
|
36
37
|
"util": "^0.10.3",
|
|
37
38
|
"winston": "^3.8.2",
|
|
38
39
|
"xml2js": "^0.4.23",
|
package/src/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict'
|
|
3
|
-
import { exec } from 'child_process'
|
|
3
|
+
import { exec, spawnSync } from 'child_process'
|
|
4
4
|
import { readFileSync } from 'fs'
|
|
5
5
|
import path from 'path'
|
|
6
6
|
import yargs from 'yargs'
|
|
7
7
|
import { hideBin } from 'yargs/helpers'
|
|
8
8
|
import winston from 'winston'
|
|
9
|
-
import
|
|
9
|
+
import clc from 'cli-color'
|
|
10
10
|
import convertHrtime from 'convert-hrtime'
|
|
11
11
|
import axios from 'axios'
|
|
12
12
|
import { marked } from 'marked'
|
|
@@ -51,7 +51,7 @@ global.logger = winston.createLogger({
|
|
|
51
51
|
|
|
52
52
|
global.icons = {
|
|
53
53
|
"warn": '🔕',
|
|
54
|
-
"success":
|
|
54
|
+
"success": clc.greenBright('✔'),
|
|
55
55
|
"fail": '❗',
|
|
56
56
|
"working": '⏳',
|
|
57
57
|
"party": '🎉',
|
|
@@ -110,7 +110,7 @@ global.metaTypes = {
|
|
|
110
110
|
let types = []
|
|
111
111
|
const packageDir = getRootPath()
|
|
112
112
|
|
|
113
|
-
let errorMessage =
|
|
113
|
+
let errorMessage = clc.red('Please specify the action of ' + clc.whiteBright.bgRedBright('split') + ' or ' + clc.whiteBright.bgRedBright('combine') + '.')
|
|
114
114
|
|
|
115
115
|
displayHeader() // display header mast
|
|
116
116
|
|
|
@@ -128,7 +128,7 @@ yargs(hideBin(process.argv))
|
|
|
128
128
|
alias: 'test',
|
|
129
129
|
handler: (argv) => {
|
|
130
130
|
// THIS IS A PLACE TO TEST NEW CODE
|
|
131
|
-
global.logger.info(
|
|
131
|
+
global.logger.info(clc.magentaBright(`${global.icons.party} TEST ${global.icons.party}`))
|
|
132
132
|
}
|
|
133
133
|
})
|
|
134
134
|
.command({
|
|
@@ -139,9 +139,17 @@ yargs(hideBin(process.argv))
|
|
|
139
139
|
.check(yargCheck)
|
|
140
140
|
},
|
|
141
141
|
handler: (argv) => {
|
|
142
|
-
checkVersion(axios,
|
|
142
|
+
checkVersion({axios, spawnSync, currentVersion: pkgObj.default.version, update: true})
|
|
143
143
|
}
|
|
144
144
|
})
|
|
145
|
+
.command({
|
|
146
|
+
command: '[version]',
|
|
147
|
+
alias: 'version',
|
|
148
|
+
builder: (yargs) => {
|
|
149
|
+
yargs
|
|
150
|
+
.check(yargCheck)
|
|
151
|
+
},
|
|
152
|
+
})
|
|
145
153
|
.command({
|
|
146
154
|
command: '[split]',
|
|
147
155
|
alias: 'split',
|
|
@@ -154,7 +162,7 @@ yargs(hideBin(process.argv))
|
|
|
154
162
|
.check(yargCheck)
|
|
155
163
|
},
|
|
156
164
|
handler: (argv) => {
|
|
157
|
-
checkVersion(axios,
|
|
165
|
+
checkVersion({axios, spawnSync, currentVersion: pkgObj.default.version})
|
|
158
166
|
global.format = argv.format
|
|
159
167
|
splitHandler(argv, processStartTime)
|
|
160
168
|
}
|
|
@@ -171,7 +179,7 @@ yargs(hideBin(process.argv))
|
|
|
171
179
|
.check(yargCheck)
|
|
172
180
|
},
|
|
173
181
|
handler: (argv) => {
|
|
174
|
-
checkVersion(axios,
|
|
182
|
+
checkVersion({axios, spawnSync, currentVersion: pkgObj.default.version})
|
|
175
183
|
global.format = argv.format
|
|
176
184
|
const startProm = new Promise((resolve, reject) => {
|
|
177
185
|
if (argv.git !== undefined) {
|
|
@@ -185,10 +193,10 @@ yargs(hideBin(process.argv))
|
|
|
185
193
|
global.git.latest = data.latestCommit
|
|
186
194
|
global.git.last = data.lastCommit
|
|
187
195
|
if (data.last === undefined) {
|
|
188
|
-
console.log(`${
|
|
196
|
+
console.log(`${clc.yellowBright('git mode')} ${clc.bgMagentaBright('not active:')} no prior commit - processing all`)
|
|
189
197
|
resolve(false)
|
|
190
198
|
} else {
|
|
191
|
-
console.log(`${
|
|
199
|
+
console.log(`${clc.yellowBright('git mode')} ${clc.magentaBright('active:')} ${clc.bgBlackBright(data.lastCommit) + '..' + clc.bgBlackBright(data.latestCommit)}`)
|
|
192
200
|
console.log()
|
|
193
201
|
const diff = git.diff(global.__basedir, `${data.lastCommit}..${data.latestCommit}`)
|
|
194
202
|
diff
|
|
@@ -207,7 +215,7 @@ yargs(hideBin(process.argv))
|
|
|
207
215
|
throw error
|
|
208
216
|
})
|
|
209
217
|
} else {
|
|
210
|
-
console.log(`${
|
|
218
|
+
console.log(`${clc.yellowBright('git mode')} ${clc.magentaBright('active:')} ${clc.bgBlackBright(gitRef)}`)
|
|
211
219
|
console.log()
|
|
212
220
|
const diff = git.diff(global.__basedir, gitRef)
|
|
213
221
|
diff
|
|
@@ -253,8 +261,12 @@ function yargCheck(argv, options) {
|
|
|
253
261
|
!options.array.includes(key)
|
|
254
262
|
)
|
|
255
263
|
|
|
264
|
+
if (!argv._.includes('update')) {
|
|
265
|
+
checkVersion({axios, spawnSync, currentVersion: pkgObj.default.version, update: false})
|
|
266
|
+
}
|
|
267
|
+
|
|
256
268
|
if (invalidKeys.length > 0) {
|
|
257
|
-
const invalidKeysWithColor = invalidKeys.map(key =>
|
|
269
|
+
const invalidKeysWithColor = invalidKeys.map(key => clc.redBright(key))
|
|
258
270
|
throw new Error(`Invalid options specified: ${invalidKeysWithColor.join(', ')}`)
|
|
259
271
|
}
|
|
260
272
|
|
|
@@ -270,13 +282,13 @@ function yargCheck(argv, options) {
|
|
|
270
282
|
if (types.length > 1) {
|
|
271
283
|
// if using multiple types you cannot specify name
|
|
272
284
|
if ((typeof name != 'undefined' && name != '')) {
|
|
273
|
-
throw new Error(
|
|
285
|
+
throw new Error(clc.redBright('You cannot specify ' + clc.whiteBright.bgRedBright('--name') + ' when using multiple types.'))
|
|
274
286
|
}
|
|
275
287
|
} else {
|
|
276
288
|
switch (argv.type) {
|
|
277
289
|
case 'label':
|
|
278
290
|
if ((typeof name != 'undefined' && name != '')) {
|
|
279
|
-
throw new Error(
|
|
291
|
+
throw new Error(clc.redBright('You cannot specify ' + clc.whiteBright.bgRedBright('--name') + ' when using label.'))
|
|
280
292
|
}
|
|
281
293
|
break
|
|
282
294
|
}
|
|
@@ -291,11 +303,11 @@ function displayMessageAndDuration(startTime, message) {
|
|
|
291
303
|
let minutes = Math.floor((executionTime.seconds + Math.round(executionTime.milliseconds / 100000)) / 60)
|
|
292
304
|
let seconds = Math.round((executionTime.seconds + Math.round(executionTime.milliseconds / 100000)) % 60)
|
|
293
305
|
if (minutes == 0 && seconds == 0) {
|
|
294
|
-
durationMessage = message +
|
|
306
|
+
durationMessage = message + clc.magentaBright(`<1s`)
|
|
295
307
|
} else if (minutes > 0) {
|
|
296
|
-
durationMessage = message +
|
|
308
|
+
durationMessage = message + clc.magentaBright(`${minutes}m ${seconds}s`)
|
|
297
309
|
} else {
|
|
298
|
-
durationMessage = message +
|
|
310
|
+
durationMessage = message + clc.magentaBright(`${seconds}s`)
|
|
299
311
|
}
|
|
300
312
|
console.log('\n' + durationMessage)
|
|
301
313
|
}
|
|
@@ -384,8 +396,8 @@ function processSplit(typeItem, argv) {
|
|
|
384
396
|
|
|
385
397
|
if (processed.total == 0) resolve(true)
|
|
386
398
|
|
|
387
|
-
console.log(`${
|
|
388
|
-
console.log(`${
|
|
399
|
+
console.log(`${clc.bgBlackBright('Source path:')} ${sourceDir}`)
|
|
400
|
+
console.log(`${clc.bgBlackBright('Target path:')} ${targetDir}`)
|
|
389
401
|
console.log()
|
|
390
402
|
console.log(`Splitting a total of ${processed.total} file(s)`)
|
|
391
403
|
console.log()
|
|
@@ -412,7 +424,7 @@ function processSplit(typeItem, argv) {
|
|
|
412
424
|
})
|
|
413
425
|
})
|
|
414
426
|
Promise.allSettled(promList).then((results) => {
|
|
415
|
-
let message = `Split ${
|
|
427
|
+
let message = `Split ${clc.bgBlackBright((processed.current > promList.length) ? promList.length : processed.current)} file(s) ${(processed.errors > 0) ? 'with ' + clc.bgBlackBright.red(processed.errors) + ' error(s) ' : ''}in `
|
|
416
428
|
displayMessageAndDuration(startTime, message)
|
|
417
429
|
resolve(true)
|
|
418
430
|
})
|
|
@@ -496,7 +508,7 @@ function processCombine(typeItem, argv) {
|
|
|
496
508
|
}
|
|
497
509
|
|
|
498
510
|
processed.total = processList.length
|
|
499
|
-
console.log(`${
|
|
511
|
+
console.log(`${clc.bgBlackBright(processed.total)} ${typeItem} file(s) to process`)
|
|
500
512
|
|
|
501
513
|
// Abort if there are no files to process
|
|
502
514
|
if (processed.total == 0) {
|
|
@@ -505,8 +517,8 @@ function processCombine(typeItem, argv) {
|
|
|
505
517
|
}
|
|
506
518
|
|
|
507
519
|
console.log()
|
|
508
|
-
console.log(`${
|
|
509
|
-
console.log(`${
|
|
520
|
+
console.log(`${clc.bgBlackBright('Source path:')} ${sourceDir}`)
|
|
521
|
+
console.log(`${clc.bgBlackBright('Target path:')} ${targetDir}`)
|
|
510
522
|
console.log()
|
|
511
523
|
|
|
512
524
|
const promList = []
|
|
@@ -538,7 +550,7 @@ function processCombine(typeItem, argv) {
|
|
|
538
550
|
errors++
|
|
539
551
|
}
|
|
540
552
|
})
|
|
541
|
-
let message = `Combined ${
|
|
553
|
+
let message = `Combined ${clc.bgBlackBright(successes)} file(s) ${(errors > 0) ? 'with ' + clc.bgBlackBright(errors) + 'error(s) ' : ''}in `
|
|
542
554
|
displayMessageAndDuration(startTime, message)
|
|
543
555
|
resolve(true)
|
|
544
556
|
})
|
|
@@ -547,8 +559,8 @@ function processCombine(typeItem, argv) {
|
|
|
547
559
|
|
|
548
560
|
function gitFiles(data) {
|
|
549
561
|
data.forEach(item => {
|
|
550
|
-
if (item.path.indexOf(packageDir + '-party'
|
|
551
|
-
const pathArray = item.path.split(
|
|
562
|
+
if (item.path.indexOf(packageDir + '-party/') == 0) {
|
|
563
|
+
const pathArray = item.path.split('/')
|
|
552
564
|
if (pathArray.length > 3) {
|
|
553
565
|
if (getDirectories().includes(pathArray[3])) {
|
|
554
566
|
switch (item.action) {
|
|
@@ -603,13 +615,13 @@ function displayHeader() {
|
|
|
603
615
|
vertical: '│',
|
|
604
616
|
}
|
|
605
617
|
let versionString = `sfparty v${pkgObj.default.version}${(process.stdout.columns > pkgObj.default.description.length + 15) ? ' - ' + pkgObj.default.description : ''}`
|
|
606
|
-
let titleMessage = `${global.icons.party} ${
|
|
618
|
+
let titleMessage = `${global.icons.party} ${clc.yellowBright(versionString)} ${global.icons.party}`
|
|
607
619
|
titleMessage = titleMessage.padEnd((process.stdout.columns / 2) + versionString.length / 1.65)
|
|
608
620
|
titleMessage = titleMessage.padStart(process.stdout.columns)
|
|
609
|
-
titleMessage =
|
|
610
|
-
console.log(`${
|
|
621
|
+
titleMessage = clc.blackBright(box.vertical) + ' ' + titleMessage + ' ' + clc.blackBright(box.vertical)
|
|
622
|
+
console.log(`${clc.blackBright(box.topLeft + box.horizontal.repeat(process.stdout.columns - 2) + box.topRight)}`)
|
|
611
623
|
console.log(titleMessage)
|
|
612
|
-
console.log(`${
|
|
624
|
+
console.log(`${clc.blackBright(box.bottomLeft + box.horizontal.repeat(process.stdout.columns - 2) + box.bottomRight)}`)
|
|
613
625
|
console.log()
|
|
614
626
|
}
|
|
615
627
|
|
package/src/lib/checkVersion.js
CHANGED
|
@@ -1,42 +1,74 @@
|
|
|
1
|
-
|
|
1
|
+
import clc from 'cli-color'
|
|
2
|
+
import semver from 'semver'
|
|
3
|
+
|
|
4
|
+
class NpmNotInstalledError extends Error {
|
|
5
|
+
constructor(message) {
|
|
6
|
+
super(message)
|
|
7
|
+
this.name = "NpmNotInstalledError"
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
class PackageNotFoundError extends Error {
|
|
12
|
+
constructor(message) {
|
|
13
|
+
super(message)
|
|
14
|
+
this.name = "PackageNotFoundError"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
class UpdateError extends Error {
|
|
19
|
+
constructor(message) {
|
|
20
|
+
super(message)
|
|
21
|
+
this.name = "UpdateError"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function checkVersion({axios, spawnSync, currentVersion, update = false}) {
|
|
2
26
|
try {
|
|
3
|
-
const { data } = await axios.get('https://registry.npmjs.org/@ds-sfdc/sfparty'
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
27
|
+
const { data } = await axios.get('https://registry.npmjs.org/@ds-sfdc/sfparty', {
|
|
28
|
+
params: {
|
|
29
|
+
field: 'dist-tags.latest'
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
const latestVersion = data['dist-tags'].latest
|
|
33
|
+
if (semver.gt(latestVersion, currentVersion)) {
|
|
34
|
+
let icon
|
|
35
|
+
const version = clc.bgCyanBright(data['dist-tags'].latest)
|
|
36
|
+
if (update) {
|
|
37
|
+
icon = global.icons.working
|
|
38
|
+
} else {
|
|
39
|
+
icon = global.icons.fail
|
|
40
|
+
}
|
|
41
|
+
console.log(`${icon} A newer version ${version} is available.`)
|
|
7
42
|
if (!update) {
|
|
8
|
-
console.log(`Please upgrade by running ${
|
|
43
|
+
console.log(`Please upgrade by running ${clc.cyanBright('sfparty update')}`)
|
|
9
44
|
return 'A newer version'
|
|
10
45
|
} else {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
} else {
|
|
22
|
-
console.log(stdout)
|
|
23
|
-
console.log(stderr)
|
|
24
|
-
resolve(true)
|
|
25
|
-
}
|
|
26
|
-
})
|
|
46
|
+
let command = 'npm i -g @ds-sfdc/sfparty@latest'.split(' ')
|
|
47
|
+
console.log(`Updating the application using ${clc.cyanBright(command.join(' '))}`)
|
|
48
|
+
try {
|
|
49
|
+
const npmVersion = spawnSync('npm', ['-v'])
|
|
50
|
+
if (npmVersion.stderr && npmVersion.stderr.toString().trim() === 'command not found') {
|
|
51
|
+
throw new NpmNotInstalledError("npm is not installed on this system. Please install npm and run the command again.")
|
|
52
|
+
}
|
|
53
|
+
const update = spawnSync(command[0], command.slice(1))
|
|
54
|
+
if (update.status !== 0) {
|
|
55
|
+
throw new UpdateError("Error updating the application.")
|
|
27
56
|
}
|
|
28
|
-
|
|
57
|
+
console.log("Application updated successfully.")
|
|
58
|
+
} catch (err) {
|
|
59
|
+
throw err
|
|
60
|
+
}
|
|
29
61
|
}
|
|
30
62
|
} else {
|
|
31
63
|
if (update) {
|
|
32
64
|
console.log(`${global.icons.success} You are on the latest version.`)
|
|
33
|
-
return 'You are on the latest version'
|
|
34
65
|
}
|
|
66
|
+
return 'You are on the latest version'
|
|
35
67
|
}
|
|
36
68
|
} catch (error) {
|
|
37
|
-
|
|
69
|
+
if (error.response && error.response.status === 404) {
|
|
70
|
+
error = new PackageNotFoundError("Package not found on the npm registry")
|
|
71
|
+
}
|
|
38
72
|
throw error
|
|
39
73
|
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
74
|
+
}
|
package/src/lib/fileUtils.js
CHANGED
package/src/lib/gitUtils.js
CHANGED
|
@@ -57,7 +57,7 @@ export function diff(dir, gitRef = 'HEAD', existsSyncStub = existsSync, execSync
|
|
|
57
57
|
|
|
58
58
|
let data = ''
|
|
59
59
|
try {
|
|
60
|
-
data = execSyncStub(`git diff --name-status --oneline --relative ${gitRef}
|
|
60
|
+
data = execSyncStub(`git diff --name-status --oneline --relative ${gitRef} -- *-party/*`, { cwd: dir, maxBuffer: 1024 * 1024 * 10 }).toString()
|
|
61
61
|
} catch (error) {
|
|
62
62
|
reject(error)
|
|
63
63
|
}
|
package/src/lib/packageUtil.js
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
import path from 'path'
|
|
2
|
-
import chalk from 'chalk'
|
|
3
|
-
import * as xml2js from 'xml2js'
|
|
4
|
-
import * as fileUtils from './fileUtils.js'
|
|
5
2
|
import * as packageDefinition from '../meta/Package.js'
|
|
6
3
|
|
|
7
4
|
export class Package {
|
|
@@ -12,30 +9,45 @@ export class Package {
|
|
|
12
9
|
this.packageJSON = undefined
|
|
13
10
|
}
|
|
14
11
|
|
|
15
|
-
getPackageXML() {
|
|
12
|
+
getPackageXML(fileUtils) {
|
|
16
13
|
const that = this
|
|
17
14
|
return new Promise((resolve, reject) => {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
15
|
+
try {
|
|
16
|
+
if (that.xmlPath === undefined) throw new Error('Package not initialized')
|
|
17
|
+
|
|
18
|
+
let fileName = path.resolve(that.xmlPath)
|
|
19
|
+
if (fileUtils.fileExists(fileName) && global.git.append) {
|
|
20
|
+
let data = fileUtils.readFile(fileName)
|
|
21
|
+
data
|
|
22
|
+
.then((json) => {
|
|
23
|
+
try {
|
|
24
|
+
if (json === undefined || Object.keys(json).length === 0 ) json = packageDefinition.metadataDefinition.emptyPackage
|
|
25
|
+
processJSON(that, json, fileUtils)
|
|
26
|
+
resolve('existing')
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.error(error)
|
|
29
|
+
reject(error)
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
.catch((error) => {
|
|
33
|
+
reject(error)
|
|
34
|
+
})
|
|
35
|
+
} else {
|
|
36
|
+
try {
|
|
37
|
+
let json = JSON.parse(JSON.stringify(packageDefinition.metadataDefinition.emptyPackage))
|
|
25
38
|
processJSON(that, json)
|
|
26
|
-
resolve('
|
|
27
|
-
})
|
|
28
|
-
.catch((error) => {
|
|
39
|
+
resolve('not found')
|
|
40
|
+
} catch (error) {
|
|
29
41
|
reject(error)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
resolve('not found')
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
} catch (error) {
|
|
45
|
+
reject(error)
|
|
35
46
|
}
|
|
47
|
+
|
|
36
48
|
})
|
|
37
49
|
|
|
38
|
-
function processJSON(that, json) {
|
|
50
|
+
function processJSON(that, json, fileUtils) {
|
|
39
51
|
try {
|
|
40
52
|
json.Package.version = fileUtils.readFile(path.join(global.__basedir, 'sfdx-project.json')).sourceApiVersion
|
|
41
53
|
} catch (error) {
|
|
@@ -49,8 +61,9 @@ export class Package {
|
|
|
49
61
|
addMember(type, member) {
|
|
50
62
|
const that = this
|
|
51
63
|
if (that.packageJSON === undefined) throw new Error('getPackageXML must be called before adding members')
|
|
52
|
-
if (type === undefined) throw new Error('An undefined type was received when attempting to add a member')
|
|
53
|
-
if (member === undefined) throw new Error('An undefined member was received when attempting to add a member')
|
|
64
|
+
if (type === undefined || type.trim() == '') throw new Error('An undefined type was received when attempting to add a member')
|
|
65
|
+
if (member === undefined || member.trim() == '') throw new Error('An undefined member was received when attempting to add a member')
|
|
66
|
+
if (member.indexOf(`.${global.format}`) !== -1) throw new Error('Part file received as member is not allowed')
|
|
54
67
|
|
|
55
68
|
const packageJSON = that.packageJSON
|
|
56
69
|
let foundMember = false
|
|
@@ -77,82 +90,99 @@ export class Package {
|
|
|
77
90
|
})
|
|
78
91
|
}
|
|
79
92
|
} catch (error) {
|
|
80
|
-
|
|
93
|
+
throw error
|
|
81
94
|
}
|
|
82
95
|
})
|
|
83
96
|
|
|
84
97
|
// exit if member already exists
|
|
85
98
|
if (foundMember) return
|
|
86
99
|
if (foundAsterisk) {
|
|
87
|
-
// global.logger.warn(`Found ${chalk.bgBlackBright('*')} in type: ${type}.`)
|
|
88
100
|
return
|
|
89
101
|
}
|
|
90
102
|
|
|
91
|
-
|
|
92
|
-
typeJSON
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
103
|
+
try {
|
|
104
|
+
if (typeJSON !== undefined) {
|
|
105
|
+
typeJSON.members.push(member)
|
|
106
|
+
typeJSON.members.sort()
|
|
107
|
+
} else {
|
|
108
|
+
typeJSON = JSON.parse(JSON.stringify(packageDefinition.metadataDefinition.emptyNode))
|
|
109
|
+
typeJSON.name = type
|
|
110
|
+
typeJSON.members.push(member)
|
|
111
|
+
|
|
112
|
+
packageJSON.Package.types.push(typeJSON)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
packageJSON.Package.types.sort((a, b) => {
|
|
116
|
+
if (a.name < b.name) return -1
|
|
117
|
+
if (a.name > b.name) return 1
|
|
118
|
+
return 0
|
|
119
|
+
})
|
|
120
|
+
} catch (error) {
|
|
121
|
+
throw error
|
|
100
122
|
}
|
|
101
|
-
|
|
102
|
-
packageJSON.Package.types.sort((a, b) => {
|
|
103
|
-
if (a.name < b.name) return -1
|
|
104
|
-
if (a.name > b.name) return 1
|
|
105
|
-
return 0
|
|
106
|
-
})
|
|
107
123
|
}
|
|
108
124
|
|
|
109
|
-
savePackage() {
|
|
125
|
+
savePackage(xml2js, fileUtils) {
|
|
110
126
|
let that = this
|
|
111
127
|
let json = that.packageJSON.Package
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
128
|
+
try {
|
|
129
|
+
json.$.xmlns = json.$.xmlns.replace('http:', 'https:')
|
|
130
|
+
const version = json.version
|
|
131
|
+
delete json.version
|
|
132
|
+
json.version = version
|
|
133
|
+
|
|
134
|
+
const builder = new xml2js.Builder(
|
|
135
|
+
{
|
|
136
|
+
cdata: false,
|
|
137
|
+
rootName: 'Package',
|
|
138
|
+
xmldec: { 'version': '1.0', 'encoding': 'UTF-8' }
|
|
139
|
+
}
|
|
140
|
+
)
|
|
141
|
+
let fileName = that.xmlPath
|
|
142
|
+
fileUtils.createDirectory(path.dirname(fileName))
|
|
143
|
+
|
|
144
|
+
const xml = builder.buildObject(json)
|
|
145
|
+
|
|
146
|
+
fileUtils.writeFile(fileName, xml)
|
|
147
|
+
|
|
148
|
+
} catch (error) {
|
|
149
|
+
throw error
|
|
150
|
+
}
|
|
127
151
|
}
|
|
128
152
|
}
|
|
129
153
|
|
|
130
154
|
function transformJSON(json) {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
155
|
+
try {
|
|
156
|
+
json.forEach(typesItem => {
|
|
157
|
+
Object.keys(typesItem).forEach(key => {
|
|
158
|
+
let jsonString = JSON.stringify(typesItem[key], (name, value) => {
|
|
159
|
+
if (key == 'members') {
|
|
160
|
+
return value
|
|
161
|
+
} else {
|
|
162
|
+
return xml2json(value)
|
|
163
|
+
}
|
|
164
|
+
})
|
|
165
|
+
typesItem[key] = JSON.parse(jsonString)
|
|
139
166
|
})
|
|
140
|
-
typesItem[key] = JSON.parse(jsonString)
|
|
141
167
|
})
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
168
|
+
|
|
169
|
+
return
|
|
170
|
+
} catch (error) {
|
|
171
|
+
throw error
|
|
172
|
+
}
|
|
146
173
|
|
|
147
|
-
function xml2json(currentValue) {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
174
|
+
function xml2json(currentValue) {
|
|
175
|
+
try {
|
|
176
|
+
if (Array.isArray(currentValue)) {
|
|
177
|
+
if (currentValue.length == 1) {
|
|
178
|
+
currentValue = currentValue[0].toString().trim()
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (currentValue == 'true') currentValue = true
|
|
182
|
+
if (currentValue == 'false') currentValue = false
|
|
183
|
+
return currentValue
|
|
184
|
+
} catch (error) {
|
|
185
|
+
throw error
|
|
151
186
|
}
|
|
152
187
|
}
|
|
153
|
-
if (currentValue == 'true') currentValue = true
|
|
154
|
-
if (currentValue == 'false') currentValue = false
|
|
155
|
-
return currentValue
|
|
156
188
|
}
|
|
157
|
-
|
|
158
|
-
// Create JEST tests that cover 100% of the code
|
package/src/party/combine.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from 'path'
|
|
2
2
|
import logUpdate from 'log-update'
|
|
3
|
-
import
|
|
3
|
+
import clc from 'cli-color'
|
|
4
4
|
import convertHrtime from 'convert-hrtime'
|
|
5
5
|
import cliSpinners from 'cli-spinners'
|
|
6
6
|
import os from 'node:os'
|
|
@@ -110,8 +110,8 @@ export class Combine {
|
|
|
110
110
|
if (global.git.enabled) {
|
|
111
111
|
that.#addPkg = new packageUtil.Package(that.addManifest)
|
|
112
112
|
that.#desPkg = new packageUtil.Package(that.desManifest)
|
|
113
|
-
const prom1 = that.#addPkg.getPackageXML()
|
|
114
|
-
const prom2 = that.#desPkg.getPackageXML()
|
|
113
|
+
const prom1 = that.#addPkg.getPackageXML(fileUtils)
|
|
114
|
+
const prom2 = that.#desPkg.getPackageXML(fileUtils)
|
|
115
115
|
|
|
116
116
|
Promise.allSettled([prom1, prom2])
|
|
117
117
|
.then((results) => {
|
|
@@ -139,7 +139,7 @@ export class Combine {
|
|
|
139
139
|
} else {
|
|
140
140
|
logUpdate(that.#spinnerMessage
|
|
141
141
|
.replace('[%1]', that.sequence.toString().padStart(that.total.toString().length, ' '))
|
|
142
|
-
.replace('[%2]', `. ${
|
|
142
|
+
.replace('[%2]', `. ${clc.redBright('source not found - removing XML file')}`)
|
|
143
143
|
.replace('[%3]', ``)
|
|
144
144
|
.replace('[%4]', `${global.icons.delete} `)
|
|
145
145
|
.replace('[%5]', that.#fileName.shortName)
|
|
@@ -155,8 +155,8 @@ export class Combine {
|
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
function savePackageXML(that) {
|
|
158
|
-
that.#addPkg.savePackage()
|
|
159
|
-
that.#desPkg.savePackage()
|
|
158
|
+
that.#addPkg.savePackage(xml2js, fileUtils)
|
|
159
|
+
that.#desPkg.savePackage(xml2js, fileUtils)
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
function getXML(that) {
|
|
@@ -167,14 +167,14 @@ export class Combine {
|
|
|
167
167
|
processed.current++
|
|
168
168
|
|
|
169
169
|
that.#startTime = process.hrtime.bigint()
|
|
170
|
-
that.#spinnerMessage = `[%1] of ${that.total} - ${that.#root}: [%4]${
|
|
170
|
+
that.#spinnerMessage = `[%1] of ${that.total} - ${that.#root}: [%4]${clc.yellowBright('[%5]')}[%2][%3]`
|
|
171
171
|
|
|
172
172
|
try {
|
|
173
173
|
that.#types.forEach(key => {
|
|
174
174
|
// display message
|
|
175
175
|
logUpdate(that.#spinnerMessage
|
|
176
176
|
.replace('[%1]', that.sequence.toString().padStart(that.total.toString().length, ' '))
|
|
177
|
-
.replace('[%2]', `\n${
|
|
177
|
+
.replace('[%2]', `\n${clc.magentaBright(nextFrame(that))} ${key}`)
|
|
178
178
|
.replace('[%3]', `${that.#errorMessage}`)
|
|
179
179
|
.replace('[%4]', `${global.icons.working} `)
|
|
180
180
|
.replace('[%5]', `${that.#fileName.shortName} `)
|
|
@@ -199,7 +199,7 @@ export class Combine {
|
|
|
199
199
|
} else if (that.metadataDefinition.directories.includes(key)) {
|
|
200
200
|
processDirectory(that, key)
|
|
201
201
|
} else {
|
|
202
|
-
global.logger.warn(`Unexpected metadata type: ${
|
|
202
|
+
global.logger.warn(`Unexpected metadata type: ${clc.redBright(key)}`)
|
|
203
203
|
}
|
|
204
204
|
})
|
|
205
205
|
return true
|
|
@@ -234,7 +234,7 @@ export class Combine {
|
|
|
234
234
|
fileList.forEach((file, index) => {
|
|
235
235
|
logUpdate(that.#spinnerMessage
|
|
236
236
|
.replace('[%1]', that.sequence.toString().padStart(that.total.toString().length, ' '))
|
|
237
|
-
.replace('[%2]', `\n${
|
|
237
|
+
.replace('[%2]', `\n${clc.magentaBright(nextFrame(that))} ${key} - ${index + 1} of ${fileList.length} - ${clc.magentaBright(file)}`)
|
|
238
238
|
.replace('[%3]', `${that.#errorMessage}`)
|
|
239
239
|
.replace('[%4]', `${global.icons.working} `)
|
|
240
240
|
.replace('[%5]', `${that.#fileName.shortName} `)
|
|
@@ -263,7 +263,7 @@ export class Combine {
|
|
|
263
263
|
fileObj.shortName === undefined ||
|
|
264
264
|
fileObj.fullName === undefined
|
|
265
265
|
) {
|
|
266
|
-
global.displayError(`${global.icons.warn} Invalid file information passed ${
|
|
266
|
+
global.displayError(`${global.icons.warn} Invalid file information passed ${clc.redBright(fileObj)}`, true)
|
|
267
267
|
}
|
|
268
268
|
|
|
269
269
|
if (!fileUtils.fileExists(fileObj.fullName)) {
|
package/src/party/split.js
CHANGED
|
@@ -4,7 +4,7 @@ import path from 'path'
|
|
|
4
4
|
import { readFile } from 'fs'
|
|
5
5
|
import { Parser } from 'xml2js'
|
|
6
6
|
import logUpdate from 'log-update'
|
|
7
|
-
import
|
|
7
|
+
import clc from 'cli-color'
|
|
8
8
|
import convertHrtime from 'convert-hrtime'
|
|
9
9
|
import cliSpinners from 'cli-spinners'
|
|
10
10
|
import * as fileUtils from '../lib/fileUtils.js'
|
|
@@ -128,7 +128,7 @@ export class Split {
|
|
|
128
128
|
})
|
|
129
129
|
|
|
130
130
|
function processJSON(that, json, baseDir) {
|
|
131
|
-
that.#spinnerMessage = `[%1] of ${that.total} - ${that.#root}: [%4]${
|
|
131
|
+
that.#spinnerMessage = `[%1] of ${that.total} - ${that.#root}: [%4]${clc.yellowBright(that.#fileName.shortName)}[%2][%3]`
|
|
132
132
|
|
|
133
133
|
let targetDir = baseDir
|
|
134
134
|
if (processed.type != that.#root) {
|
|
@@ -140,7 +140,7 @@ export class Split {
|
|
|
140
140
|
that.sequence = processed.current
|
|
141
141
|
logUpdate(that.#spinnerMessage
|
|
142
142
|
.replace('[%1]', that.sequence.toString().padStart(that.total.toString().length, ' '))
|
|
143
|
-
.replace('[%2]', `\n${
|
|
143
|
+
.replace('[%2]', `\n${clc.magentaBright(nextFrame(that))} ${key}`)
|
|
144
144
|
.replace('[%3]', `${that.#errorMessage}`)
|
|
145
145
|
.replace('[%4]', `${global.icons.working} `)
|
|
146
146
|
)
|
|
@@ -176,7 +176,7 @@ export class Split {
|
|
|
176
176
|
that.sequence = processed.current
|
|
177
177
|
logUpdate(that.#spinnerMessage
|
|
178
178
|
.replace('[%1]', that.sequence.toString().padStart(that.total.toString().length, ' '))
|
|
179
|
-
.replace('[%2]', `\n${
|
|
179
|
+
.replace('[%2]', `\n${clc.magentaBright(nextFrame(that))} ${key}`)
|
|
180
180
|
.replace('[%3]', `${that.#errorMessage}`)
|
|
181
181
|
.replace('[%4]', `${global.icons.working} `)
|
|
182
182
|
)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import axios from 'axios'
|
|
2
|
+
import { spawnSync } from 'child_process'
|
|
3
|
+
import clc from 'cli-color'
|
|
4
|
+
import { checkVersion } from '../src/lib/checkVersion.js'
|
|
5
|
+
|
|
6
|
+
global.icons = {
|
|
7
|
+
"success": clc.greenBright('✔'),
|
|
8
|
+
"fail": '❗',
|
|
9
|
+
"working": '⏳',
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
jest.mock('axios')
|
|
13
|
+
jest.mock('child_process', () => ({ spawnSync: jest.fn() }))
|
|
14
|
+
|
|
15
|
+
describe('checkVersion', () => {
|
|
16
|
+
let spy
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
jest.clearAllMocks()
|
|
19
|
+
spy = jest.spyOn(console, 'log')
|
|
20
|
+
})
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
spy.mockRestore()
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('should return "A newer version" if a newer version is available', async () => {
|
|
26
|
+
axios.get.mockResolvedValue({ data: { 'dist-tags': { latest: '2.0.0' } } })
|
|
27
|
+
const result = await checkVersion({axios, spawnSync, currentVersion: '1.0.0'})
|
|
28
|
+
expect(result).toBe('A newer version')
|
|
29
|
+
}, { silent: true })
|
|
30
|
+
|
|
31
|
+
it('should return "You are on the latest version" if the current version is the latest version', async () => {
|
|
32
|
+
axios.get.mockResolvedValue({ data: { 'dist-tags': { latest: '1.0.0' } } })
|
|
33
|
+
const result = await checkVersion({axios, spawnSync, currentVersion: '1.0.0'})
|
|
34
|
+
expect(result).toBe('You are on the latest version')
|
|
35
|
+
}, { silent: true })
|
|
36
|
+
|
|
37
|
+
it('should throw a NpmNotInstalledError if npm is not installed', async () => {
|
|
38
|
+
axios.get.mockResolvedValue({ data: { 'dist-tags': { latest: '2.0.0' } } })
|
|
39
|
+
spawnSync.mockReturnValue({ status: 1, stderr: { toString: () => 'command not found' } })
|
|
40
|
+
try {
|
|
41
|
+
await checkVersion({axios, spawnSync, currentVersion: '1.0.0', update: true})
|
|
42
|
+
} catch (err) {
|
|
43
|
+
expect(err.name).toBe('NpmNotInstalledError')
|
|
44
|
+
expect(err.message).toBe('npm is not installed on this system. Please install npm and run the command again.')
|
|
45
|
+
}
|
|
46
|
+
}, { silent: true })
|
|
47
|
+
|
|
48
|
+
it('should throw a PackageNotFoundError if the package is not found on the npm registry', async () => {
|
|
49
|
+
axios.get.mockRejectedValue({ response: { status: 404 } });
|
|
50
|
+
try {
|
|
51
|
+
await checkVersion({axios, spawnSync, currentVersion: '1.0.0'});
|
|
52
|
+
} catch (err) {
|
|
53
|
+
expect(err.name).toBe('PackageNotFoundError');
|
|
54
|
+
expect(err.message).toBe('Package not found on the npm registry');
|
|
55
|
+
}
|
|
56
|
+
}, { silent: true });
|
|
57
|
+
|
|
58
|
+
it('should throw a UpdateError if an error occurs while updating the package', async () => {
|
|
59
|
+
axios.get.mockResolvedValue({ data: { 'dist-tags': { latest: '2.0.0' } } });
|
|
60
|
+
spawnSync.mockReturnValue({ status: 1, stderr: { toString: () => 'Update error' } });
|
|
61
|
+
try {
|
|
62
|
+
await checkVersion({axios, spawnSync, currentVersion: '1.0.0', update: true});
|
|
63
|
+
} catch (err) {
|
|
64
|
+
expect(err.name).toBe('UpdateError');
|
|
65
|
+
expect(err.message).toBe('Error updating the application.');
|
|
66
|
+
}
|
|
67
|
+
}, { silent: true });
|
|
68
|
+
|
|
69
|
+
it('should throw a UpdateError if update.status !== 0', async () => {
|
|
70
|
+
axios.get.mockResolvedValue({ data: { 'dist-tags': { latest: '2.0.0' } } });
|
|
71
|
+
spawnSync.mockImplementationOnce(() => ({ status: 0 }));
|
|
72
|
+
spawnSync.mockImplementationOnce(() => ({ status: 1, stderr: { toString: () => 'Update error' } }));
|
|
73
|
+
try {
|
|
74
|
+
await checkVersion({axios, spawnSync, currentVersion: '1.0.0', update: true});
|
|
75
|
+
} catch (err) {
|
|
76
|
+
expect(err.name).toBe('UpdateError');
|
|
77
|
+
expect(err.message).toBe('Error updating the application.');
|
|
78
|
+
}
|
|
79
|
+
}, { silent: true });
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
it('should log "You are on the latest version" if update flag is true and the current version is the latest version', async () => {
|
|
83
|
+
axios.get.mockResolvedValue({ data: { 'dist-tags': { latest: '1.0.0' } } });
|
|
84
|
+
await checkVersion({axios, spawnSync, currentVersion: '1.0.0', update: true});
|
|
85
|
+
expect(console.log).toHaveBeenCalledWith(`${global.icons.success} You are on the latest version.`);
|
|
86
|
+
}, { silent: true });
|
|
87
|
+
|
|
88
|
+
it('should log "Application updated successfully." after successful update', async () => {
|
|
89
|
+
axios.get.mockResolvedValue({ data: { 'dist-tags': { latest: '2.0.0' } } });
|
|
90
|
+
spawnSync.mockReturnValue({ status: 0 });
|
|
91
|
+
await checkVersion({axios, spawnSync, currentVersion: '1.0.0', update: true});
|
|
92
|
+
expect(console.log).toHaveBeenCalledWith("Application updated successfully.");
|
|
93
|
+
}, { silent: true });
|
|
94
|
+
|
|
95
|
+
})
|
|
@@ -10,45 +10,43 @@ jest.mock('child_process', () => ({
|
|
|
10
10
|
}))
|
|
11
11
|
const gitRef = "HEAD~1..HEAD"
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
})
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
jest.clearAllMocks()
|
|
15
|
+
})
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
17
|
+
test('rejects if directory is not a git repository', async () => {
|
|
18
|
+
existsSync.mockReturnValueOnce(false)
|
|
19
|
+
try {
|
|
20
|
+
await diff('/path/to/dir')
|
|
21
|
+
fail('Expected function to throw an error', gitRef, existsSync)
|
|
22
|
+
} catch (error) {
|
|
23
|
+
expect(error.message).toEqual('The directory "/path/to/dir" is not a git repository')
|
|
24
|
+
}
|
|
25
|
+
})
|
|
27
26
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
`A\tfile1.txt
|
|
27
|
+
test('resolves with files when git diff command is successful', async () => {
|
|
28
|
+
existsSync.mockReturnValueOnce(true).mockReturnValueOnce(true)
|
|
29
|
+
execSync.mockReturnValueOnce(
|
|
30
|
+
`A\tfile1.txt
|
|
32
31
|
M\tfile2.txt
|
|
33
32
|
D\tfile3.txt`)
|
|
34
33
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
34
|
+
const files = await diff('/path/to/dir', gitRef, existsSync, execSync)
|
|
35
|
+
expect(files).toEqual([
|
|
36
|
+
{ type: 'add', path: 'file1.txt', action: 'add' },
|
|
37
|
+
{ type: 'modify', path: 'file2.txt', action: 'add' },
|
|
38
|
+
{ type: 'delete', path: 'file3.txt', action: 'delete' }
|
|
39
|
+
])
|
|
40
|
+
})
|
|
42
41
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
test('rejects when git diff command fails', async () => {
|
|
43
|
+
existsSync.mockReturnValueOnce(true).mockReturnValueOnce(true)
|
|
44
|
+
execSync.mockImplementation(() => { throw new Error('Command failed') })
|
|
46
45
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
})
|
|
46
|
+
try {
|
|
47
|
+
await diff('/path/to/dir', gitRef, existsSync, execSync)
|
|
48
|
+
fail('Expected function to throw an error')
|
|
49
|
+
} catch (error) {
|
|
50
|
+
expect(error.message).toEqual('Command failed')
|
|
51
|
+
}
|
|
54
52
|
})
|
package/test/lib/git/log.spec.js
CHANGED
|
@@ -8,26 +8,24 @@ jest.mock('child_process', () => {
|
|
|
8
8
|
}
|
|
9
9
|
})
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
})
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
jest.resetAllMocks()
|
|
13
|
+
})
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
15
|
+
it('should return an array of git commit hashes', () => {
|
|
16
|
+
const commits = ['1234567890abcdef', '234567890abcdef1', '34567890abcdef12']
|
|
17
|
+
execSync.mockReturnValue(commits.join(os.EOL))
|
|
18
|
+
const dir = process.cwd()
|
|
19
|
+
const gitRef = 'HEAD~1..HEAD'
|
|
20
|
+
const result = log(dir, gitRef, execSync)
|
|
21
|
+
expect(execSync).toHaveBeenCalledWith(`git log --format=format:%H ${gitRef}`, { cwd: dir, encoding: 'utf-8' })
|
|
22
|
+
expect(result).toEqual(commits)
|
|
23
|
+
})
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
})
|
|
25
|
+
it('should throw an error if git is not installed or no entry found in path', () => {
|
|
26
|
+
const error = { message: 'ENOENT' }
|
|
27
|
+
execSync.mockImplementation(() => { throw error })
|
|
28
|
+
const dir = process.cwd()
|
|
29
|
+
const gitRef = 'HEAD~1..HEAD'
|
|
30
|
+
expect(() => log(dir, gitRef, execSync)).toThrowError('git not installed or no entry found in path')
|
|
33
31
|
})
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import * as packageDefinition from '../../../src/meta/Package.js'
|
|
2
|
+
import { Package } from '../../../src/lib/packageUtil.js'
|
|
3
|
+
|
|
4
|
+
let pkg;
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
pkg = new Package('xmlPath');
|
|
7
|
+
pkg.packageJSON = JSON.parse(JSON.stringify(packageDefinition.metadataDefinition.emptyPackage));
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
jest.clearAllMocks();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
it('should add a member to the pkg JSON', () => {
|
|
16
|
+
pkg.addMember('type', 'member');
|
|
17
|
+
expect(pkg.packageJSON.Package.types[0].name).toBe('type');
|
|
18
|
+
expect(pkg.packageJSON.Package.types[0].members).toEqual(['member']);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should throw an error if packageJSON is undefined', () => {
|
|
22
|
+
pkg.packageJSON = undefined;
|
|
23
|
+
expect(() => pkg.addMember('type', 'member')).toThrowError('getPackageXML must be called before adding members');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should throw an error if type is undefined', () => {
|
|
27
|
+
expect(() => pkg.addMember(undefined, 'member')).toThrowError('An undefined type was received when attempting to add a member');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should throw an error if member is undefined', () => {
|
|
31
|
+
expect(() => pkg.addMember('type', undefined)).toThrowError('An undefined member was received when attempting to add a member');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should throw an error if member is a part file', () => {
|
|
35
|
+
global.format = "part"
|
|
36
|
+
expect(() => pkg.addMember('type', 'member.part')).toThrowError('Part file received as member is not allowed');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should not add the member if it already exists', () => {
|
|
40
|
+
pkg.packageJSON.Package.types = [{ name: "type", members: ["member"] }];
|
|
41
|
+
pkg.addMember('type', 'member');
|
|
42
|
+
expect(pkg.packageJSON.Package.types[0].members).toEqual(["member"]);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should not add the member if type already has an asterisk', () => {
|
|
46
|
+
pkg.packageJSON.Package.types = [{ name: "type", members: ["*"] }];
|
|
47
|
+
pkg.addMember('type', 'member');
|
|
48
|
+
expect(pkg.packageJSON.Package.types[0].members).toEqual(["*"]);
|
|
49
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as packageDefinition from '../../../src/meta/Package.js'
|
|
2
|
+
import { Package } from '../../../src/lib/packageUtil.js'
|
|
3
|
+
|
|
4
|
+
let pkg;
|
|
5
|
+
const fileUtils = {
|
|
6
|
+
fileExists: jest.fn(),
|
|
7
|
+
readFile: jest.fn(),
|
|
8
|
+
}
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
pkg = new Package('xmlPath');
|
|
11
|
+
});
|
|
12
|
+
global.__basedir = '.'
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
jest.clearAllMocks();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should default the package if the json is empty', async () => {
|
|
18
|
+
fileUtils.fileExists.mockReturnValue(true);
|
|
19
|
+
fileUtils.readFile.mockResolvedValue({});
|
|
20
|
+
global.git = { append: true }
|
|
21
|
+
const result = await pkg.getPackageXML(fileUtils);
|
|
22
|
+
expect(result).toBe('existing');
|
|
23
|
+
expect(fileUtils.fileExists).toHaveBeenCalled();
|
|
24
|
+
expect(fileUtils.readFile).toHaveBeenCalled();
|
|
25
|
+
expect(pkg.packageJSON).toEqual(packageDefinition.metadataDefinition.emptyPackage);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should read an existing file and call processJSON', async () => {
|
|
29
|
+
fileUtils.fileExists.mockReturnValue(true);
|
|
30
|
+
fileUtils.readFile.mockResolvedValue(packageDefinition.metadataDefinition.emptyPackage);
|
|
31
|
+
global.git = { append: true }
|
|
32
|
+
const result = await pkg.getPackageXML(fileUtils);
|
|
33
|
+
expect(result).toBe('existing');
|
|
34
|
+
expect(fileUtils.fileExists).toHaveBeenCalled();
|
|
35
|
+
expect(fileUtils.readFile).toHaveBeenCalled();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should create an empty pkg JSON and call processJSON', async () => {
|
|
39
|
+
fileUtils.fileExists.mockReturnValue(false);
|
|
40
|
+
const finalJSON = JSON.parse(JSON.stringify(packageDefinition.metadataDefinition.emptyPackage))
|
|
41
|
+
finalJSON.Package.version = packageDefinition.metadataDefinition.fallbackVersion
|
|
42
|
+
const result = await pkg.getPackageXML(fileUtils);
|
|
43
|
+
expect(result).toBe('not found');
|
|
44
|
+
expect(fileUtils.fileExists).toHaveBeenCalled();
|
|
45
|
+
expect(pkg.packageJSON).toEqual(finalJSON);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should throw an error if xmlPath is undefined', async () => {
|
|
49
|
+
pkg.xmlPath = undefined;
|
|
50
|
+
await expect(pkg.getPackageXML(fileUtils)).rejects.toThrowError('Package not initialized');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should throw an error if error occurs during processing', async () => {
|
|
54
|
+
fileUtils.fileExists.mockReturnValue(true);
|
|
55
|
+
fileUtils.readFile.mockRejectedValue(new Error('Error'));
|
|
56
|
+
await expect(pkg.getPackageXML(fileUtils)).rejects.toThrowError('Error');
|
|
57
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as xml2js from 'xml2js'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import fs from 'fs'
|
|
4
|
+
import { Package } from '../../../src/lib/packageUtil.js'
|
|
5
|
+
|
|
6
|
+
const fileUtils = {
|
|
7
|
+
createDirectory: jest.fn(),
|
|
8
|
+
writeFile: jest.fn()
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
describe('savePackage', () => {
|
|
12
|
+
let pkg
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
pkg = new Package('path/to/file.xml')
|
|
15
|
+
pkg.packageJSON = {
|
|
16
|
+
Package: {
|
|
17
|
+
$: { xmlns: 'http://www.example.com' },
|
|
18
|
+
version: '1.0'
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
jest.resetAllMocks()
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('should replace http with https in xmlns property', () => {
|
|
28
|
+
pkg.savePackage(xml2js, fileUtils)
|
|
29
|
+
expect(pkg.packageJSON.Package.$.xmlns).toBe('https://www.example.com')
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('should save version property to variable, delete it from json and set it again', () => {
|
|
33
|
+
const version = pkg.packageJSON.Package.version
|
|
34
|
+
pkg.savePackage(xml2js, fileUtils)
|
|
35
|
+
expect(version).toBe('1.0')
|
|
36
|
+
expect(pkg.packageJSON.Package.version).toBe(version)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('should build xml object from json and write it to a file', () => {
|
|
40
|
+
pkg.addMember('type', 'member');
|
|
41
|
+
expect(pkg.packageJSON.Package.types[0].name).toBe('type');
|
|
42
|
+
expect(pkg.packageJSON.Package.types[0].members).toEqual(['member']);
|
|
43
|
+
pkg.savePackage(xml2js, fileUtils)
|
|
44
|
+
const xml = '<?xml version="1.0" encoding="UTF-8"?>\n<Package xmlns="https://www.example.com">\n <types>\n <members>member</members>\n <name>type</name>\n </types>\n <version>1.0</version>\n</Package>'
|
|
45
|
+
expect(fileUtils.createDirectory).toHaveBeenCalledWith(path.dirname('path/to/file.xml'))
|
|
46
|
+
expect(fileUtils.writeFile).toHaveBeenCalledWith('path/to/file.xml', xml)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('should throw an error if it occurs', () => {
|
|
50
|
+
fileUtils.createDirectory.mockImplementation(() => {
|
|
51
|
+
throw new Error('createDirectory error')
|
|
52
|
+
})
|
|
53
|
+
expect(() => {
|
|
54
|
+
pkg.savePackage(xml2js, fileUtils)
|
|
55
|
+
}).toThrowError('createDirectory error')
|
|
56
|
+
})
|
|
57
|
+
})
|