Skip to content

Commit 03d47db

Browse files
Eunjae Leeshipjs
andauthored
feat(monorepo): update dependencies (#937)
* feat(monorepo): update dependencies * chore: remove unnecessary config * docs: update the doc Co-authored-by: shipjs <shipjs@test.com>
1 parent 74531a3 commit 03d47db

File tree

6 files changed

+291
-13
lines changed

6 files changed

+291
-13
lines changed
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { getListToUpdate, printListToUpdate } from '../dependencyUpdater';
2+
import { print } from '../../util';
3+
import { mockPrint } from '../../../tests/util';
4+
5+
describe('getListToUpdate', () => {
6+
const list = [
7+
{
8+
packagePath: 'packages/package-core',
9+
json: {
10+
name: 'core',
11+
},
12+
},
13+
{
14+
packagePath: 'packages/package-js',
15+
json: {
16+
name: 'js',
17+
dependencies: {
18+
core: '^0.3.1',
19+
},
20+
},
21+
},
22+
{
23+
packagePath: 'packages/package-plugin-abc',
24+
json: {
25+
name: 'plugin-abc',
26+
dependencies: {
27+
core: '0.3.1',
28+
},
29+
peerDependencies: {
30+
js: '~0.3.1',
31+
},
32+
devDependencies: {
33+
js: '^0.3.1',
34+
},
35+
},
36+
},
37+
];
38+
39+
it('gets correct list', () => {
40+
const actual = getListToUpdate('0.3.2', list);
41+
expect(actual).toEqual([
42+
{
43+
name: 'js',
44+
packagePath: 'packages/package-js',
45+
updates: {
46+
dependencies: [
47+
{
48+
currentVersion: '^0.3.1',
49+
dependency: 'core',
50+
nextVersion: '^0.3.2',
51+
},
52+
],
53+
},
54+
},
55+
{
56+
name: 'plugin-abc',
57+
packagePath: 'packages/package-plugin-abc',
58+
updates: {
59+
dependencies: [
60+
{
61+
currentVersion: '0.3.1',
62+
dependency: 'core',
63+
nextVersion: '0.3.2',
64+
},
65+
],
66+
devDependencies: [
67+
{
68+
currentVersion: '^0.3.1',
69+
dependency: 'js',
70+
nextVersion: '^0.3.2',
71+
},
72+
],
73+
peerDependencies: [
74+
{
75+
currentVersion: '~0.3.1',
76+
dependency: 'js',
77+
nextVersion: '~0.3.2',
78+
},
79+
],
80+
},
81+
},
82+
]);
83+
});
84+
85+
it('prints the correct update list', () => {
86+
const output = [];
87+
mockPrint(print, output);
88+
printListToUpdate(getListToUpdate('0.3.2', list));
89+
expect(output).toMatchInlineSnapshot(`
90+
Array [
91+
" package: js (packages/package-js}/package.json)",
92+
" dependencies:",
93+
" core: ^0.3.1 -> ^0.3.2",
94+
" package: plugin-abc (packages/package-plugin-abc}/package.json)",
95+
" dependencies:",
96+
" core: 0.3.1 -> 0.3.2",
97+
" devDependencies:",
98+
" js: ^0.3.1 -> ^0.3.2",
99+
" peerDependencies:",
100+
" js: ~0.3.1 -> ~0.3.2",
101+
]
102+
`);
103+
});
104+
});
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import { resolve } from 'path';
2+
import { readFileSync, writeFileSync } from 'fs';
3+
import { print } from '../util';
4+
import { runPrettier } from '../helper';
5+
6+
const typesOfDependencies = [
7+
'dependencies',
8+
'devDependencies',
9+
'peerDependencies',
10+
];
11+
12+
export function getListToUpdate(_nextVersion, list) {
13+
const packageNames = list.map(({ json }) => json.name);
14+
15+
return list
16+
.map(({ packagePath, json }) => {
17+
const updates = typesOfDependencies
18+
.map((dependencyType) => {
19+
let dependenciesToUpdate = Object.keys(
20+
json[dependencyType] || {}
21+
).filter((dependency) => packageNames.includes(dependency));
22+
23+
dependenciesToUpdate = dependenciesToUpdate
24+
.map((dependencyToUpdate) => {
25+
const currentVersion = json[dependencyType][dependencyToUpdate];
26+
const prefix =
27+
currentVersion.startsWith('~') || currentVersion.startsWith('^')
28+
? currentVersion[0]
29+
: '';
30+
const nextVersion = `${prefix}${_nextVersion}`;
31+
if (currentVersion !== nextVersion) {
32+
return {
33+
dependency: dependencyToUpdate,
34+
currentVersion,
35+
nextVersion,
36+
};
37+
} else {
38+
return null;
39+
}
40+
})
41+
.filter(Boolean);
42+
43+
if (dependenciesToUpdate.length === 0) {
44+
return null;
45+
} else {
46+
return {
47+
type: dependencyType,
48+
dependenciesToUpdate,
49+
};
50+
}
51+
})
52+
.filter(Boolean);
53+
54+
if (updates.length === 0) {
55+
return null;
56+
} else {
57+
return {
58+
packagePath,
59+
name: json.name,
60+
updates: updates.reduce((acc, { type, dependenciesToUpdate }) => {
61+
// eslint-disable-next-line no-param-reassign
62+
acc[type] = dependenciesToUpdate;
63+
return acc;
64+
}, {}),
65+
};
66+
}
67+
})
68+
.filter(Boolean);
69+
}
70+
71+
export function printListToUpdate(list) {
72+
list.forEach(({ name, packagePath, updates }) => {
73+
print(` package: ${name} (${packagePath}}/package.json)`);
74+
Object.keys(updates).forEach((dependencyType) => {
75+
print(` ${dependencyType}:`);
76+
updates[dependencyType].forEach(
77+
({ dependency, currentVersion, nextVersion }) => {
78+
print(` ${dependency}: ${currentVersion} -> ${nextVersion}`);
79+
}
80+
);
81+
});
82+
});
83+
}
84+
85+
export async function runUpdates(list) {
86+
list.forEach(({ name, packagePath, updates }) => {
87+
print(` package: ${name} (${packagePath}}/package.json)`);
88+
const filePath = resolve(packagePath, 'package.json');
89+
const json = JSON.parse(readFileSync(filePath).toString());
90+
Object.keys(updates).forEach((dependencyType) => {
91+
print(` ${dependencyType}:`);
92+
updates[dependencyType].forEach(
93+
({ dependency, currentVersion, nextVersion }) => {
94+
print(` ${dependency}: ${currentVersion} -> ${nextVersion}`);
95+
json[dependencyType][dependency] = nextVersion;
96+
}
97+
);
98+
});
99+
writeFileSync(filePath, JSON.stringify(json, null, 2));
100+
});
101+
102+
await Promise.all(
103+
list.map(({ packagePath }) =>
104+
runPrettier({
105+
filePath: resolve(packagePath, 'package.json'),
106+
dir: packagePath,
107+
})
108+
)
109+
);
110+
}
111+
112+
export function prepareJsons(packageList) {
113+
return packageList.map((packagePath) => ({
114+
packagePath,
115+
json: JSON.parse(
116+
readFileSync(resolve(packagePath, 'package.json')).toString()
117+
),
118+
}));
119+
}

packages/shipjs/src/step/prepare/__tests__/updateVersionMonorepo.spec.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,23 @@
11
import { expandPackageList, updateVersion } from 'shipjs-lib';
22
import { print } from '../../../util';
3+
import { prepareJsons } from '../../../helper/dependencyUpdater';
4+
5+
jest.mock('../../../helper/dependencyUpdater', () => {
6+
return {
7+
...jest.requireActual('../../../helper/dependencyUpdater'),
8+
prepareJsons: jest.fn(() => []),
9+
};
10+
});
11+
312
import updateVersionMonorepo from '../updateVersionMonorepo';
413
import { mockPrint } from '../../../../tests/util';
514

15+
jest.mock('../updateVersionMonorepo', () => {
16+
const newModule = jest.requireActual('../updateVersionMonorepo');
17+
newModule.prepareJsons = jest.fn(() => []);
18+
return newModule;
19+
});
20+
621
describe('updateVersionMonorepo', () => {
722
it('works', () => {
823
expandPackageList.mockImplementationOnce(() => [
@@ -66,6 +81,7 @@ describe('updateVersionMonorepo', () => {
6681
]);
6782
const output = [];
6883
mockPrint(print, output);
84+
prepareJsons.mockImplementation(() => []);
6985
updateVersionMonorepo({
7086
config: {
7187
versionUpdated: () => {},
@@ -84,6 +100,7 @@ describe('updateVersionMonorepo', () => {
84100
"Actual packages to bump:",
85101
"-> packages/a/package.json",
86102
"-> packages/b/package.json",
103+
"Updating dependencies:",
87104
"-> execute versionUpdated() callback.",
88105
]
89106
`);

packages/shipjs/src/step/prepare/updateVersionMonorepo.js

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,44 @@ import { expandPackageList, updateVersion } from 'shipjs-lib';
22
import runStep from '../runStep';
33
import { wrapExecWithDir, print } from '../../util';
44
import { info } from '../../color';
5+
import {
6+
printListToUpdate,
7+
getListToUpdate,
8+
prepareJsons,
9+
runUpdates,
10+
} from '../../helper/dependencyUpdater';
511

612
export default async ({ config, nextVersion, releaseType, dir, dryRun }) =>
713
await runStep(
814
{ title: 'Updating the versions on the monorepo.' },
915
async () => {
1016
const {
1117
versionUpdated,
12-
monorepo: { mainVersionFile, packagesToBump },
18+
monorepo: {
19+
mainVersionFile,
20+
packagesToBump,
21+
updateDependencies = true,
22+
},
1323
} = config;
1424
const packageList = expandPackageList(packagesToBump, dir);
25+
1526
if (dryRun) {
1627
print(`Your configuration: ${JSON.stringify(packagesToBump)}`);
1728
print(`Main version file: ${mainVersionFile}`);
1829
print(`Actual packages to bump:`);
1930
packageList.forEach((packageDir) =>
2031
print(`-> ${info(`${packageDir}/package.json`)}`)
2132
);
33+
34+
if (updateDependencies) {
35+
print(`Updating dependencies:`);
36+
printListToUpdate(
37+
getListToUpdate(nextVersion, prepareJsons(packageList))
38+
);
39+
} else {
40+
print(`Not updating dependencies.`);
41+
}
42+
2243
if (versionUpdated) {
2344
print(`-> execute ${info('versionUpdated()')} callback.`);
2445
}
@@ -30,6 +51,12 @@ export default async ({ config, nextVersion, releaseType, dir, dryRun }) =>
3051
print(`-> ${info(`${packageDir}/package.json`)}`);
3152
updateVersion({ nextVersion, dir: packageDir });
3253
});
54+
55+
if (updateDependencies) {
56+
print(`Updating dependencies:`);
57+
runUpdates(getListToUpdate(nextVersion, prepareJsons(packageList)));
58+
}
59+
3360
if (versionUpdated) {
3461
await versionUpdated({
3562
version: nextVersion,

ship.config.js

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,6 @@ module.exports = {
1313
json.version = version;
1414
});
1515

16-
// update package.json
17-
updateJson(dir, "package.json", (json) => {
18-
json.version = version;
19-
});
20-
21-
// update dependency
22-
updateJson(dir, "packages/shipjs/package.json", (json) => {
23-
json.dependencies["shipjs-lib"] = version;
24-
});
25-
2616
// update `version.js`
2717
fs.writeFileSync(
2818
path.resolve(dir, "packages/shipjs/src/version.js"),

website/reference/all-config.md

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ module.exports = {
1111
mainVersionFile: 'package.json',
1212
packagesToBump: ['packages/*', 'examples/*'],
1313
packagesToPublish: ['packages/*'],
14+
updateDependencies: true // optional, default: true
1415
},
1516
};
1617
```
@@ -21,13 +22,33 @@ If `monorepo` is defined, Ship.js will treat the project as a monorepo.
2122
Ship.js currently does not provide independent versioning. It means all the packages in the monorepo must have the same version.
2223
:::
2324

24-
- **`shipjs prepare`**
25+
### **`shipjs prepare`**
2526

2627
1. Ship.js reads version from `mainVersionFile`.
2728
2. When next version is decided, Ship.js will update the version at `mainVersionFile`.
2829
3. Ship.js will update all the versions in `packagesToBump`.
30+
4. When `updateDependencies: true`, it updates the dependencies, too.
2931

30-
- **`shipjs trigger`**
32+
For example,
33+
34+
```js
35+
// ship.config.js
36+
packagesToBump: ['packages/my-package-core', 'packages/my-package-js']
37+
```
38+
39+
```js
40+
// packages/my-package-js/package.json
41+
{
42+
...
43+
"dependencies": {
44+
"my-package-core": "^x.y.z"
45+
}
46+
}
47+
```
48+
49+
Ship.js will check `dependencies`, `devDependencies` and `peerDependencies` and update the version to the latest. If you don't want this behavior, put `updateDependencies: false` in the config.
50+
51+
### **`shipjs trigger`**
3152

3253
1. Ship.js will only publish the packages from `packagesToPublish`.
3354

0 commit comments

Comments
 (0)