Skip to content

Commit 1896d1c

Browse files
committed
feat: apply upgrade patch in subdirectory if necessary (#272)
1 parent 24668a7 commit 1896d1c

File tree

5 files changed

+141
-26
lines changed

5 files changed

+141
-26
lines changed

.prettierrc.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// added for Jest inline snapshots to not use default Prettier config
2+
module.exports = {
3+
bracketSpacing: false,
4+
trailingComma: "all"
5+
}

packages/cli/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
"react-native": "^0.59.0"
5959
},
6060
"devDependencies": {
61-
"snapshot-diff": "^0.5.0"
61+
"snapshot-diff": "^0.5.0",
62+
"strip-ansi": "^5.2.0"
6263
}
6364
}

packages/cli/src/commands/upgrade/__tests__/upgrade.test.js

Lines changed: 102 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,15 @@ import execa from 'execa';
33
import path from 'path';
44
import fs from 'fs';
55
import snapshotDiff from 'snapshot-diff';
6+
import stripAnsi from 'strip-ansi';
67
import upgrade from '../upgrade';
78
import {fetch} from '../helpers';
89
import logger from '../../../tools/logger';
910

1011
jest.mock('https');
1112
jest.mock('fs');
1213
jest.mock('path');
13-
jest.mock('execa', () => {
14-
const module = jest.fn((command, args) => {
15-
mockPushLog('$', 'execa', command, args);
16-
if (command === 'npm' && args[3] === '--json') {
17-
return Promise.resolve({
18-
stdout: '{"react": "16.6.3"}',
19-
});
20-
}
21-
return Promise.resolve({stdout: ''});
22-
});
23-
return module;
24-
});
14+
jest.mock('execa');
2515
jest.mock(
2616
'/project/root/node_modules/react-native/package.json',
2717
() => ({name: 'react-native', version: '0.57.8'}),
@@ -32,6 +22,19 @@ jest.mock(
3222
() => ({name: 'TestApp', dependencies: {'react-native': '^0.57.8'}}),
3323
{virtual: true},
3424
);
25+
jest.mock(
26+
'/project/root/NestedApp/node_modules/react-native/package.json',
27+
() => ({name: 'react-native', version: '0.57.8'}),
28+
{virtual: true},
29+
);
30+
jest.mock(
31+
'/project/root/NestedApp/package.json',
32+
() => ({
33+
name: 'TestAppNested',
34+
dependencies: {'react-native': '^0.57.8'},
35+
}),
36+
{virtual: true},
37+
);
3538
jest.mock('../../../tools/PackageManager', () => ({
3639
install: args => {
3740
mockPushLog('$ yarn add', ...args);
@@ -49,6 +52,28 @@ jest.mock('../../../tools/logger', () => ({
4952
log: jest.fn((...args) => mockPushLog(args)),
5053
}));
5154

55+
const mockExecaDefault = (command, args) => {
56+
mockPushLog('$', 'execa', command, args);
57+
if (command === 'npm' && args[3] === '--json') {
58+
return Promise.resolve({stdout: '{"react": "16.6.3"}'});
59+
}
60+
if (command === 'git' && args[0] === 'rev-parse') {
61+
return Promise.resolve({stdout: ''});
62+
}
63+
return Promise.resolve({stdout: ''});
64+
};
65+
66+
const mockExecaNested = (command, args) => {
67+
mockPushLog('$', 'execa', command, args);
68+
if (command === 'npm' && args[3] === '--json') {
69+
return Promise.resolve({stdout: '{"react": "16.6.3"}'});
70+
}
71+
if (command === 'git' && args[0] === 'rev-parse') {
72+
return Promise.resolve({stdout: 'NestedApp/'});
73+
}
74+
return Promise.resolve({stdout: ''});
75+
};
76+
5277
const currentVersion = '0.57.8';
5378
const newVersion = '0.58.4';
5479
const olderVersion = '0.56.0';
@@ -67,22 +92,47 @@ const samplePatch = jest
6792
let logs = [];
6893
const mockPushLog = (...args) =>
6994
logs.push(args.map(x => (Array.isArray(x) ? x.join(' ') : x)).join(' '));
70-
const flushOutput = () => logs.join('\n');
95+
const flushOutput = () => stripAnsi(logs.join('\n'));
7196

7297
beforeEach(() => {
7398
jest.clearAllMocks();
99+
jest.restoreAllMocks();
74100
// $FlowFixMe
75101
fs.writeFileSync = jest.fn(filename => mockPushLog('[fs] write', filename));
76102
// $FlowFixMe
77103
fs.unlinkSync = jest.fn((...args) => mockPushLog('[fs] unlink', args));
78104
logs = [];
105+
(execa: any).mockImplementation(mockExecaDefault);
106+
});
107+
108+
afterEach(() => {
109+
// $FlowFixMe
110+
fs.writeFileSync = jest.requireMock('fs').writeFileSync;
111+
// $FlowFixMe
112+
fs.unlinkSync = jest.requireMock('fs').unlinkSync;
79113
});
80114

81115
test('uses latest version of react-native when none passed', async () => {
82116
await upgrade.func([], ctx, opts);
83117
expect(execa).toBeCalledWith('npm', ['info', 'react-native', 'version']);
84118
});
85119

120+
test('applies patch in current working directory when nested', async () => {
121+
(fetch: any).mockImplementation(() => Promise.resolve(samplePatch));
122+
(execa: any).mockImplementation(mockExecaNested);
123+
const config = {...ctx, root: '/project/root/NestedApp'};
124+
await upgrade.func([newVersion], config, opts);
125+
126+
expect(execa).toBeCalledWith('git', [
127+
'apply',
128+
'tmp-upgrade-rn.patch',
129+
'--exclude=NestedApp/package.json',
130+
'-p2',
131+
'--3way',
132+
'--directory=NestedApp/',
133+
]);
134+
});
135+
86136
test('errors when invalid version passed', async () => {
87137
await upgrade.func(['next'], ctx, opts);
88138
expect(logger.error).toBeCalledWith(
@@ -126,9 +176,10 @@ test('fetches regular patch, adds remote, applies patch, installs deps, removes
126176
expect(flushOutput()).toMatchInlineSnapshot(`
127177
"info Fetching diff between v0.57.8 and v0.58.4...
128178
[fs] write tmp-upgrade-rn.patch
129-
$ execa git apply --check tmp-upgrade-rn.patch --exclude=package.json -p2 --3way
179+
$ execa git rev-parse --show-prefix
180+
$ execa git apply --check tmp-upgrade-rn.patch --exclude=package.json -p2 --3way --directory=
130181
info Applying diff...
131-
$ execa git apply tmp-upgrade-rn.patch --exclude=package.json -p2 --3way
182+
$ execa git apply tmp-upgrade-rn.patch --exclude=package.json -p2 --3way --directory=
132183
[fs] unlink tmp-upgrade-rn.patch
133184
$ execa git status -s
134185
info Installing \\"[email protected]\\" and its peer dependencies...
@@ -146,7 +197,32 @@ success Upgraded React Native to v0.58.4 🎉. Now you can review and commit the
146197
contextLines: 1,
147198
}),
148199
).toMatchSnapshot('RnDiffApp is replaced with app name (TestApp)');
149-
});
200+
}, 60000);
201+
test('fetches regular patch, adds remote, applies patch, installs deps, removes remote when updated from nested directory', async () => {
202+
(fetch: any).mockImplementation(() => Promise.resolve(samplePatch));
203+
(execa: any).mockImplementation(mockExecaNested);
204+
const config = {...ctx, root: '/project/root/NestedApp'};
205+
await upgrade.func([newVersion], config, opts);
206+
expect(flushOutput()).toMatchInlineSnapshot(`
207+
"info Fetching diff between v0.57.8 and v0.58.4...
208+
[fs] write tmp-upgrade-rn.patch
209+
$ execa git rev-parse --show-prefix
210+
$ execa git apply --check tmp-upgrade-rn.patch --exclude=NestedApp/package.json -p2 --3way --directory=NestedApp/
211+
info Applying diff...
212+
$ execa git apply tmp-upgrade-rn.patch --exclude=NestedApp/package.json -p2 --3way --directory=NestedApp/
213+
[fs] unlink tmp-upgrade-rn.patch
214+
$ execa git status -s
215+
info Installing \\"[email protected]\\" and its peer dependencies...
216+
$ execa npm info [email protected] peerDependencies --json
217+
218+
$ execa git add package.json
219+
$ execa git add yarn.lock
220+
$ execa git add package-lock.json
221+
info Running \\"git status\\" to check what changed...
222+
$ execa git status
223+
success Upgraded React Native to v0.58.4 🎉. Now you can review and commit the changes"
224+
`);
225+
}, 60000);
150226
test('cleans up if patching fails,', async () => {
151227
(fetch: any).mockImplementation(() => Promise.resolve(samplePatch));
152228
(execa: any).mockImplementation((command, args) => {
@@ -162,6 +238,9 @@ test('cleans up if patching fails,', async () => {
162238
stderr: 'error: .flowconfig: does not exist in index\n',
163239
});
164240
}
241+
if (command === 'git' && args[0] === 'rev-parse') {
242+
return Promise.resolve({stdout: ''});
243+
}
165244
return Promise.resolve({stdout: ''});
166245
});
167246
try {
@@ -174,17 +253,18 @@ test('cleans up if patching fails,', async () => {
174253
expect(flushOutput()).toMatchInlineSnapshot(`
175254
"info Fetching diff between v0.57.8 and v0.58.4...
176255
[fs] write tmp-upgrade-rn.patch
177-
$ execa git apply --check tmp-upgrade-rn.patch --exclude=package.json -p2 --3way
256+
$ execa git rev-parse --show-prefix
257+
$ execa git apply --check tmp-upgrade-rn.patch --exclude=package.json -p2 --3way --directory=
178258
info Applying diff (excluding: package.json, .flowconfig)...
179-
$ execa git apply tmp-upgrade-rn.patch --exclude=package.json --exclude=.flowconfig -p2 --3way
180-
[2merror: .flowconfig: does not exist in index[22m
259+
$ execa git apply tmp-upgrade-rn.patch --exclude=package.json --exclude=.flowconfig -p2 --3way --directory=
260+
error: .flowconfig: does not exist in index
181261
error Automatically applying diff failed
182262
[fs] unlink tmp-upgrade-rn.patch
183263
$ execa git status -s
184264
error Patch failed to apply for unknown reason. Please fall back to manual way of upgrading
185265
info You may find these resources helpful:
186-
• Release notes: [4m[2mhttps://github.com/facebook/react-native/releases/tag/v0.58.4[22m[24m
187-
• Comparison between versions: [4m[2mhttps://github.com/react-native-community/rn-diff-purge/compare/version/0.57.8..version/0.58.4[22m[24m
188-
• Git diff: [4m[2mhttps://github.com/react-native-community/rn-diff-purge/compare/version/0.57.8..version/0.58.4.diff[22m[24m"
266+
• Release notes: https://github.com/facebook/react-native/releases/tag/v0.58.4
267+
• Comparison between versions: https://github.com/react-native-community/rn-diff-purge/compare/version/0.57.8..version/0.58.4
268+
• Git diff: https://github.com/react-native-community/rn-diff-purge/compare/version/0.57.8..version/0.58.4.diff"
189269
`);
190270
});

packages/cli/src/commands/upgrade/upgrade.js

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,16 +135,24 @@ const applyPatch = async (
135135
tmpPatchFile: string,
136136
) => {
137137
let filesToExclude = ['package.json'];
138+
// $FlowFixMe ThenableChildProcess is incompatible with Promise
139+
const {stdout: relativePathFromRoot} = await execa('git', [
140+
'rev-parse',
141+
'--show-prefix',
142+
]);
138143
try {
139144
try {
140-
const excludes = filesToExclude.map(e => `--exclude=${e}`);
145+
const excludes = filesToExclude.map(
146+
e => `--exclude=${path.join(relativePathFromRoot, e)}`,
147+
);
141148
await execa('git', [
142149
'apply',
143150
'--check',
144151
tmpPatchFile,
145152
...excludes,
146153
'-p2',
147154
'--3way',
155+
`--directory=${relativePathFromRoot}`,
148156
]);
149157
logger.info('Applying diff...');
150158
} catch (error) {
@@ -158,8 +166,17 @@ const applyPatch = async (
158166

159167
logger.info(`Applying diff (excluding: ${filesToExclude.join(', ')})...`);
160168
} finally {
161-
const excludes = filesToExclude.map(e => `--exclude=${e}`);
162-
await execa('git', ['apply', tmpPatchFile, ...excludes, '-p2', '--3way']);
169+
const excludes = filesToExclude.map(
170+
e => `--exclude=${path.join(relativePathFromRoot, e)}`,
171+
);
172+
await execa('git', [
173+
'apply',
174+
tmpPatchFile,
175+
...excludes,
176+
'-p2',
177+
'--3way',
178+
`--directory=${relativePathFromRoot}`,
179+
]);
163180
}
164181
} catch (error) {
165182
if (error.stderr) {

yarn.lock

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1707,6 +1707,11 @@ ansi-regex@^4.0.0:
17071707
version "4.0.0"
17081708
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.0.0.tgz#70de791edf021404c3fd615aa89118ae0432e5a9"
17091709

1710+
ansi-regex@^4.1.0:
1711+
version "4.1.0"
1712+
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997"
1713+
integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==
1714+
17101715
ansi-styles@^2.2.1:
17111716
version "2.2.1"
17121717
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
@@ -7463,6 +7468,13 @@ strip-ansi@^5.0.0:
74637468
dependencies:
74647469
ansi-regex "^4.0.0"
74657470

7471+
strip-ansi@^5.2.0:
7472+
version "5.2.0"
7473+
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
7474+
integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==
7475+
dependencies:
7476+
ansi-regex "^4.1.0"
7477+
74667478
[email protected], strip-bom@^3.0.0:
74677479
version "3.0.0"
74687480
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"

0 commit comments

Comments
 (0)