@wmfs/lerna-sync 1.37.1 → 1.39.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.
@@ -0,0 +1,136 @@
1
+ /* eslint-env mocha */
2
+
3
+ const { expect } = require('chai')
4
+ const { captureConsole } = require('./helpers/fixtures')
5
+ const packageFoldersFromPatterns = require('../lib/utils/package-folders-from-patterns')
6
+ const mapLimit = require('../lib/utils/map-limit')
7
+
8
+ describe('packageFoldersFromPatterns', () => {
9
+ let output
10
+ beforeEach(() => { output = captureConsole() })
11
+ afterEach(() => output.restore())
12
+
13
+ it('turns "<folder>/*" patterns into folder names', () => {
14
+ expect(packageFoldersFromPatterns(['packages/*', 'plugins/*', 'blueprints/*']))
15
+ .to.deep.equal(['packages', 'plugins', 'blueprints'])
16
+ })
17
+
18
+ it('normalises leading ./, trailing slashes and Windows separators', () => {
19
+ expect(packageFoldersFromPatterns(['./plugins/*', 'blueprints\\*', 'apps/*/']))
20
+ .to.deep.equal(['plugins', 'blueprints', 'apps'])
21
+ })
22
+
23
+ it('keeps nested folders', () => {
24
+ expect(packageFoldersFromPatterns(['plugins/core/*'])).to.deep.equal(['plugins/core'])
25
+ })
26
+
27
+ it('skips negations silently', () => {
28
+ expect(packageFoldersFromPatterns(['packages/*', '!packages/legacy'])).to.deep.equal(['packages'])
29
+ expect(output.lines).to.have.length(0)
30
+ })
31
+
32
+ it('warns about and skips patterns that cannot be a clone target', () => {
33
+ expect(packageFoldersFromPatterns(['apps/**', 'apps/*/packages/*', 'packages'])).to.deep.equal([])
34
+ expect(output.lines).to.have.length(3)
35
+ expect(output.text()).to.include('apps/**')
36
+ })
37
+
38
+ it('de-duplicates', () => {
39
+ expect(packageFoldersFromPatterns(['plugins/*', './plugins/*'])).to.deep.equal(['plugins'])
40
+ })
41
+ })
42
+
43
+ describe('mapLimit', () => {
44
+ const delay = ms => new Promise(resolve => setTimeout(resolve, ms))
45
+
46
+ it('returns results in input order, not completion order', async () => {
47
+ const result = await mapLimit([30, 10, 20], 3, async ms => { await delay(ms); return ms })
48
+ expect(result).to.deep.equal([30, 10, 20])
49
+ })
50
+
51
+ it('never runs more than the limit at once', async () => {
52
+ let running = 0
53
+ let peak = 0
54
+ await mapLimit(Array.from({ length: 10 }, (_, i) => i), 3, async () => {
55
+ running++
56
+ peak = Math.max(peak, running)
57
+ await delay(5)
58
+ running--
59
+ })
60
+ expect(peak).to.equal(3)
61
+ })
62
+
63
+ it('handles an empty list', async () => {
64
+ expect(await mapLimit([], 4, async x => x)).to.deep.equal([])
65
+ })
66
+
67
+ it('passes the index to the callback', async () => {
68
+ expect(await mapLimit(['a', 'b'], 2, async (x, i) => `${x}${i}`)).to.deep.equal(['a0', 'b1'])
69
+ })
70
+
71
+ for (const bad of [0, -1, 1.5, NaN, undefined, '4']) {
72
+ it(`rejects a limit of ${String(bad)}`, async () => {
73
+ let error
74
+ try { await mapLimit([1, 2], bad, async x => x) } catch (e) { error = e }
75
+ expect(error).to.be.instanceOf(RangeError)
76
+ })
77
+ }
78
+ })
79
+
80
+ describe('git helper', () => {
81
+ const { git, tryGit } = require('../lib/utils/git')
82
+ const os = require('os')
83
+
84
+ // A git alias runs through sh, which Git for Windows also ships
85
+ const alias = command => ['-c', `alias.probe=!${command}`, 'probe']
86
+
87
+ it('resolves with trimmed stdout', async () => {
88
+ expect(await git(alias('printf "hello\\n\\n"'), os.tmpdir())).to.equal('hello')
89
+ })
90
+
91
+ it('kills a command that runs past the timeout', async () => {
92
+ let error
93
+ try {
94
+ await git(alias('sleep 5'), os.tmpdir(), { timeout: 300 })
95
+ } catch (e) {
96
+ error = e
97
+ }
98
+ expect(error.killed).to.equal(true)
99
+ expect(error.message).to.match(/timed out after 300ms/)
100
+ })
101
+
102
+ it('returns null from tryGit instead of throwing', async () => {
103
+ expect(await tryGit(alias('exit 1'), os.tmpdir())).to.equal(null)
104
+ })
105
+
106
+ describe('environment', () => {
107
+ const saved = {}
108
+ const keys = ['GIT_TERMINAL_PROMPT', 'GIT_SSH_COMMAND', 'GIT_SSH']
109
+
110
+ beforeEach(() => keys.forEach(k => { saved[k] = process.env[k]; delete process.env[k] }))
111
+ afterEach(() => keys.forEach(k => {
112
+ if (saved[k] === undefined) delete process.env[k]
113
+ else process.env[k] = saved[k]
114
+ }))
115
+
116
+ const envVar = name => git(alias(`printf %s "$${name}"`), os.tmpdir())
117
+
118
+ it('disables terminal credential prompts', async () => {
119
+ expect(await envVar('GIT_TERMINAL_PROMPT')).to.equal('0')
120
+ })
121
+
122
+ it('stops ssh prompting for passphrases or host keys', async () => {
123
+ expect(await envVar('GIT_SSH_COMMAND')).to.equal('ssh -o BatchMode=yes')
124
+ })
125
+
126
+ it('keeps a GIT_SSH_COMMAND the user already set', async () => {
127
+ process.env.GIT_SSH_COMMAND = 'ssh -i ~/.ssh/work_key'
128
+ expect(await envVar('GIT_SSH_COMMAND')).to.equal('ssh -i ~/.ssh/work_key')
129
+ })
130
+
131
+ it('does not change process.env itself', async () => {
132
+ await envVar('GIT_TERMINAL_PROMPT')
133
+ expect(process.env.GIT_TERMINAL_PROMPT).to.equal(undefined)
134
+ })
135
+ })
136
+ })
@@ -1,49 +0,0 @@
1
- const chalk = require('chalk')
2
- const path = require('path')
3
- const run = require('./../utils/run')
4
-
5
- module.exports = async function cloneAction (action, options, messages) {
6
- const repoName = action.config.repoName
7
- const destPath = path.join(
8
- options.monorepoPath,
9
- action.config.packageName,
10
- repoName
11
- )
12
-
13
- const creds = `${options.gitHubUser}:${options.gitHubToken}@github.com`
14
-
15
- // eg.g. cloneUrl = 'https://github.com/octocat/Hello-World.git'
16
- const cloneUrl = `https://github.com/${options.gitHubOrgName}/${repoName}.git`
17
- const cloneCmd = `git clone ${cloneUrl.replace('github.com', creds)} ${destPath}`
18
-
19
- console.log(`${chalk.green('CLONE')} ${chalk.gray(`/${action.config.packageName}/`)}${repoName}`)
20
-
21
- try {
22
- await run(cloneCmd)
23
- } catch (err) {
24
- console.log(`${chalk.inverse(chalk.red('FAILED'))} - ${chalk.red(err.message)}`)
25
- messages.addMessage(
26
- repoName,
27
- 'Cloning',
28
- 'ERROR',
29
- err
30
- )
31
- }
32
- }
33
-
34
- /*
35
- async function cloneRepo (cloneUrl, creds, destPath) {
36
- const cloneCmd = `git clone ${cloneUrl.replace('github.com', creds)} ${destPath}`
37
-
38
- try {
39
- await run(cloneCmd)
40
- return [true, 'cloned']
41
- } catch (err) {
42
- if (!err.message.includes('already exists and is not an empty directory')) {
43
- throw err
44
- }
45
- }
46
- return [false, 'cloned']
47
- }
48
-
49
- */
@@ -1,4 +0,0 @@
1
- module.exports = {
2
- PULL: require('./pull'),
3
- CLONE: require('./clone')
4
- }
@@ -1,52 +0,0 @@
1
- const path = require('path')
2
- const STATUS_CMD = 'git -c core.quotepath=false status'
3
- const MERGE_CMD = 'git -c core.quotepath=false merge origin/master --no-stat -v'
4
- const COMMIT_OR_STASH = /commit your changes or stash them /
5
- const ALREADY_UP_TO_DATE = /Already up to date./
6
- const UNMERGED_CHANGES = /not possible because you have unmerged files/
7
- const FETCH_CMD = 'git -c core.quotepath=false fetch origin --progress --prune'
8
- const run = require('./../utils/run')
9
- const chalk = require('chalk')
10
- module.exports = async function pullAction (action, options, messages) {
11
- const repoName = action.config.repoName
12
- const destPath = path.join(
13
- options.monorepoPath,
14
- action.config.packageName,
15
- repoName
16
- )
17
-
18
- try {
19
- await run(FETCH_CMD, destPath)
20
- const mergeResult = await run(MERGE_CMD, destPath)
21
- if (ALREADY_UP_TO_DATE.test(mergeResult)) {
22
- await run(STATUS_CMD, destPath)
23
- action.type = 'SKIP'
24
- // TODO: Study what's returned and regex to victory, if such a thing is possible.
25
- messages.addMessage(
26
- repoName,
27
- 'Pulling',
28
- 'INFO',
29
- new Error('You may have untracked files that need adding.')
30
- )
31
- console.log(`${chalk.blue(' INFO')} ${chalk.gray(`/${action.config.packageName}/`)}${repoName}`)
32
- } else {
33
- console.log(`${chalk.cyan(' PULL')} ${chalk.gray(`/${action.config.packageName}/`)}${repoName}`)
34
- }
35
- } catch (err) {
36
- if (COMMIT_OR_STASH.test(err.message) || UNMERGED_CHANGES.test(err.message)) {
37
- messages.addMessage(
38
- repoName,
39
- 'Pulling',
40
- 'WARNING',
41
- new Error('Failed to fetch/pull because there are local changes afoot.\nYou\'ll need to push/stash them before this repo can be synced.')
42
- )
43
- } else {
44
- messages.addMessage(
45
- repoName,
46
- 'Pulling',
47
- 'ERROR',
48
- err
49
- )
50
- }
51
- }
52
- }
@@ -1,69 +0,0 @@
1
- const chalk = require('chalk')
2
-
3
- module.exports = class Messages {
4
- constructor () {
5
- this.messages = {}
6
- this.errorCount = 0
7
- this.messageCount = 0
8
- }
9
-
10
- addMessage (repoName, domain, level, err) {
11
- this.messageCount++
12
- if (!this.messages[domain]) {
13
- this.messages[domain] = []
14
- }
15
- this.messages[domain].push(
16
- {
17
- repoName,
18
- level,
19
- err
20
- }
21
- )
22
- if (level === 'ERROR') {
23
- this.errorCount++
24
- }
25
- }
26
-
27
- output () {
28
- if (this.messageCount > 0) {
29
- console.log('')
30
- console.log(`Messages ${chalk.blue(`(${this.messageCount})`)}`)
31
-
32
- Object.entries(this.messages).forEach(([domain, messages]) => {
33
- console.log('')
34
- console.log(` ${chalk.underline(domain)}`)
35
- console.log('')
36
- messages.forEach(
37
- message => {
38
- let title = message.level
39
- title = `[${title}]`
40
- switch (message.level) {
41
- case 'ERROR':
42
- title = chalk.red(`${title} ${message.repoName}`)
43
- break
44
- case 'WARNING':
45
- title = chalk.yellow(`${title} ${message.repoName}`)
46
- break
47
- case 'INFO':
48
- title = chalk.blue(`${title} ${message.repoName}`)
49
- break
50
- }
51
- title = chalk.bold(title)
52
- let messageBody = message.err.toString().replace(/\n/g, '\n ')
53
- if (messageBody.slice(0, 7) === 'Error: ') {
54
- messageBody = messageBody.slice(7)
55
- }
56
-
57
- console.log(` ${title}`)
58
- console.log(` ${messageBody}`)
59
- console.log('')
60
- }
61
- )
62
- })
63
-
64
- if (this.errorCount > 0) {
65
- console.log(chalk.inverse(chalk.red(`SYNCING FINISHED WITH ${this.errorCount} ERROR${this.errorCount === 1 ? '' : 'S'}`)))
66
- }
67
- }
68
- }
69
- }
package/lib/utils/run.js DELETED
@@ -1,11 +0,0 @@
1
- const { exec } = require('child_process')
2
-
3
- module.exports = function run (cmd, cwd = null) {
4
- return new Promise((resolve, reject) => {
5
- exec(cmd, { cwd }, (err, stdout, stderr) => {
6
- // console.log(stdout)
7
- if (err) reject(err)
8
- else resolve(stdout)
9
- })
10
- })
11
- }