Skip to content

Commit d4c4d3e

Browse files
author
Murat
committed
feat(upgrade): upgrade command now imports integrate-lock, packageJson and .upgrade folder
1 parent 92b1dfb commit d4c4d3e

21 files changed

+633
-46
lines changed

src/__tests__/mocks/mockAll.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,9 @@ if (!didSetup) {
123123
'mock-package': '^1.2.3',
124124
'react-native': '^1.2.3',
125125
},
126+
engines: {
127+
node: '>=16',
128+
},
126129
});
127130
});
128131
didSetup = true;

src/__tests__/mocks/mockFs.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@ export const mockFs = {
2828
},
2929
copyFile: jest.fn((from: string, to: string, cb: CallableFunction) => {
3030
mockFs.copyFileSync(from, to);
31-
cb(true);
31+
cb();
3232
}),
3333
mkdirSync: (): boolean => {
3434
return true;
3535
},
36-
mkdir: jest.fn((_path, _opts, cb: CallableFunction) => cb(true) as void),
36+
mkdir: jest.fn((_path, _opts, cb: CallableFunction) => cb() as void),
3737
readdirSync: (): string[] => {
3838
return ['test' + Constants.XCODEPROJ_EXT];
3939
},

src/__tests__/mocks/mockPrompter.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export const mockPrompter = {
2222
multiselect: jest.fn(({ options }: MultiselectPromptArgs) =>
2323
options.map(x => x.value)
2424
),
25+
select: jest.fn(({ options }: MultiselectPromptArgs) => options[0].value),
2526
text: jest.fn(() => 'test'),
2627
cancel: jest.fn(),
2728
isCancel: jest.fn(() => false),

src/__tests__/unit/prompter.spec.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
logSuccess,
1717
logWarning,
1818
multiselect,
19+
select,
1920
startSpinner,
2021
stopSpinner,
2122
summarize,
@@ -154,6 +155,15 @@ describe('prompter', () => {
154155
expect(mockPrompter.multiselect).toHaveBeenCalled();
155156
expect(opts).toEqual(['opt1', 'opt2']);
156157
});
158+
it('should prompt select', async () => {
159+
mockPrompter.select.mockClear();
160+
const opts = await select('test', {
161+
options: [{ value: 'opt1' }, { value: 'opt2' }],
162+
});
163+
164+
expect(mockPrompter.select).toHaveBeenCalled();
165+
expect(opts).toEqual('opt1');
166+
});
157167
it('should cancel confirm', async () => {
158168
mockPrompter.confirm.mockClear();
159169
mockPrompter.isCancel.mockImplementationOnce(() => true);
@@ -196,6 +206,21 @@ it('should cancel multiselect', async () => {
196206

197207
expect(mockPrompter.multiselect).toHaveBeenCalled();
198208
});
209+
it('should cancel select', async () => {
210+
mockPrompter.select.mockClear();
211+
mockPrompter.isCancel.mockImplementationOnce(() => true);
212+
213+
// @ts-ignore
214+
// eslint-disable-next-line @typescript-eslint/no-empty-function
215+
216+
await expect(async () =>
217+
select('test', {
218+
options: [{ value: 'opt1' }, { value: 'opt2' }],
219+
})
220+
).rejects.toThrowError('program aborted');
221+
222+
expect(mockPrompter.select).toHaveBeenCalled();
223+
});
199224

200225
describe('summarize', () => {
201226
it('should shorten long text', () => {
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
require('../../../../mocks/mockAll');
2+
import path from 'path';
3+
import { Constants } from '../../../../../constants';
4+
import { LockData } from '../../../../../types/integrator.types';
5+
import { ImportGetter } from '../../../../../types/upgrade.types';
6+
import { getProjectPath } from '../../../../../utils/getProjectPath';
7+
import { importIntegrateLockJson } from '../../../../../utils/upgrade/other/importIntegrateLockJson';
8+
import { mockFs } from '../../../../mocks/mockFs';
9+
10+
describe('importIntegrateLockJson', () => {
11+
it('should get integrate-lock.json', async () => {
12+
mockFs.writeFileSync(
13+
'/oldProject/' + Constants.LOCK_FILE_NAME,
14+
JSON.stringify(
15+
{
16+
lockfileVersion: 1,
17+
packages: {
18+
'mock-package': {
19+
integrated: true,
20+
version: '1.0.0',
21+
},
22+
'non-integrated-mock-package': {
23+
integrated: false,
24+
version: '1.0.0',
25+
},
26+
},
27+
} as LockData,
28+
null,
29+
2
30+
)
31+
);
32+
33+
const importGetter = importIntegrateLockJson('/oldProject') as ImportGetter;
34+
expect(importGetter).toBeTruthy();
35+
expect(importGetter.value).toEqual('2 total, 1 integrated packages');
36+
37+
await importGetter.setter();
38+
39+
expect(
40+
mockFs.readFileSync(path.join(getProjectPath(), Constants.LOCK_FILE_NAME))
41+
).toContain('non-integrated-mock-package');
42+
});
43+
it('should handle errors', () => {
44+
mockFs.setReadPermission(false);
45+
mockFs.writeFileSync('/oldProject/' + Constants.LOCK_FILE_NAME, '');
46+
47+
const importGetter = importIntegrateLockJson('/oldProject') as ImportGetter;
48+
expect(importGetter).toBeNull();
49+
});
50+
it('should handle not finding integrate lock', () => {
51+
const importGetter = importIntegrateLockJson('/oldProject') as ImportGetter;
52+
expect(importGetter).toBeNull();
53+
});
54+
});
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
require('../../../../mocks/mockAll');
2+
const mockSpawn = jest.spyOn(require('child_process'), 'spawn');
3+
4+
import path from 'path';
5+
import { Constants } from '../../../../../constants';
6+
import { PackageJsonType } from '../../../../../types/mod.types';
7+
import { ImportGetter } from '../../../../../types/upgrade.types';
8+
import { getProjectPath } from '../../../../../utils/getProjectPath';
9+
import {
10+
getInstallCommand,
11+
importPackageJson,
12+
} from '../../../../../utils/upgrade/other/importPackageJson';
13+
import { mockFs } from '../../../../mocks/mockFs';
14+
import { mockPrompter } from '../../../../mocks/mockPrompter';
15+
16+
describe('importPackageJson', () => {
17+
it('should get package.json', async () => {
18+
mockFs.writeFileSync(
19+
'/oldProject/' + Constants.PACKAGE_JSON_FILE_NAME,
20+
JSON.stringify(
21+
{
22+
name: 'test',
23+
version: '1.0.0',
24+
description: 'old',
25+
scripts: {
26+
start: 'start',
27+
},
28+
dependencies: {
29+
'react-native': '1.0.0',
30+
'mock-package': '1.0.0',
31+
'non-integrated-mock-package': '1.0.0',
32+
},
33+
devDependencies: {
34+
'some-package': '1.0.0',
35+
},
36+
something: 'value',
37+
engines: {
38+
node: '>=14',
39+
},
40+
} as PackageJsonType,
41+
null,
42+
2
43+
)
44+
);
45+
46+
const importGetter = importPackageJson('/oldProject') as ImportGetter;
47+
expect(importGetter).toBeTruthy();
48+
expect(importGetter.value).toEqual('[email protected]');
49+
50+
mockSpawn.mockImplementationOnce(() => ({
51+
on: (_event: string, cb: CallableFunction) => {
52+
cb(0);
53+
},
54+
}));
55+
56+
await importGetter.setter();
57+
58+
expect(
59+
mockFs.readFileSync(
60+
path.join(getProjectPath(), Constants.PACKAGE_JSON_FILE_NAME)
61+
)
62+
).toContain('non-integrated-mock-package');
63+
});
64+
it('should get package.json and handle failed module installation', async () => {
65+
mockFs.writeFileSync(
66+
'/oldProject/' + Constants.PACKAGE_JSON_FILE_NAME,
67+
JSON.stringify(
68+
{
69+
name: 'test',
70+
dependencies: {
71+
'react-native': '1.0.0',
72+
'some-package': '1.0.0',
73+
},
74+
} as PackageJsonType,
75+
null,
76+
2
77+
)
78+
);
79+
80+
const importGetter = importPackageJson('/oldProject') as ImportGetter;
81+
82+
mockSpawn.mockImplementationOnce(() => ({
83+
on: (_event: string, cb: CallableFunction) => {
84+
cb(1);
85+
},
86+
}));
87+
mockPrompter.multiselect.mockClear();
88+
89+
await importGetter.setter();
90+
expect(mockPrompter.multiselect).toHaveBeenCalled();
91+
});
92+
it('should handle errors', () => {
93+
mockFs.setReadPermission(false);
94+
mockFs.writeFileSync('/oldProject/' + Constants.PACKAGE_JSON_FILE_NAME, '');
95+
96+
const importGetter = importPackageJson('/oldProject') as ImportGetter;
97+
expect(importGetter).toBeNull();
98+
});
99+
it('should handle not finding react-native in package lock', () => {
100+
mockFs.writeFileSync(
101+
'/oldProject/' + Constants.PACKAGE_JSON_FILE_NAME,
102+
JSON.stringify(
103+
{
104+
name: 'test',
105+
version: '1.0.0',
106+
dependencies: {
107+
'mock-package': '1.0.0',
108+
'non-integrated-mock-package': '1.0.0',
109+
},
110+
} as PackageJsonType,
111+
null,
112+
2
113+
)
114+
);
115+
116+
const importGetter = importPackageJson('/oldProject') as ImportGetter;
117+
expect(importGetter).toBeNull();
118+
});
119+
120+
describe('getInstallCommand', () => {
121+
it('should ask user when multiple lock exists', async () => {
122+
mockFs.writeFileSync('/oldProject/package-lock.json', '');
123+
mockFs.writeFileSync('/oldProject/yarn.lock', '');
124+
mockFs.writeFileSync('/oldProject/pnpm-lock.yaml', '');
125+
mockFs.writeFileSync('/oldProject/bun.lockb', '');
126+
127+
mockPrompter.select.mockClear();
128+
const cmd = await getInstallCommand('/oldProject');
129+
130+
expect(mockPrompter.select).toHaveBeenCalled();
131+
expect(cmd).toEqual('npm install');
132+
});
133+
it('should proceed when single lock exists', async () => {
134+
mockFs.writeFileSync('/oldProject/yarn.lock', '');
135+
136+
mockPrompter.select.mockClear();
137+
const cmd = await getInstallCommand('/oldProject');
138+
139+
expect(mockPrompter.select).not.toHaveBeenCalled();
140+
expect(cmd).toEqual('yarn install');
141+
});
142+
});
143+
});
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
require('../../../../mocks/mockAll');
2+
import path from 'path';
3+
import { ImportGetter } from '../../../../../types/upgrade.types';
4+
import { getProjectPath } from '../../../../../utils/getProjectPath';
5+
import { importUpgradeFolder } from '../../../../../utils/upgrade/other/importUpgradeFolder';
6+
import { mockFs } from '../../../../mocks/mockFs';
7+
8+
describe('importUpgradeFolder', () => {
9+
it('should get .upgrade', async () => {
10+
mockFs.writeFileSync('/oldProject/.upgrade/some.file', 'random');
11+
12+
const importGetter = importUpgradeFolder('/oldProject') as ImportGetter;
13+
expect(importGetter).toBeTruthy();
14+
expect(importGetter.value).toEqual('1 files');
15+
16+
await importGetter.setter();
17+
18+
expect(
19+
mockFs.readFileSync(path.join(getProjectPath(), '.upgrade/some.file'))
20+
).toContain('random');
21+
});
22+
it('should handle errors', () => {
23+
mockFs.setReadPermission(false);
24+
mockFs.writeFileSync('/oldProject/.upgrade/some.file', 'random');
25+
26+
const importGetter = importUpgradeFolder('/oldProject') as ImportGetter;
27+
expect(importGetter).toBeNull();
28+
});
29+
it('should handle not finding .upgrade folder', () => {
30+
const importGetter = importUpgradeFolder('/oldProject') as ImportGetter;
31+
expect(importGetter).toBeNull();
32+
});
33+
it('should handle copy error', async () => {
34+
mockFs.writeFileSync('/oldProject/.upgrade/some.file', 'random');
35+
36+
mockFs.copyFile.mockImplementationOnce(
37+
(from: string, to: string, cb: CallableFunction) => {
38+
cb(new Error('random'));
39+
}
40+
);
41+
const importGetter = importUpgradeFolder('/oldProject') as ImportGetter;
42+
await expect(importGetter.setter()).rejects.toThrow('random');
43+
});
44+
});

src/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export const Constants = {
22
CURRENT_LOCK_VERSION: 1,
33
LOCK_FILE_NAME: 'integrate-lock.json',
4+
UPGRADE_FOLDER_NAME: '.upgrade',
45
PACKAGE_JSON_FILE_NAME: 'package.json',
56
APP_DELEGATE_FILE_NAME: 'AppDelegate.mm',
67
NOTIFICATION_SERVICE_FILE_NAME: 'NotificationService.m',

src/prompter.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,16 @@ import {
77
multiselect as promptMultiselect,
88
note,
99
outro,
10+
select as promptSelect,
1011
spinner,
1112
text as promptText,
1213
} from '@clack/prompts';
1314
import color from 'picocolors';
1415
import {
1516
ConfirmPromptArgs,
16-
MultiselectOptionValue,
1717
MultiselectPromptArgs,
18+
OptionValue,
19+
SelectPromptArgs,
1820
TextPromptArgs,
1921
} from './types/prompt.types';
2022
import { listenForKeys } from './utils/waitInputToContinue';
@@ -85,7 +87,7 @@ export function stopSpinner(msg: string): void {
8587
export async function multiselect(
8688
msg: string,
8789
args: MultiselectPromptArgs
88-
): Promise<MultiselectOptionValue[]> {
90+
): Promise<OptionValue[]> {
8991
const response = await promptMultiselect({
9092
message: msg,
9193
required: args.required,
@@ -104,6 +106,28 @@ export async function multiselect(
104106
return response;
105107
}
106108

109+
export async function select(
110+
msg: string,
111+
args: SelectPromptArgs
112+
): Promise<OptionValue> {
113+
const response = await promptSelect({
114+
message: msg,
115+
options: args.options.map(x => ({
116+
value: x.value,
117+
label: x.label || x.value.toString(),
118+
hint: x.hint,
119+
})),
120+
initialValue: args.initialValue,
121+
maxItems: args.maxItems,
122+
});
123+
if (isCancel(response)) {
124+
cancel('operation cancelled');
125+
process.abort();
126+
}
127+
// @ts-ignore
128+
return response;
129+
}
130+
107131
export async function confirm(
108132
msg: string,
109133
args: ConfirmPromptArgs = {}

src/tasks/mainApplicationTask.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,8 @@ function getMainApplicationPath(lang?: AndroidCodeType) {
144144
lang === 'kotlin'
145145
? Constants.MAIN_APPLICATION_KT_FILE_NAME
146146
: Constants.MAIN_APPLICATION_JAVA_FILE_NAME,
147-
].join('/')
147+
].join('/'),
148+
{ nodir: true }
148149
)[0];
149150
if (!mainApplicationPath)
150151
throw new Error(

0 commit comments

Comments
 (0)