Skip to content

Commit

Permalink
feat: apply upgrade patch in subdirectory if necessary (#272)
Browse files Browse the repository at this point in the history
  • Loading branch information
thymikee committed Apr 10, 2019
1 parent 24668a7 commit 1896d1c
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 26 deletions.
5 changes: 5 additions & 0 deletions .prettierrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// added for Jest inline snapshots to not use default Prettier config
module.exports = {
bracketSpacing: false,
trailingComma: "all"
}
3 changes: 2 additions & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"react-native": "^0.59.0"
},
"devDependencies": {
"snapshot-diff": "^0.5.0"
"snapshot-diff": "^0.5.0",
"strip-ansi": "^5.2.0"
}
}
124 changes: 102 additions & 22 deletions packages/cli/src/commands/upgrade/__tests__/upgrade.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,15 @@ import execa from 'execa';
import path from 'path';
import fs from 'fs';
import snapshotDiff from 'snapshot-diff';
import stripAnsi from 'strip-ansi';
import upgrade from '../upgrade';
import {fetch} from '../helpers';
import logger from '../../../tools/logger';

jest.mock('https');
jest.mock('fs');
jest.mock('path');
jest.mock('execa', () => {
const module = jest.fn((command, args) => {
mockPushLog('$', 'execa', command, args);
if (command === 'npm' && args[3] === '--json') {
return Promise.resolve({
stdout: '{"react": "16.6.3"}',
});
}
return Promise.resolve({stdout: ''});
});
return module;
});
jest.mock('execa');
jest.mock(
'/project/root/node_modules/react-native/package.json',
() => ({name: 'react-native', version: '0.57.8'}),
Expand All @@ -32,6 +22,19 @@ jest.mock(
() => ({name: 'TestApp', dependencies: {'react-native': '^0.57.8'}}),
{virtual: true},
);
jest.mock(
'/project/root/NestedApp/node_modules/react-native/package.json',
() => ({name: 'react-native', version: '0.57.8'}),
{virtual: true},
);
jest.mock(
'/project/root/NestedApp/package.json',
() => ({
name: 'TestAppNested',
dependencies: {'react-native': '^0.57.8'},
}),
{virtual: true},
);
jest.mock('../../../tools/PackageManager', () => ({
install: args => {
mockPushLog('$ yarn add', ...args);
Expand All @@ -49,6 +52,28 @@ jest.mock('../../../tools/logger', () => ({
log: jest.fn((...args) => mockPushLog(args)),
}));

const mockExecaDefault = (command, args) => {
mockPushLog('$', 'execa', command, args);
if (command === 'npm' && args[3] === '--json') {
return Promise.resolve({stdout: '{"react": "16.6.3"}'});
}
if (command === 'git' && args[0] === 'rev-parse') {
return Promise.resolve({stdout: ''});
}
return Promise.resolve({stdout: ''});
};

const mockExecaNested = (command, args) => {
mockPushLog('$', 'execa', command, args);
if (command === 'npm' && args[3] === '--json') {
return Promise.resolve({stdout: '{"react": "16.6.3"}'});
}
if (command === 'git' && args[0] === 'rev-parse') {
return Promise.resolve({stdout: 'NestedApp/'});
}
return Promise.resolve({stdout: ''});
};

const currentVersion = '0.57.8';
const newVersion = '0.58.4';
const olderVersion = '0.56.0';
Expand All @@ -67,22 +92,47 @@ const samplePatch = jest
let logs = [];
const mockPushLog = (...args) =>
logs.push(args.map(x => (Array.isArray(x) ? x.join(' ') : x)).join(' '));
const flushOutput = () => logs.join('\n');
const flushOutput = () => stripAnsi(logs.join('\n'));

beforeEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
// $FlowFixMe
fs.writeFileSync = jest.fn(filename => mockPushLog('[fs] write', filename));
// $FlowFixMe
fs.unlinkSync = jest.fn((...args) => mockPushLog('[fs] unlink', args));
logs = [];
(execa: any).mockImplementation(mockExecaDefault);
});

afterEach(() => {
// $FlowFixMe
fs.writeFileSync = jest.requireMock('fs').writeFileSync;
// $FlowFixMe
fs.unlinkSync = jest.requireMock('fs').unlinkSync;
});

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

test('applies patch in current working directory when nested', async () => {
(fetch: any).mockImplementation(() => Promise.resolve(samplePatch));
(execa: any).mockImplementation(mockExecaNested);
const config = {...ctx, root: '/project/root/NestedApp'};
await upgrade.func([newVersion], config, opts);

expect(execa).toBeCalledWith('git', [
'apply',
'tmp-upgrade-rn.patch',
'--exclude=NestedApp/package.json',
'-p2',
'--3way',
'--directory=NestedApp/',
]);
});

test('errors when invalid version passed', async () => {
await upgrade.func(['next'], ctx, opts);
expect(logger.error).toBeCalledWith(
Expand Down Expand Up @@ -126,9 +176,10 @@ test('fetches regular patch, adds remote, applies patch, installs deps, removes
expect(flushOutput()).toMatchInlineSnapshot(`
"info Fetching diff between v0.57.8 and v0.58.4...
[fs] write tmp-upgrade-rn.patch
$ execa git apply --check tmp-upgrade-rn.patch --exclude=package.json -p2 --3way
$ execa git rev-parse --show-prefix
$ execa git apply --check tmp-upgrade-rn.patch --exclude=package.json -p2 --3way --directory=
info Applying diff...
$ execa git apply tmp-upgrade-rn.patch --exclude=package.json -p2 --3way
$ execa git apply tmp-upgrade-rn.patch --exclude=package.json -p2 --3way --directory=
[fs] unlink tmp-upgrade-rn.patch
$ execa git status -s
info Installing \\"react-native@0.58.4\\" and its peer dependencies...
Expand All @@ -146,7 +197,32 @@ success Upgraded React Native to v0.58.4 🎉. Now you can review and commit the
contextLines: 1,
}),
).toMatchSnapshot('RnDiffApp is replaced with app name (TestApp)');
});
}, 60000);
test('fetches regular patch, adds remote, applies patch, installs deps, removes remote when updated from nested directory', async () => {
(fetch: any).mockImplementation(() => Promise.resolve(samplePatch));
(execa: any).mockImplementation(mockExecaNested);
const config = {...ctx, root: '/project/root/NestedApp'};
await upgrade.func([newVersion], config, opts);
expect(flushOutput()).toMatchInlineSnapshot(`
"info Fetching diff between v0.57.8 and v0.58.4...
[fs] write tmp-upgrade-rn.patch
$ execa git rev-parse --show-prefix
$ execa git apply --check tmp-upgrade-rn.patch --exclude=NestedApp/package.json -p2 --3way --directory=NestedApp/
info Applying diff...
$ execa git apply tmp-upgrade-rn.patch --exclude=NestedApp/package.json -p2 --3way --directory=NestedApp/
[fs] unlink tmp-upgrade-rn.patch
$ execa git status -s
info Installing \\"react-native@0.58.4\\" and its peer dependencies...
$ execa npm info react-native@0.58.4 peerDependencies --json
$ yarn add react-native@0.58.4 react@16.6.3
$ execa git add package.json
$ execa git add yarn.lock
$ execa git add package-lock.json
info Running \\"git status\\" to check what changed...
$ execa git status
success Upgraded React Native to v0.58.4 🎉. Now you can review and commit the changes"
`);
}, 60000);
test('cleans up if patching fails,', async () => {
(fetch: any).mockImplementation(() => Promise.resolve(samplePatch));
(execa: any).mockImplementation((command, args) => {
Expand All @@ -162,6 +238,9 @@ test('cleans up if patching fails,', async () => {
stderr: 'error: .flowconfig: does not exist in index\n',
});
}
if (command === 'git' && args[0] === 'rev-parse') {
return Promise.resolve({stdout: ''});
}
return Promise.resolve({stdout: ''});
});
try {
Expand All @@ -174,17 +253,18 @@ test('cleans up if patching fails,', async () => {
expect(flushOutput()).toMatchInlineSnapshot(`
"info Fetching diff between v0.57.8 and v0.58.4...
[fs] write tmp-upgrade-rn.patch
$ execa git apply --check tmp-upgrade-rn.patch --exclude=package.json -p2 --3way
$ execa git rev-parse --show-prefix
$ execa git apply --check tmp-upgrade-rn.patch --exclude=package.json -p2 --3way --directory=
info Applying diff (excluding: package.json, .flowconfig)...
$ execa git apply tmp-upgrade-rn.patch --exclude=package.json --exclude=.flowconfig -p2 --3way
[2merror: .flowconfig: does not exist in index[22m
$ execa git apply tmp-upgrade-rn.patch --exclude=package.json --exclude=.flowconfig -p2 --3way --directory=
error: .flowconfig: does not exist in index
error Automatically applying diff failed
[fs] unlink tmp-upgrade-rn.patch
$ execa git status -s
error Patch failed to apply for unknown reason. Please fall back to manual way of upgrading
info You may find these resources helpful:
• Release notes: [4m[2mhttps://github.com/facebook/react-native/releases/tag/v0.58.4[22m[24m
• Comparison between versions: [4m[2mhttps://github.com/react-native-community/rn-diff-purge/compare/version/0.57.8..version/0.58.4[22m[24m
• Git diff: [4m[2mhttps://github.com/react-native-community/rn-diff-purge/compare/version/0.57.8..version/0.58.4.diff[22m[24m"
• Release notes: https://github.com/facebook/react-native/releases/tag/v0.58.4
• Comparison between versions: https://github.com/react-native-community/rn-diff-purge/compare/version/0.57.8..version/0.58.4
• Git diff: https://github.com/react-native-community/rn-diff-purge/compare/version/0.57.8..version/0.58.4.diff"
`);
});
23 changes: 20 additions & 3 deletions packages/cli/src/commands/upgrade/upgrade.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,16 +135,24 @@ const applyPatch = async (
tmpPatchFile: string,
) => {
let filesToExclude = ['package.json'];
// $FlowFixMe ThenableChildProcess is incompatible with Promise
const {stdout: relativePathFromRoot} = await execa('git', [
'rev-parse',
'--show-prefix',
]);
try {
try {
const excludes = filesToExclude.map(e => `--exclude=${e}`);
const excludes = filesToExclude.map(
e => `--exclude=${path.join(relativePathFromRoot, e)}`,
);
await execa('git', [
'apply',
'--check',
tmpPatchFile,
...excludes,
'-p2',
'--3way',
`--directory=${relativePathFromRoot}`,
]);
logger.info('Applying diff...');
} catch (error) {
Expand All @@ -158,8 +166,17 @@ const applyPatch = async (

logger.info(`Applying diff (excluding: ${filesToExclude.join(', ')})...`);
} finally {
const excludes = filesToExclude.map(e => `--exclude=${e}`);
await execa('git', ['apply', tmpPatchFile, ...excludes, '-p2', '--3way']);
const excludes = filesToExclude.map(
e => `--exclude=${path.join(relativePathFromRoot, e)}`,
);
await execa('git', [
'apply',
tmpPatchFile,
...excludes,
'-p2',
'--3way',
`--directory=${relativePathFromRoot}`,
]);
}
} catch (error) {
if (error.stderr) {
Expand Down
12 changes: 12 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1707,6 +1707,11 @@ ansi-regex@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.0.0.tgz#70de791edf021404c3fd615aa89118ae0432e5a9"

ansi-regex@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997"
integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==

ansi-styles@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
Expand Down Expand Up @@ -7463,6 +7468,13 @@ strip-ansi@^5.0.0:
dependencies:
ansi-regex "^4.0.0"

strip-ansi@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==
dependencies:
ansi-regex "^4.1.0"

strip-bom@3.0.0, strip-bom@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
Expand Down

0 comments on commit 1896d1c

Please sign in to comment.