This repository has been archived by the owner on Jan 18, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 477
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[doctor] check for @types/react-native on 48+ (and other things you s…
…houldn't install directly) (#4731) * [doctor] check for @types/react-native on 48+ * also check for npm/ yarn installs * add pnpm to direct install check
- Loading branch information
1 parent
a04201e
commit 4cd9960
Showing
6 changed files
with
198 additions
and
64 deletions.
There are no files selected for viewing
83 changes: 83 additions & 0 deletions
83
packages/expo-doctor/src/checks/DirectPackageInstallCheck.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
import { DoctorMultiCheck, DoctorMultiCheckItemBase } from './DoctorMultiCheck'; | ||
import { DoctorCheckParams, DoctorCheckResult } from './checks.types'; | ||
|
||
export type DirectPackageInstallCheckItem = { | ||
packageName: string; | ||
} & DoctorMultiCheckItemBase; | ||
|
||
const baseCheckItem = { | ||
getMessage: (packageName: string) => | ||
`The package "${packageName}" should not be installed directly in your project. It is a dependency of other Expo packages, which will install it automatically as needed.`, | ||
sdkVersionRange: '*', | ||
}; | ||
|
||
const shouldBeInstalledGloballyItem = { | ||
getMessage: (packageName: string) => | ||
`The package "${packageName}" should not be installed directly in your project. It may conflict with the globally-installed version.`, | ||
sdkVersionRange: '*', | ||
}; | ||
|
||
export const directPackageInstallCheckItems: DirectPackageInstallCheckItem[] = [ | ||
{ | ||
packageName: 'expo-modules-core', | ||
...baseCheckItem, | ||
}, | ||
{ | ||
packageName: 'expo-modules-autolinking', | ||
...baseCheckItem, | ||
}, | ||
{ | ||
packageName: 'expo-dev-launcher', | ||
...baseCheckItem, | ||
}, | ||
{ | ||
packageName: 'expo-dev-menu', | ||
...baseCheckItem, | ||
}, | ||
{ | ||
packageName: 'npm', | ||
...shouldBeInstalledGloballyItem, | ||
}, | ||
{ | ||
packageName: 'yarn', | ||
...shouldBeInstalledGloballyItem, | ||
}, | ||
{ | ||
packageName: 'pnpm', | ||
...shouldBeInstalledGloballyItem, | ||
}, | ||
{ | ||
packageName: '@types/react-native', | ||
getMessage: () => | ||
`The package "@types/react-native" should not be installed directly in your project, as types are included with the "react-native" package.`, | ||
sdkVersionRange: '>=48.0.0', | ||
}, | ||
]; | ||
|
||
export class DirectPackageInstallCheck extends DoctorMultiCheck<DirectPackageInstallCheckItem> { | ||
description = 'Check dependencies for packages that should not be installed directly'; | ||
|
||
sdkVersionRange = '*'; | ||
|
||
checkItems = directPackageInstallCheckItems; | ||
|
||
protected async runAsyncInner( | ||
{ pkg }: DoctorCheckParams, | ||
checkItems: DirectPackageInstallCheckItem[] | ||
): Promise<DoctorCheckResult> { | ||
const issues: string[] = []; | ||
|
||
// ** check for dependencies that should only be transitive ** | ||
checkItems.forEach(checkItem => { | ||
const packageName = checkItem.packageName; | ||
if (pkg.dependencies?.[packageName] || pkg.devDependencies?.[packageName]) { | ||
issues.push(checkItem.getMessage(packageName)); | ||
} | ||
}); | ||
|
||
return { | ||
isSuccessful: issues.length === 0, | ||
issues, | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import semver from 'semver'; | ||
|
||
import { DoctorCheck, DoctorCheckParams, DoctorCheckResult } from './checks.types'; | ||
|
||
export interface DoctorMultiCheckItemBase { | ||
getMessage: ((packageName: string) => string) | (() => string); | ||
sdkVersionRange: string; | ||
} | ||
|
||
// | ||
export abstract class DoctorMultiCheck<TCheckItem extends DoctorMultiCheckItemBase> | ||
implements DoctorCheck { | ||
abstract readonly checkItems: TCheckItem[]; | ||
|
||
abstract description: string; | ||
|
||
// will be the most permissive semver for all check items | ||
abstract sdkVersionRange: string; | ||
|
||
protected abstract runAsyncInner( | ||
params: DoctorCheckParams, | ||
checkItems: TCheckItem[] | ||
): Promise<DoctorCheckResult>; | ||
|
||
async runAsync(params: DoctorCheckParams): Promise<DoctorCheckResult> { | ||
const filteredCheckItems = this.checkItems.filter( | ||
check => | ||
params.exp.sdkVersion === 'UNVERSIONED' || | ||
semver.satisfies(params.exp.sdkVersion!, check.sdkVersionRange) | ||
); | ||
|
||
return this.runAsyncInner(params, filteredCheckItems); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
79 changes: 79 additions & 0 deletions
79
packages/expo-doctor/src/checks/__tests__/DirectPackageInstallCheck.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import { vol } from 'memfs'; | ||
|
||
import { | ||
DirectPackageInstallCheck, | ||
directPackageInstallCheckItems, | ||
} from '../DirectPackageInstallCheck'; | ||
|
||
jest.mock('fs'); | ||
|
||
const projectRoot = '/tmp/project'; | ||
|
||
// required by runAsync | ||
const additionalProjectProps = { | ||
exp: { | ||
name: 'name', | ||
slug: 'slug', | ||
sdkVersion: '48.0.0', | ||
}, | ||
projectRoot, | ||
}; | ||
|
||
describe('runAsync', () => { | ||
beforeEach(() => { | ||
vol.fromJSON({ | ||
[projectRoot + '/node_modules/.bin/expo']: 'test', | ||
}); | ||
}); | ||
|
||
it('returns result with isSuccessful = true if empty dependencies, devDependencies, scripts', async () => { | ||
const check = new DirectPackageInstallCheck(); | ||
const result = await check.runAsync({ | ||
pkg: { name: 'name', version: '1.0.0' }, | ||
...additionalProjectProps, | ||
}); | ||
expect(result.isSuccessful).toBeTruthy(); | ||
}); | ||
|
||
it(`returns result with isSuccessful = true if package.json contains a dependency that is on list, but SDK requirement is not met`, async () => { | ||
const check = new DirectPackageInstallCheck(); | ||
const result = await check.runAsync({ | ||
pkg: { name: 'name', version: '1.0.0', dependencies: { '@types/react-native': '17.0.1' } }, | ||
...additionalProjectProps, | ||
exp: { | ||
name: 'name', | ||
slug: 'slug', | ||
sdkVersion: '47.0.0', | ||
}, | ||
}); | ||
expect(result.isSuccessful).toBeTruthy(); | ||
}); | ||
|
||
const dependencyLocations = ['dependencies', 'devDependencies']; | ||
|
||
dependencyLocations.forEach(dependencyLocation => { | ||
it(`returns result with isSuccessful = true if ${dependencyLocation} does not contain a dependency that should only be installed by another Expo package`, async () => { | ||
const check = new DirectPackageInstallCheck(); | ||
const result = await check.runAsync({ | ||
pkg: { name: 'name', version: '1.0.0', [dependencyLocation]: { somethingjs: '17.0.1' } }, | ||
...additionalProjectProps, | ||
}); | ||
expect(result.isSuccessful).toBeTruthy(); | ||
}); | ||
|
||
directPackageInstallCheckItems.forEach(transitiveOnlyDependency => { | ||
it(`returns result with isSuccessful = false if ${dependencyLocation} contains ${transitiveOnlyDependency.packageName}`, async () => { | ||
const check = new DirectPackageInstallCheck(); | ||
const result = await check.runAsync({ | ||
pkg: { | ||
name: 'name', | ||
version: '1.0.0', | ||
[dependencyLocation]: { [transitiveOnlyDependency.packageName]: '1.0.0' }, | ||
}, | ||
...additionalProjectProps, | ||
}); | ||
expect(result.isSuccessful).toBeFalsy(); | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters