@toptal/davinci-update 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.MD +4 -0
- package/README.md +21 -0
- package/bin/davinci-update.mjs +10 -0
- package/package.json +34 -0
- package/src/actions.mjs +164 -0
- package/src/commands/from.mjs +55 -0
- package/src/index.mjs +9 -0
package/LICENSE.MD
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# `@toptal/davinci-update`
|
|
2
|
+
|
|
3
|
+
Updates Picasso, Topkit and Davinci packages
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx @toptal/davinci-update from https://github.com/toptal/picasso/pull/4265
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
This will do the following:
|
|
12
|
+
- Fetch the latest alpha packages from the PR comment and update all matching versions in all package.json files in the current directory
|
|
13
|
+
- Run `yarn install` to update the lockfile
|
|
14
|
+
- Run `syncpack fix-mismatches` to fix any version mismatches and package.json formatting (can be skipped)
|
|
15
|
+
|
|
16
|
+
Please note, in order for this to work, alpha packages must be generated first (usually, by commenting `@toptal-bot run package:alpha-release`)
|
|
17
|
+
|
|
18
|
+
## Options
|
|
19
|
+
|
|
20
|
+
- `--no-syncpack` or `-S` skips running `syncpack fix-mismatches`
|
|
21
|
+
- `--create-pr` or `-C` commits the changes and creates a draft PR in a new branch against `master`
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@toptal/davinci-update",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Update Picasso, Davinci and Topkit packages from a pull request",
|
|
5
|
+
"repository": "toptal/davinci.git",
|
|
6
|
+
"author": "Toptal",
|
|
7
|
+
"license": "SEE LICENSE IN LICENSE.MD",
|
|
8
|
+
"bugs": "https://github.com/toptal/davinci/issues",
|
|
9
|
+
"homepage": "https://github.com/toptal/davinci/tree/master/packages/code#readme",
|
|
10
|
+
"bin": {
|
|
11
|
+
"davinci-update": "./bin/davinci-update.mjs"
|
|
12
|
+
},
|
|
13
|
+
"main": "src/index.mjs",
|
|
14
|
+
"files": [
|
|
15
|
+
"src",
|
|
16
|
+
"bin",
|
|
17
|
+
"CHANGELOG.md"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"test": "NODE_OPTIONS='--experimental-vm-modules --no-warnings' yarn jest"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@toptal/davinci-cli-shared": "^2.4.0",
|
|
24
|
+
"hygen": "^6.2.11",
|
|
25
|
+
"lodash.camelcase": "^4.3.0",
|
|
26
|
+
"lodash.kebabcase": "^4.1.1"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@jest/globals": "^29.7.0"
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
}
|
|
34
|
+
}
|
package/src/actions.mjs
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { $, print } from '@toptal/davinci-cli-shared'
|
|
2
|
+
import { promises as fs } from 'fs'
|
|
3
|
+
|
|
4
|
+
export const ghInstalled = async () => {
|
|
5
|
+
const isInstalled = (await $`gh version`.exitCode) === 0
|
|
6
|
+
if (!isInstalled) {
|
|
7
|
+
print.red(
|
|
8
|
+
'GitHub CLI is not installed. Please install and authenticate before running this command.'
|
|
9
|
+
)
|
|
10
|
+
print.red('https://cli.github.com/')
|
|
11
|
+
process.exit(1)
|
|
12
|
+
}
|
|
13
|
+
return isInstalled
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const fetchComments = async prLink => {
|
|
17
|
+
const spinner = print.startSpinner('Fetching PR comments...')
|
|
18
|
+
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
|
+
spinner.stop()
|
|
24
|
+
print.green('Fetched PR comments')
|
|
25
|
+
return JSON.parse(request.stdout)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const parseLatestAlpha = comments => {
|
|
29
|
+
const alphaComments = comments.filter(comment =>
|
|
30
|
+
comment.body.includes('Your alpha package is ready')
|
|
31
|
+
)
|
|
32
|
+
if (alphaComments.length === 0) {
|
|
33
|
+
print.red(
|
|
34
|
+
'No generated alpha packages found in the PR, please use @toptal-bot run package:alpha-release'
|
|
35
|
+
)
|
|
36
|
+
process.exit(1)
|
|
37
|
+
}
|
|
38
|
+
const latestAlpha = alphaComments.sort(
|
|
39
|
+
(a, b) => new Date(b.createdAt) - new Date(a.createdAt)
|
|
40
|
+
)[0]
|
|
41
|
+
const packages = latestAlpha.body.split('<br/>').slice(1).slice(0, -1)
|
|
42
|
+
const packagesSanitized = packages.map(line =>
|
|
43
|
+
line.replace(/.*`yarn add (.*)`.*/, '$1')
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
const packageMap = packagesSanitized.reduce((acc, pkg) => {
|
|
47
|
+
const atIndex = pkg.lastIndexOf('@')
|
|
48
|
+
const name = pkg.substring(0, atIndex)
|
|
49
|
+
acc[name] = pkg.substring(atIndex + 1)
|
|
50
|
+
return acc
|
|
51
|
+
}, {})
|
|
52
|
+
|
|
53
|
+
print.grey(
|
|
54
|
+
`Found ${packagesSanitized.length} alpha versions in ${latestAlpha.url}`
|
|
55
|
+
)
|
|
56
|
+
return packageMap
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const processPackageJson = async (file, packages) => {
|
|
60
|
+
try {
|
|
61
|
+
const packageJson = JSON.parse(await fs.readFile(file, 'utf-8'))
|
|
62
|
+
let packageUpdated = false
|
|
63
|
+
Object.keys(packages).forEach(pkg => {
|
|
64
|
+
if (
|
|
65
|
+
packageJson.dependencies &&
|
|
66
|
+
packageJson.dependencies[pkg] &&
|
|
67
|
+
packages[pkg] !== packageJson.dependencies[pkg]
|
|
68
|
+
) {
|
|
69
|
+
packageJson.dependencies[pkg] = packages[pkg]
|
|
70
|
+
packageUpdated = true
|
|
71
|
+
}
|
|
72
|
+
if (
|
|
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
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
if (packageUpdated) {
|
|
90
|
+
await fs.writeFile(file, JSON.stringify(packageJson, null, 2))
|
|
91
|
+
return file
|
|
92
|
+
}
|
|
93
|
+
} catch (e) {
|
|
94
|
+
print.red(`Error processing ${file}`)
|
|
95
|
+
console.error(e)
|
|
96
|
+
process.exit(1)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export const updatePackageJsonFiles = async packages => {
|
|
101
|
+
const spinner = print.startSpinner(
|
|
102
|
+
'Updating package.json files in the current repository...'
|
|
103
|
+
)
|
|
104
|
+
const packageJsonFiles =
|
|
105
|
+
await $`find . -type f -name "package.json" | grep -v '/node_modules/' | grep -v '/dist-package/' | grep -v '/dist/' | grep -v '/__tests__/'`
|
|
106
|
+
const files = packageJsonFiles.stdout.split('\n').filter(Boolean)
|
|
107
|
+
const resolvedPackages = await Promise.all(
|
|
108
|
+
files.map(file => processPackageJson(file, packages))
|
|
109
|
+
)
|
|
110
|
+
spinner.stop()
|
|
111
|
+
const updatedPackages = resolvedPackages.filter(Boolean)
|
|
112
|
+
if (updatedPackages.length === 0) {
|
|
113
|
+
print.yellow('No package.json files were updated.')
|
|
114
|
+
return false
|
|
115
|
+
}
|
|
116
|
+
print.green(`Updated ${updatedPackages.length} package.json files.`)
|
|
117
|
+
return true
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export const runSyncpack = async () => {
|
|
121
|
+
const spinner = print.startSpinner('Running syncpack fix-mismatches ...')
|
|
122
|
+
const request = await $`yarn run syncpack fix-mismatches`
|
|
123
|
+
spinner.stop()
|
|
124
|
+
if (request.exitCode !== 0) {
|
|
125
|
+
print.red('Error running syncpack')
|
|
126
|
+
}
|
|
127
|
+
print.green('Running syncpack fix-mismatches finished successfully')
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export const yarnInstall = async () => {
|
|
131
|
+
const spinner = print.startSpinner('Running yarn install...')
|
|
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
|
+
}
|
|
138
|
+
print.green('A new yarn.lock is generated!')
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export const createPullRequest = async prLink => {
|
|
142
|
+
print.grey(`Creating a Pull Request with the updated packages`)
|
|
143
|
+
|
|
144
|
+
const branchName = `fx-${prLink.split('/').at(-3)}-pr-${prLink
|
|
145
|
+
.split('/')
|
|
146
|
+
.at(-1)}-update-alpha`
|
|
147
|
+
const spinner1 = print.startSpinner(
|
|
148
|
+
`Committing changes to a new branch ${branchName}`
|
|
149
|
+
)
|
|
150
|
+
await $`git checkout -b ${branchName}`
|
|
151
|
+
await $`git add .`
|
|
152
|
+
await $`git commit -m "Update to alpha packages"`
|
|
153
|
+
spinner1.stop()
|
|
154
|
+
const spinner2 = print.startSpinner(`Pushing...`)
|
|
155
|
+
await $`git push --set-upstream origin ${branchName}`
|
|
156
|
+
spinner2.stop()
|
|
157
|
+
const spinner3 = print.startSpinner(
|
|
158
|
+
`Creating a draft Pull Request to master...`
|
|
159
|
+
)
|
|
160
|
+
const result =
|
|
161
|
+
await $`gh pr create -d --title "Update to alpha packages" --body "This PR updates packages to the latest alpha versions found in ${prLink}" --base master`
|
|
162
|
+
spinner3.stop()
|
|
163
|
+
print.success(`Created a draft Pull Request: ${result.stdout}`)
|
|
164
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { print, createArgument, $ } from '@toptal/davinci-cli-shared'
|
|
2
|
+
import {
|
|
3
|
+
ghInstalled,
|
|
4
|
+
fetchComments,
|
|
5
|
+
parseLatestAlpha,
|
|
6
|
+
updatePackageJsonFiles,
|
|
7
|
+
runSyncpack,
|
|
8
|
+
yarnInstall,
|
|
9
|
+
createPullRequest,
|
|
10
|
+
} from '../actions.mjs'
|
|
11
|
+
|
|
12
|
+
const updateFrom = async (prLink, { createPr, syncpack }) => {
|
|
13
|
+
$.verbose = false
|
|
14
|
+
print.header(
|
|
15
|
+
'Updating packages in the current repository to the latest alpha in ',
|
|
16
|
+
prLink,
|
|
17
|
+
createPr ? 'and creating a new PR' : ''
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
if (await ghInstalled()) {
|
|
21
|
+
const { comments } = await fetchComments(prLink)
|
|
22
|
+
const packages = parseLatestAlpha(comments)
|
|
23
|
+
const didUpdate = await updatePackageJsonFiles(packages)
|
|
24
|
+
if (didUpdate) {
|
|
25
|
+
if (syncpack) {
|
|
26
|
+
await runSyncpack()
|
|
27
|
+
} else {
|
|
28
|
+
print.yellow('Skipping syncpack')
|
|
29
|
+
}
|
|
30
|
+
await yarnInstall()
|
|
31
|
+
if (createPr) {
|
|
32
|
+
await createPullRequest(prLink)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
process.exit(0)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const fromCommand = program => {
|
|
40
|
+
return program
|
|
41
|
+
.createCommand('from', { isDefault: true })
|
|
42
|
+
.description('Update packages from a Pull Request')
|
|
43
|
+
.addArgument(
|
|
44
|
+
createArgument(
|
|
45
|
+
'<prLink>',
|
|
46
|
+
'Link to a Picasso, Davinci or Topkit pull request with alpha packages generated'
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
.option('-C, --create-pr', 'Automatically create a PR when finished')
|
|
50
|
+
.option(
|
|
51
|
+
'-S, --no-syncpack',
|
|
52
|
+
'Do not run syncpack fix after updating package.json files'
|
|
53
|
+
)
|
|
54
|
+
.action(updateFrom)
|
|
55
|
+
}
|