Skip to content

Commit db88cd0

Browse files
author
Tom Lienard
authored
feat: introduce new @scaleway/changesets-renovate package (#1216)
* feat: introduce new @scaleway/changesets-renovate package * fix: remove .eslintrc * fix: simplify bin * fix: update snapshots * chore: add tests * fix: tsc error * chore: remove "heavily" inspired after all changes
1 parent 2200053 commit db88cd0

File tree

9 files changed

+335
-13
lines changed

9 files changed

+335
-13
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
**/__tests__/**
2+
src
3+
.eslintrc.cjs
4+
!.npmignore
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# `@scaleway/changesets-renovate`
2+
3+
## Automatically create changesets for Renovate
4+
5+
## Install
6+
7+
```bash
8+
$ pnpm add --global @scaleway/changesets-renovate
9+
```
10+
11+
## Usage
12+
13+
This package is a very simple CLI:
14+
15+
```bash
16+
changesets-renovate
17+
```
18+
19+
It's inspired by this GitHub Action from Backstage: https://github.com/backstage/backstage/blob/master/.github/workflows/sync_renovate-changesets.yml
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"name": "@scaleway/changesets-renovate",
3+
"version": "1.0.0",
4+
"description": "Automatically create changesets for Renovate",
5+
"keywords": [
6+
"changesets",
7+
"renovate",
8+
"sync"
9+
],
10+
"type": "module",
11+
"engines": {
12+
"node": ">=14.x"
13+
},
14+
"bin": {
15+
"changesets-renovate": "dist/index.js"
16+
},
17+
"sideEffects": false,
18+
"publishConfig": {
19+
"access": "public"
20+
},
21+
"repository": {
22+
"type": "git",
23+
"url": "https://github.com/scaleway/scaleway-lib",
24+
"directory": "packages/changesets-renovate"
25+
},
26+
"license": "MIT",
27+
"dependencies": {
28+
"simple-git": "^3.17.0"
29+
}
30+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`generate changeset file should generate changeset file, commit and push 1`] = `
4+
[MockFunction] {
5+
"calls": [
6+
[
7+
".changeset/renovate-test.md",
8+
"---
9+
'packageName': patch
10+
---
11+
12+
Updated dependency \`package\` to \`version\`.
13+
Updated dependency \`package2\` to \`version2\`.
14+
",
15+
],
16+
],
17+
"results": [
18+
{
19+
"type": "return",
20+
"value": undefined,
21+
},
22+
],
23+
}
24+
`;
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import fs from 'node:fs/promises'
2+
import { simpleGit } from 'simple-git'
3+
import { run } from '..'
4+
5+
jest.mock('simple-git')
6+
jest.mock('node:fs/promises')
7+
8+
beforeEach(() => {
9+
jest.spyOn(console, 'log')
10+
})
11+
12+
afterEach(() => {
13+
jest.resetAllMocks()
14+
})
15+
16+
describe('generate changeset file', () => {
17+
it('should skip if not in renovate branch', async () => {
18+
// @ts-expect-error we mock at the top
19+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
20+
simpleGit.mockReturnValue({
21+
branch: () => ({
22+
current: 'main',
23+
}),
24+
})
25+
26+
await run()
27+
28+
expect(console.log).toHaveBeenCalledWith('Not a renovate branch, skipping')
29+
})
30+
31+
it('should skip if .changeset is already modified', async () => {
32+
// @ts-expect-error we mock at the top
33+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
34+
simpleGit.mockReturnValue({
35+
branch: () => ({
36+
current: 'renovate/test',
37+
}),
38+
diffSummary: () => ({
39+
files: [
40+
{
41+
file: '.changeset/hello.yml',
42+
},
43+
],
44+
}),
45+
})
46+
47+
await run()
48+
49+
expect(console.log).toHaveBeenCalledWith(
50+
'Changeset already exists, skipping',
51+
)
52+
})
53+
54+
it('should skip no package.json files have been modified', async () => {
55+
// @ts-expect-error we mock at the top
56+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
57+
simpleGit.mockReturnValue({
58+
branch: () => ({
59+
current: 'renovate/test',
60+
}),
61+
diffSummary: () => ({
62+
files: [],
63+
}),
64+
})
65+
66+
await run()
67+
68+
expect(console.log).toHaveBeenCalledWith(
69+
'No package.json changes to published packages, skipping',
70+
)
71+
})
72+
73+
it('should generate changeset file, commit and push', async () => {
74+
const rev = 'test'
75+
const fileName = `.changeset/renovate-${rev}.md`
76+
const file = 'test/package.json'
77+
const revparse = jest.fn().mockReturnValue(rev)
78+
const add = jest.fn()
79+
const commit = jest.fn()
80+
const push = jest.fn()
81+
82+
// @ts-expect-error we mock at the top
83+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
84+
simpleGit.mockReturnValue({
85+
branch: () => ({
86+
current: 'renovate/test',
87+
}),
88+
diffSummary: () => ({
89+
files: [
90+
{
91+
file,
92+
},
93+
],
94+
}),
95+
show: () => `
96+
+ "package": "version"
97+
+ "package2": "version2"
98+
`,
99+
revparse,
100+
add,
101+
commit,
102+
push,
103+
})
104+
105+
fs.readFile = jest.fn().mockResolvedValue(`{"name":"packageName"}`)
106+
fs.writeFile = jest.fn()
107+
108+
await run()
109+
110+
expect(fs.readFile).toHaveBeenCalledWith(file, 'utf8')
111+
expect(fs.writeFile).toMatchSnapshot()
112+
expect(add).toHaveBeenCalledWith(fileName)
113+
expect(commit).toHaveBeenCalledWith([], undefined, {
114+
'-C': 'HEAD',
115+
'--amend': null,
116+
'--no-edit': null,
117+
})
118+
expect(push).toHaveBeenCalledWith(['--force'])
119+
})
120+
})
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
#!/usr/bin/env node
2+
3+
import fs from 'node:fs/promises'
4+
import { simpleGit } from 'simple-git'
5+
6+
async function getPackagesNames(files: string[]): Promise<string[]> {
7+
const promises = files.map(async file => {
8+
const data = JSON.parse(await fs.readFile(file, 'utf8')) as {
9+
name: string
10+
}
11+
12+
return data.name
13+
})
14+
15+
return Promise.all(promises)
16+
}
17+
18+
async function createChangeset(
19+
fileName: string,
20+
packageBumps: Map<string, string>,
21+
packages: string[],
22+
) {
23+
let message = ''
24+
25+
for (const [pkg, bump] of packageBumps) {
26+
message += `Updated dependency \`${pkg}\` to \`${bump}\`.\n`
27+
}
28+
29+
const pkgs = packages.map(pkg => `'${pkg}': patch`).join('\n')
30+
const body = `---\n${pkgs}\n---\n\n${message.trim()}\n`
31+
await fs.writeFile(fileName, body)
32+
}
33+
34+
async function getBumps(files: string[]): Promise<Map<string, string>> {
35+
const bumps = new Map()
36+
37+
const promises = files.map(async file => {
38+
const changes = await simpleGit().show([file])
39+
40+
for (const change of changes.split('\n')) {
41+
if (change.startsWith('+ ')) {
42+
const match = change.match(/"(.*?)"/g)
43+
44+
if (match?.[0] && match[1]) {
45+
bumps.set(match[0].replace(/"/g, ''), match[1].replace(/"/g, ''))
46+
}
47+
}
48+
}
49+
})
50+
51+
await Promise.all(promises)
52+
53+
return bumps
54+
}
55+
56+
export async function run() {
57+
const branch = await simpleGit().branch(['--show-current'])
58+
59+
if (!branch.current.startsWith('renovate/')) {
60+
console.log('Not a renovate branch, skipping')
61+
62+
return
63+
}
64+
65+
const diffOutput = await simpleGit().diffSummary(['--name-only', 'HEAD~1'])
66+
const diffFiles = diffOutput.files.map(file => file.file)
67+
68+
if (diffFiles.find(f => f.startsWith('.changeset'))) {
69+
console.log('Changeset already exists, skipping')
70+
71+
return
72+
}
73+
74+
const files = diffFiles.filter(file => file.includes('package.json'))
75+
76+
if (!files.length) {
77+
console.log('No package.json changes to published packages, skipping')
78+
79+
return
80+
}
81+
82+
const packageNames = await getPackagesNames(files)
83+
const shortHash = await simpleGit().revparse(['--short', 'HEAD'])
84+
const fileName = `.changeset/renovate-${shortHash.trim()}.md`
85+
const packageBumps = await getBumps(files)
86+
87+
await createChangeset(fileName, packageBumps, packageNames)
88+
await simpleGit().add(fileName)
89+
await simpleGit().commit([], undefined, {
90+
'-C': 'HEAD',
91+
'--amend': null,
92+
'--no-edit': null,
93+
})
94+
await simpleGit().push(['--force'])
95+
}
96+
97+
run().catch(console.error)

packages/use-i18n/src/__tests__/__snapshots__/formatDate.ts.snap

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ exports[`formatDate should work with custom format and locale de 2`] = `"Donners
1212

1313
exports[`formatDate should work with custom format and locale de 3`] = `"Donnerstag, 13. F 20 n. Chr., 15:28:00 Koordinierte Weltzeit"`;
1414

15-
exports[`formatDate should work with custom format and locale en 1`] = `"Thursday, F 13, 20 AD, 04:28:00PM Coordinated Universal Time"`;
15+
exports[`formatDate should work with custom format and locale en 1`] = `"Thursday, F 13, 20 AD, 04:28:00 PM Coordinated Universal Time"`;
1616

17-
exports[`formatDate should work with custom format and locale en 2`] = `"Thursday, F 13, 20 AD, 03:28:00PM Coordinated Universal Time"`;
17+
exports[`formatDate should work with custom format and locale en 2`] = `"Thursday, F 13, 20 AD, 03:28:00 PM Coordinated Universal Time"`;
1818

19-
exports[`formatDate should work with custom format and locale en 3`] = `"Thursday, F 13, 20 AD, 03:28:00PM Coordinated Universal Time"`;
19+
exports[`formatDate should work with custom format and locale en 3`] = `"Thursday, F 13, 20 AD, 03:28:00 PM Coordinated Universal Time"`;
2020

2121
exports[`formatDate should work with custom format and locale es 1`] = `"jueves, 13 F 20 d. C., 16:28:00 (tiempo universal coordinado)"`;
2222

@@ -38,7 +38,7 @@ exports[`formatDate should work with custom format and locale ro 3`] = `"joi, 13
3838

3939
exports[`formatDate should work with format "hour", for date = "2020-02-13T15:28:00.000Z" and locale "de" 1`] = `"13. Februar 2020 um 15:28"`;
4040

41-
exports[`formatDate should work with format "hour", for date = "2020-02-13T15:28:00.000Z" and locale "en" 1`] = `"February 13, 2020 at 3:28PM"`;
41+
exports[`formatDate should work with format "hour", for date = "2020-02-13T15:28:00.000Z" and locale "en" 1`] = `"February 13, 2020 at 3:28 PM"`;
4242

4343
exports[`formatDate should work with format "hour", for date = "2020-02-13T15:28:00.000Z" and locale "es" 1`] = `"13 de febrero de 2020, 15:28"`;
4444

@@ -48,7 +48,7 @@ exports[`formatDate should work with format "hour", for date = "2020-02-13T15:28
4848

4949
exports[`formatDate should work with format "hour", for date = "1581607680000" and locale "de" 1`] = `"13. Februar 2020 um 15:28"`;
5050

51-
exports[`formatDate should work with format "hour", for date = "1581607680000" and locale "en" 1`] = `"February 13, 2020 at 3:28PM"`;
51+
exports[`formatDate should work with format "hour", for date = "1581607680000" and locale "en" 1`] = `"February 13, 2020 at 3:28 PM"`;
5252

5353
exports[`formatDate should work with format "hour", for date = "1581607680000" and locale "es" 1`] = `"13 de febrero de 2020, 15:28"`;
5454

@@ -58,7 +58,7 @@ exports[`formatDate should work with format "hour", for date = "1581607680000" a
5858

5959
exports[`formatDate should work with format "hour", for date = "new Date(2020, 1, 13, 16, 28)" and locale "de" 1`] = `"13. Februar 2020 um 16:28"`;
6060

61-
exports[`formatDate should work with format "hour", for date = "new Date(2020, 1, 13, 16, 28)" and locale "en" 1`] = `"February 13, 2020 at 4:28PM"`;
61+
exports[`formatDate should work with format "hour", for date = "new Date(2020, 1, 13, 16, 28)" and locale "en" 1`] = `"February 13, 2020 at 4:28 PM"`;
6262

6363
exports[`formatDate should work with format "hour", for date = "new Date(2020, 1, 13, 16, 28)" and locale "es" 1`] = `"13 de febrero de 2020, 16:28"`;
6464

@@ -68,7 +68,7 @@ exports[`formatDate should work with format "hour", for date = "new Date(2020, 1
6868

6969
exports[`formatDate should work with format "hourOnly", for date = "2020-02-13T15:28:00.000Z" and locale "de" 1`] = `"15:28"`;
7070

71-
exports[`formatDate should work with format "hourOnly", for date = "2020-02-13T15:28:00.000Z" and locale "en" 1`] = `"3:28PM"`;
71+
exports[`formatDate should work with format "hourOnly", for date = "2020-02-13T15:28:00.000Z" and locale "en" 1`] = `"3:28 PM"`;
7272

7373
exports[`formatDate should work with format "hourOnly", for date = "2020-02-13T15:28:00.000Z" and locale "es" 1`] = `"15:28"`;
7474

@@ -78,7 +78,7 @@ exports[`formatDate should work with format "hourOnly", for date = "2020-02-13T1
7878

7979
exports[`formatDate should work with format "hourOnly", for date = "1581607680000" and locale "de" 1`] = `"15:28"`;
8080

81-
exports[`formatDate should work with format "hourOnly", for date = "1581607680000" and locale "en" 1`] = `"3:28PM"`;
81+
exports[`formatDate should work with format "hourOnly", for date = "1581607680000" and locale "en" 1`] = `"3:28 PM"`;
8282

8383
exports[`formatDate should work with format "hourOnly", for date = "1581607680000" and locale "es" 1`] = `"15:28"`;
8484

@@ -88,7 +88,7 @@ exports[`formatDate should work with format "hourOnly", for date = "158160768000
8888

8989
exports[`formatDate should work with format "hourOnly", for date = "new Date(2020, 1, 13, 16, 28)" and locale "de" 1`] = `"16:28"`;
9090

91-
exports[`formatDate should work with format "hourOnly", for date = "new Date(2020, 1, 13, 16, 28)" and locale "en" 1`] = `"4:28PM"`;
91+
exports[`formatDate should work with format "hourOnly", for date = "new Date(2020, 1, 13, 16, 28)" and locale "en" 1`] = `"4:28 PM"`;
9292

9393
exports[`formatDate should work with format "hourOnly", for date = "new Date(2020, 1, 13, 16, 28)" and locale "es" 1`] = `"16:28"`;
9494

@@ -158,7 +158,7 @@ exports[`formatDate should work with format "numeric", for date = "new Date(2020
158158

159159
exports[`formatDate should work with format "numericHour", for date = "2020-02-13T15:28:00.000Z" and locale "de" 1`] = `"2020-02-13 15:28"`;
160160

161-
exports[`formatDate should work with format "numericHour", for date = "2020-02-13T15:28:00.000Z" and locale "en" 1`] = `"2020-02-13 3:28PM"`;
161+
exports[`formatDate should work with format "numericHour", for date = "2020-02-13T15:28:00.000Z" and locale "en" 1`] = `"2020-02-13 3:28 PM"`;
162162

163163
exports[`formatDate should work with format "numericHour", for date = "2020-02-13T15:28:00.000Z" and locale "es" 1`] = `"2020-02-13 15:28"`;
164164

@@ -168,7 +168,7 @@ exports[`formatDate should work with format "numericHour", for date = "2020-02-1
168168

169169
exports[`formatDate should work with format "numericHour", for date = "1581607680000" and locale "de" 1`] = `"2020-02-13 15:28"`;
170170

171-
exports[`formatDate should work with format "numericHour", for date = "1581607680000" and locale "en" 1`] = `"2020-02-13 3:28PM"`;
171+
exports[`formatDate should work with format "numericHour", for date = "1581607680000" and locale "en" 1`] = `"2020-02-13 3:28 PM"`;
172172

173173
exports[`formatDate should work with format "numericHour", for date = "1581607680000" and locale "es" 1`] = `"2020-02-13 15:28"`;
174174

@@ -178,7 +178,7 @@ exports[`formatDate should work with format "numericHour", for date = "158160768
178178

179179
exports[`formatDate should work with format "numericHour", for date = "new Date(2020, 1, 13, 16, 28)" and locale "de" 1`] = `"2020-02-13 16:28"`;
180180

181-
exports[`formatDate should work with format "numericHour", for date = "new Date(2020, 1, 13, 16, 28)" and locale "en" 1`] = `"2020-02-13 4:28PM"`;
181+
exports[`formatDate should work with format "numericHour", for date = "new Date(2020, 1, 13, 16, 28)" and locale "en" 1`] = `"2020-02-13 4:28 PM"`;
182182

183183
exports[`formatDate should work with format "numericHour", for date = "new Date(2020, 1, 13, 16, 28)" and locale "es" 1`] = `"2020-02-13 16:28"`;
184184

0 commit comments

Comments
 (0)