@toptal/davinci-update 1.0.0 → 1.0.1
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/CHANGELOG.md +11 -0
- package/bin/davinci-update.mjs +4 -6
- package/package.json +2 -2
- package/src/actions.mjs +111 -79
- package/src/commands/from.mjs +28 -12
- package/src/index.mjs +0 -9
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# @toptal/davinci-update
|
|
2
|
+
|
|
3
|
+
## 1.0.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#2393](https://github.com/toptal/davinci/pull/2393) [`8400c60f`](https://github.com/toptal/davinci/commit/8400c60f55ef8c82e99e22404009d2b1ad57aa40) Thanks [@mkrl](https://github.com/mkrl)!
|
|
8
|
+
|
|
9
|
+
### davinci-update
|
|
10
|
+
|
|
11
|
+
- adds a utility that updates Picasso and Davinci alpha packages using a PR link
|
package/bin/davinci-update.mjs
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import { loadCommands, bootstrap } from '@toptal/davinci-cli-shared'
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import { fromCommand } from '../src/commands/from.mjs'
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
cliEngine.loadCommands(commands, 'davinci-update')
|
|
10
|
-
cliEngine.bootstrap()
|
|
7
|
+
loadCommands([fromCommand], 'davinci-update')
|
|
8
|
+
bootstrap()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toptal/davinci-update",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Update Picasso, Davinci and Topkit packages from a pull request",
|
|
5
5
|
"repository": "toptal/davinci.git",
|
|
6
6
|
"author": "Toptal",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"CHANGELOG.md"
|
|
18
18
|
],
|
|
19
19
|
"scripts": {
|
|
20
|
-
"test": "
|
|
20
|
+
"test": "exit 0"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@toptal/davinci-cli-shared": "^2.4.0",
|
package/src/actions.mjs
CHANGED
|
@@ -1,31 +1,28 @@
|
|
|
1
|
-
import { $, print } from '@toptal/davinci-cli-shared'
|
|
1
|
+
import { $, print, spinner, globby, prompt } from '@toptal/davinci-cli-shared'
|
|
2
2
|
import { promises as fs } from 'fs'
|
|
3
3
|
|
|
4
|
-
export const
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
export const ensureGhIsInstalled = async () => {
|
|
5
|
+
try {
|
|
6
|
+
await $`gh version`
|
|
7
|
+
return true
|
|
8
|
+
} catch (err) {
|
|
7
9
|
print.red(
|
|
8
10
|
'GitHub CLI is not installed. Please install and authenticate before running this command.'
|
|
9
11
|
)
|
|
10
12
|
print.red('https://cli.github.com/')
|
|
11
13
|
process.exit(1)
|
|
12
14
|
}
|
|
13
|
-
return isInstalled
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
export const fetchComments = async prLink => {
|
|
17
18
|
const spinner = print.startSpinner('Fetching PR comments...')
|
|
18
19
|
const request = await $`gh pr view ${prLink} --json comments`
|
|
19
|
-
if (request.exitCode !== 0) {
|
|
20
|
-
print.red('Error fetching comments')
|
|
21
|
-
process.exit(1)
|
|
22
|
-
}
|
|
23
20
|
spinner.stop()
|
|
24
21
|
print.green('Fetched PR comments')
|
|
25
22
|
return JSON.parse(request.stdout)
|
|
26
23
|
}
|
|
27
24
|
|
|
28
|
-
export const
|
|
25
|
+
export const extractLatestAlphaFromComments = comments => {
|
|
29
26
|
const alphaComments = comments.filter(comment =>
|
|
30
27
|
comment.body.includes('Your alpha package is ready')
|
|
31
28
|
)
|
|
@@ -35,9 +32,9 @@ export const parseLatestAlpha = comments => {
|
|
|
35
32
|
)
|
|
36
33
|
process.exit(1)
|
|
37
34
|
}
|
|
38
|
-
const latestAlpha = alphaComments.sort(
|
|
39
|
-
(a, b) => new Date(b.createdAt) - new Date(a.createdAt)
|
|
40
|
-
)
|
|
35
|
+
const [latestAlpha] = alphaComments.sort(
|
|
36
|
+
(a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
|
|
37
|
+
)
|
|
41
38
|
const packages = latestAlpha.body.split('<br/>').slice(1).slice(0, -1)
|
|
42
39
|
const packagesSanitized = packages.map(line =>
|
|
43
40
|
line.replace(/.*`yarn add (.*)`.*/, '$1')
|
|
@@ -61,29 +58,15 @@ const processPackageJson = async (file, packages) => {
|
|
|
61
58
|
const packageJson = JSON.parse(await fs.readFile(file, 'utf-8'))
|
|
62
59
|
let packageUpdated = false
|
|
63
60
|
Object.keys(packages).forEach(pkg => {
|
|
64
|
-
|
|
65
|
-
packageJson.dependencies
|
|
66
|
-
packageJson.
|
|
67
|
-
|
|
68
|
-
) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
packageJson.devDependencies &&
|
|
74
|
-
packageJson.devDependencies[pkg] &&
|
|
75
|
-
packages[pkg] !== packageJson.devDependencies[pkg]
|
|
76
|
-
) {
|
|
77
|
-
packageJson.devDependencies[pkg] = packages[pkg]
|
|
78
|
-
packageUpdated = true
|
|
79
|
-
}
|
|
80
|
-
if (
|
|
81
|
-
packageJson.peerDependencies &&
|
|
82
|
-
packageJson.peerDependencies[pkg] &&
|
|
83
|
-
packages[pkg] !== packageJson.peerDependencies[pkg]
|
|
84
|
-
) {
|
|
85
|
-
packageJson.peerDependencies[pkg] = packages[pkg]
|
|
86
|
-
packageUpdated = true
|
|
61
|
+
for (const deps of [
|
|
62
|
+
packageJson.dependencies,
|
|
63
|
+
packageJson.devDependencies,
|
|
64
|
+
packageJson.peerDependencies,
|
|
65
|
+
]) {
|
|
66
|
+
if (deps?.[pkg] && deps[pkg] !== packages[pkg]) {
|
|
67
|
+
deps[pkg] = packages[pkg]
|
|
68
|
+
packageUpdated = true
|
|
69
|
+
}
|
|
87
70
|
}
|
|
88
71
|
})
|
|
89
72
|
if (packageUpdated) {
|
|
@@ -97,18 +80,51 @@ const processPackageJson = async (file, packages) => {
|
|
|
97
80
|
}
|
|
98
81
|
}
|
|
99
82
|
|
|
83
|
+
export const updateResolutions = async packages => {
|
|
84
|
+
try {
|
|
85
|
+
const selectedPackages = await prompt.checkbox(
|
|
86
|
+
'packages',
|
|
87
|
+
'Select packages to update',
|
|
88
|
+
Object.keys(packages).map(pkg => ({
|
|
89
|
+
name: pkg,
|
|
90
|
+
value: pkg,
|
|
91
|
+
})),
|
|
92
|
+
15
|
|
93
|
+
)
|
|
94
|
+
const packageJson = JSON.parse(await fs.readFile('./package.json', 'utf-8'))
|
|
95
|
+
packageJson.resolutions = packageJson.resolutions || {}
|
|
96
|
+
selectedPackages.forEach(pkg => {
|
|
97
|
+
packageJson.resolutions[pkg] = packages[pkg]
|
|
98
|
+
})
|
|
99
|
+
await fs.writeFile(
|
|
100
|
+
'./package.json',
|
|
101
|
+
`${JSON.stringify(packageJson, null, 2)}\n`
|
|
102
|
+
)
|
|
103
|
+
} catch (e) {
|
|
104
|
+
print.red(`Could not update resolutions in package.json`)
|
|
105
|
+
console.error(e)
|
|
106
|
+
process.exit(1)
|
|
107
|
+
}
|
|
108
|
+
print.green('Updated resolutions in package.json')
|
|
109
|
+
return true
|
|
110
|
+
}
|
|
111
|
+
|
|
100
112
|
export const updatePackageJsonFiles = async packages => {
|
|
101
|
-
const
|
|
102
|
-
'Updating package.json files in the current repository...'
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
113
|
+
const updatedPackages = await spinner(
|
|
114
|
+
'Updating package.json files in the current repository...',
|
|
115
|
+
async () => {
|
|
116
|
+
const packageJsonFiles = await globby(
|
|
117
|
+
['**/package.json', 'package.json'],
|
|
118
|
+
{
|
|
119
|
+
gitignore: true,
|
|
120
|
+
}
|
|
121
|
+
)
|
|
122
|
+
const resolvedPackages = await Promise.all(
|
|
123
|
+
packageJsonFiles.map(file => processPackageJson(file, packages))
|
|
124
|
+
)
|
|
125
|
+
return resolvedPackages.filter(Boolean)
|
|
126
|
+
}
|
|
109
127
|
)
|
|
110
|
-
spinner.stop()
|
|
111
|
-
const updatedPackages = resolvedPackages.filter(Boolean)
|
|
112
128
|
if (updatedPackages.length === 0) {
|
|
113
129
|
print.yellow('No package.json files were updated.')
|
|
114
130
|
return false
|
|
@@ -118,47 +134,63 @@ export const updatePackageJsonFiles = async packages => {
|
|
|
118
134
|
}
|
|
119
135
|
|
|
120
136
|
export const runSyncpack = async () => {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
print.red('Error running syncpack')
|
|
126
|
-
}
|
|
137
|
+
await spinner(
|
|
138
|
+
'Running syncpack fix-mismatches ...',
|
|
139
|
+
async () => await $`yarn run syncpack fix-mismatches`
|
|
140
|
+
)
|
|
127
141
|
print.green('Running syncpack fix-mismatches finished successfully')
|
|
128
142
|
}
|
|
129
143
|
|
|
130
144
|
export const yarnInstall = async () => {
|
|
131
|
-
|
|
132
|
-
const request = await $`yarn install`
|
|
133
|
-
spinner.stop()
|
|
134
|
-
if (request.exitCode !== 0) {
|
|
135
|
-
print.red('Error running yarn install')
|
|
136
|
-
process.exit(1)
|
|
137
|
-
}
|
|
145
|
+
await spinner('Running yarn install...', async () => await $`yarn install`)
|
|
138
146
|
print.green('A new yarn.lock is generated!')
|
|
139
147
|
}
|
|
140
148
|
|
|
141
|
-
export const
|
|
142
|
-
print.grey(`Creating a Pull Request with the updated packages`)
|
|
143
|
-
|
|
149
|
+
export const createOrUpdatePullRequest = async prLink => {
|
|
144
150
|
const branchName = `fx-${prLink.split('/').at(-3)}-pr-${prLink
|
|
145
151
|
.split('/')
|
|
146
152
|
.at(-1)}-update-alpha`
|
|
147
|
-
|
|
148
|
-
`
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
153
|
+
try {
|
|
154
|
+
await spinner(`Checking if a ${branchName} already exists...`, async () => {
|
|
155
|
+
await $`git fetch`
|
|
156
|
+
// Using --exit-code on a remote origin, this will fail if the branch exists
|
|
157
|
+
await $`git ls-remote --exit-code --heads origin ${branchName}`
|
|
158
|
+
})
|
|
159
|
+
// Branch already exists
|
|
160
|
+
await spinner(
|
|
161
|
+
`Branch ${branchName} already exists, pushing new changes to the branch...`,
|
|
162
|
+
async () => {
|
|
163
|
+
await $`git checkout ${branchName}`
|
|
164
|
+
await $`git add .`
|
|
165
|
+
await $`git commit -m "Update to alpha packages"`
|
|
166
|
+
await $`git push origin ${branchName}`
|
|
167
|
+
}
|
|
168
|
+
)
|
|
169
|
+
print.success(`Pushed new changes to remote ${branchName}`)
|
|
170
|
+
} catch {
|
|
171
|
+
// Branch does not exist
|
|
172
|
+
print.grey(`Creating a Pull Request with the updated packages`)
|
|
173
|
+
try {
|
|
174
|
+
await spinner(
|
|
175
|
+
`Committing changes to a new branch ${branchName}`,
|
|
176
|
+
async () => {
|
|
177
|
+
await $`git checkout -b ${branchName}`
|
|
178
|
+
await $`git add .`
|
|
179
|
+
await $`git commit -m "Update to alpha packages"`
|
|
180
|
+
}
|
|
181
|
+
)
|
|
182
|
+
await spinner(`Pushing...`, async () => {
|
|
183
|
+
await $`git push --set-upstream origin ${branchName}`
|
|
184
|
+
})
|
|
185
|
+
const result = await spinner(
|
|
186
|
+
`Creating a draft Pull Request to master...`,
|
|
187
|
+
() =>
|
|
188
|
+
$`gh pr create -d --title "Update to alpha packages" --body "This PR updates packages to the latest alpha versions found in ${prLink}" --base master`
|
|
189
|
+
)
|
|
190
|
+
print.success(`Created a draft Pull Request: ${result.stdout}`)
|
|
191
|
+
} catch {
|
|
192
|
+
print.red('Failed to commit changes and create a PR')
|
|
193
|
+
process.exit(1)
|
|
194
|
+
}
|
|
195
|
+
}
|
|
164
196
|
}
|
package/src/commands/from.mjs
CHANGED
|
@@ -1,36 +1,45 @@
|
|
|
1
1
|
import { print, createArgument, $ } from '@toptal/davinci-cli-shared'
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
ensureGhIsInstalled,
|
|
4
4
|
fetchComments,
|
|
5
|
-
|
|
5
|
+
extractLatestAlphaFromComments,
|
|
6
6
|
updatePackageJsonFiles,
|
|
7
7
|
runSyncpack,
|
|
8
8
|
yarnInstall,
|
|
9
|
-
|
|
9
|
+
createOrUpdatePullRequest,
|
|
10
|
+
updateResolutions,
|
|
10
11
|
} from '../actions.mjs'
|
|
11
12
|
|
|
12
|
-
const updateFrom = async (prLink, { createPr, syncpack }) => {
|
|
13
|
+
const updateFrom = async (prLink, { createPr, syncpack, resolutions }) => {
|
|
13
14
|
$.verbose = false
|
|
14
15
|
print.header(
|
|
15
|
-
|
|
16
|
+
`Updating ${
|
|
17
|
+
resolutions ? 'resolutions' : 'packages'
|
|
18
|
+
} in the current repository to the latest alpha in `,
|
|
16
19
|
prLink,
|
|
17
|
-
createPr ? 'and creating a new PR' : ''
|
|
20
|
+
createPr ? 'and creating (or updating) a new PR' : ''
|
|
18
21
|
)
|
|
19
22
|
|
|
20
|
-
if (await
|
|
23
|
+
if (await ensureGhIsInstalled()) {
|
|
21
24
|
const { comments } = await fetchComments(prLink)
|
|
22
|
-
const packages =
|
|
23
|
-
const didUpdate =
|
|
25
|
+
const packages = extractLatestAlphaFromComments(comments)
|
|
26
|
+
const didUpdate = resolutions
|
|
27
|
+
? await updateResolutions(packages)
|
|
28
|
+
: await updatePackageJsonFiles(packages)
|
|
24
29
|
if (didUpdate) {
|
|
25
|
-
if (syncpack) {
|
|
30
|
+
if (syncpack && !resolutions) {
|
|
26
31
|
await runSyncpack()
|
|
27
32
|
} else {
|
|
28
33
|
print.yellow('Skipping syncpack')
|
|
29
34
|
}
|
|
30
35
|
await yarnInstall()
|
|
31
36
|
if (createPr) {
|
|
32
|
-
await
|
|
37
|
+
await createOrUpdatePullRequest(prLink)
|
|
33
38
|
}
|
|
39
|
+
} else {
|
|
40
|
+
print.yellow(
|
|
41
|
+
'Nothing to update. Perhaps you already have the versions you need or using a wrong directory? Exiting...'
|
|
42
|
+
)
|
|
34
43
|
}
|
|
35
44
|
}
|
|
36
45
|
process.exit(0)
|
|
@@ -46,10 +55,17 @@ export const fromCommand = program => {
|
|
|
46
55
|
'Link to a Picasso, Davinci or Topkit pull request with alpha packages generated'
|
|
47
56
|
)
|
|
48
57
|
)
|
|
49
|
-
.option(
|
|
58
|
+
.option(
|
|
59
|
+
'-C, --create-pr',
|
|
60
|
+
'Automatically create or update a PR when finished'
|
|
61
|
+
)
|
|
50
62
|
.option(
|
|
51
63
|
'-S, --no-syncpack',
|
|
52
64
|
'Do not run syncpack fix after updating package.json files'
|
|
53
65
|
)
|
|
66
|
+
.option(
|
|
67
|
+
'-R, --resolutions',
|
|
68
|
+
'Update resolutions in root package.json. This will only update one file and prompt you to select the packages. Skips running syncpack automatically.'
|
|
69
|
+
)
|
|
54
70
|
.action(updateFrom)
|
|
55
71
|
}
|