feat(dep-check): add migration message (#1941)

This commit is contained in:
Tommy Nguyen 2022-11-11 08:53:04 +01:00 коммит произвёл GitHub
Родитель f7194964b1
Коммит 93b480ab8e
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
54 изменённых файлов: 34 добавлений и 3815 удалений

Просмотреть файл

@ -0,0 +1,5 @@
---
"@rnx-kit/dep-check": minor
---
Tell users to migrate to `@rnx-kit/align-deps`

Просмотреть файл

@ -1,3 +1,7 @@
# `@rnx-kit/dep-check` has been renamed to `@rnx-kit/align-deps`
For more details, read the RFC: https://github.com/microsoft/rnx-kit/pull/1757
<!--remove-block start-->
# @rnx-kit/dep-check

Просмотреть файл

@ -21,11 +21,7 @@
"scripts": {
"build": "rnx-kit-scripts build",
"bundle": "rnx-kit-scripts bundle",
"format": "rnx-kit-scripts format",
"lint": "rnx-kit-scripts lint",
"test": "rnx-kit-scripts test",
"update-profile": "node scripts/update-profile.mjs",
"update-readme": "node scripts/update-readme.js"
"format": "rnx-kit-scripts format"
},
"devDependencies": {
"@rnx-kit/config": "*",
@ -44,8 +40,6 @@
"detect-indent": "^6.0.0",
"jest-diff": "^26.0.0",
"lodash": "^4.17.21",
"markdown-table": "^2.0.0",
"pacote": "^12.0.0",
"prompts": "^2.4.0",
"semver": "^7.0.0",
"yargs": "^16.0.0"
@ -55,8 +49,5 @@
},
"eslintConfig": {
"extends": "@rnx-kit/eslint-config"
},
"jest": {
"preset": "@rnx-kit/scripts"
}
}

Просмотреть файл

@ -1,399 +0,0 @@
#!/usr/bin/env node
// @ts-check
import { existsSync as fileExists } from "fs";
import * as fs from "fs/promises";
import markdownTable from "markdown-table";
import pacote from "pacote";
import * as path from "path";
import semverCoerce from "semver/functions/coerce.js";
import semverCompare from "semver/functions/compare.js";
import { fileURLToPath } from "url";
import { isMetaPackage } from "../lib/capabilities.js";
/**
* @typedef {import("../src/types").MetaPackage} MetaPackage
* @typedef {import("../src/types").Package} Package
* @typedef {import("../src/types").Profile} Profile
*
* @typedef {{
* name: string;
* version: string;
* latest: string;
* homepage: string;
* dependencies?: Record<string, string>;
* peerDependencies?: Record<string, string>;
* }} PackageInfo
*/
/**
* Fetches package manifest from npm.
* @param {MetaPackage | Package} pkg
* @param {string=} defaultTag
* @returns {Promise<PackageInfo | void>}
*/
async function fetchPackageInfo(pkg, defaultTag = "latest") {
if (isMetaPackage(pkg)) {
return Promise.resolve();
}
const { name, version } = pkg;
const manifest = await pacote.manifest(name, {
defaultTag,
fullMetadata: true,
});
return {
name,
version,
latest: manifest.version,
homepage: manifest.homepage,
dependencies: manifest.dependencies,
peerDependencies: manifest.peerDependencies,
};
}
/**
* @param {string} packageName
* @param {Record<string, string>?} dependencies
* @returns {string}
*/
function getPackageVersion(packageName, dependencies) {
const packageVersion = dependencies?.[packageName];
if (!packageVersion) {
throw new Error(`Failed to get '${packageName}' version`);
}
return semverCoerce(packageVersion).version;
}
/**
* Returns the path to a profile.
* @param {string} preset
* @param {string} profileVersion
* @returns {string}
*/
function getProfilePath(preset, profileVersion) {
const __dirname = path.dirname(fileURLToPath(import.meta.url));
return path.relative(
process.cwd(),
path.join(
__dirname,
"..",
"src",
"presets",
preset,
`profile-${profileVersion}.ts`
)
);
}
/**
* Generates a profile.
* @param {{
* preset: string;
* targetVersion: string;
* reactVersion: string;
* metroVersion: string;
* }} versions
* @returns {string}
*/
function generateFromTemplate({
preset,
targetVersion,
reactVersion,
metroVersion,
}) {
const nextVersionCoerced = semverCoerce(targetVersion);
const currentVersion = `${nextVersionCoerced.major}.${
nextVersionCoerced.minor - 1
}`;
const currentProfile = getProfilePath(preset, currentVersion);
if (!fileExists(currentProfile)) {
throw new Error(`Could not find '${currentProfile}'`);
}
const currentVersionVarName = `${nextVersionCoerced.major}_${
nextVersionCoerced.minor - 1
}`;
return `import type { Profile, Package } from "../../types";
import profile_${currentVersionVarName} from "./profile-${currentVersion}";
const reactNative: Package = {
name: "react-native",
version: "^${targetVersion}.0",
capabilities: ["react"],
};
const profile: Profile = {
...profile_${currentVersionVarName},
react: {
name: "react",
version: "${reactVersion}",
},
"react-dom": {
name: "react-dom",
version: "^${reactVersion}",
capabilities: ["react"],
},
"react-test-renderer": {
name: "react-test-renderer",
version: "${reactVersion}",
capabilities: ["react"],
devOnly: true,
},
core: reactNative,
"core-android": reactNative,
"core-ios": reactNative,
"core-macos": {
name: "react-native-macos",
version: "^${targetVersion}.0",
capabilities: ["react"],
},
"core-windows": {
name: "react-native-windows",
version: "^${targetVersion}.0",
capabilities: ["core"],
},
"babel-preset-react-native": {
name: "metro-react-native-babel-preset",
version: "^${metroVersion}",
devOnly: true,
},
metro: {
name: "metro",
version: "^${metroVersion}",
devOnly: true,
},
"metro-config": {
name: "metro-config",
version: "^${metroVersion}",
devOnly: true,
},
"metro-core": {
name: "metro-core",
version: "^${metroVersion}",
devOnly: true,
},
"metro-react-native-babel-transformer": {
name: "metro-react-native-babel-transformer",
version: "^${metroVersion}",
devOnly: true,
},
"metro-resolver": {
name: "metro-resolver",
version: "^${metroVersion}",
devOnly: true,
},
"metro-runtime": {
name: "metro-runtime",
version: "^${metroVersion}",
devOnly: true,
},
};
export default profile;
`;
}
/**
* Fetches package versions for specified react-native version.
* @param {string} preset
* @param {string} targetVersion
* @param {Profile} latestProfile
* @returns {Promise<string | undefined>}
*/
async function makeProfile(preset, targetVersion, latestProfile) {
const reactNativeInfo = await fetchPackageInfo(
latestProfile["core"],
`^${targetVersion}.0-0`
);
if (!reactNativeInfo) {
throw new Error(`Failed to get manifest of 'react-native@${targetVersion}`);
}
const { dependencies, peerDependencies } = reactNativeInfo;
if (!dependencies) {
throw new Error(
`Failed to get dependencies of 'react-native@${targetVersion}`
);
}
if (!peerDependencies) {
throw new Error(
`Failed to get peer dependencies of 'react-native@${targetVersion}`
);
}
// Fetch `metro` version from `@react-native-community/cli-plugin-metro` > `@react-native-community/cli`
const cliMetroPluginDependencies = await [
"@react-native-community/cli",
"@react-native-community/cli-plugin-metro",
].reduce(async (dependencies, packageName) => {
try {
const packageInfo = await pacote.manifest(packageName, {
defaultTag: getPackageVersion(packageName, await dependencies),
fullMetadata: true,
});
return packageInfo.dependencies;
} catch (e) {
if (e.code === "ETARGET") {
// Some packages, such as `@react-native-community/cli`, are still in
// alpha or beta while react-native RCs. Try again with the `next` tag.
const packageInfo = await pacote.manifest(packageName, {
defaultTag: "next",
fullMetadata: true,
});
return packageInfo.dependencies;
} else {
throw e;
}
}
}, Promise.resolve(dependencies));
return generateFromTemplate({
preset,
targetVersion,
reactVersion: getPackageVersion("react", peerDependencies),
metroVersion: getPackageVersion("metro", cliMetroPluginDependencies),
});
}
/**
* Displays a table of all capabilities that resolve to a package, its current
* version, and the latest available version.
*
* If `targetVersion` is specified, also generates a profile.
*
* Note that this script spawns a new process for each capability in parallel.
* It currently does not honor throttling hints of any kind.
*
* @param {{ preset?: string; targetVersion?: string; force?: boolean; }} options
*/
async function main({
preset: presetName = "microsoft",
targetVersion = "",
force,
}) {
const { preset } = await import(`../lib/presets/${presetName}/index.js`);
const allVersions = /** @type {import("../src/types").ProfileVersion[]} */ (
Object.keys(preset)
.sort((lhs, rhs) => semverCompare(semverCoerce(lhs), semverCoerce(rhs)))
.reverse()
);
const latestProfile = preset[allVersions[0]];
if (targetVersion) {
if (!force && preset[targetVersion]) {
console.error(
`Profile for '${targetVersion}' already exists. To overwrite it anyway, re-run with '--force'.`
);
process.exit(1);
}
try {
const newProfile = await makeProfile(
presetName,
targetVersion,
latestProfile
);
if (newProfile) {
const dst = getProfilePath(presetName, targetVersion);
fs.writeFile(dst, newProfile).then(() => {
console.log(`Wrote to '${dst}'`);
});
}
} catch (e) {
if (e.distTags) {
console.error(
[
e.message,
"Available tags:",
...Object.entries(e.distTags).map(
([tag, version]) => ` - ${tag}: ${version}`
),
].join("\n")
);
} else {
console.error(e);
}
process.exit(1);
}
}
const ignoredCapabilities = [
"babel-preset-react-native",
"core",
"core-android",
"core-ios",
"core-macos",
"core-windows",
"hermes",
"metro",
"metro-config",
"metro-core",
"metro-react-native-babel-transformer",
"metro-resolver",
"metro-runtime",
"react",
"react-dom",
"react-test-renderer",
];
/** @type {Record<string, PackageInfo>} */
const delta = {};
await Promise.all(
Object.entries(latestProfile)
.filter(([capability]) => {
return !ignoredCapabilities.includes(capability);
})
.map(async ([capability, pkg]) => {
await fetchPackageInfo(pkg).then((info) => {
if (info) {
delta[capability] = info;
}
});
})
);
const table = markdownTable([
["Capability", "Name", "Version", "Latest", "Homepage"],
...Object.keys(delta)
.sort()
.map((capability) => {
const { name, version, latest, homepage } = delta[capability];
return [
capability,
name,
version,
version.endsWith(latest) ? "=" : latest,
homepage,
];
}),
]);
console.log(table);
}
const options = (() => {
const options = {};
process.argv.slice(2).forEach((arg) => {
switch (arg) {
case "--force":
options.force = true;
break;
default:
if (!/^\d+\.\d+$/.test(arg)) {
console.error(
`Expected version in the format '<major>.<minor>', got: ${arg}`
);
process.exit(1);
}
options.targetVersion = arg;
break;
}
});
return options;
})();
main(options);

Просмотреть файл

@ -1,78 +0,0 @@
#!/usr/bin/env node
// @ts-check
const fs = require("fs");
const markdownTable = require("markdown-table");
const { preset } = require("../lib/presets/microsoft");
const README = "README.md";
const TOKEN_START = "<!-- @rnx-kit/dep-check/capabilities start -->";
const TOKEN_END = "<!-- @rnx-kit/dep-check/capabilities end -->";
/**
* Returns whether specified capability is a core capability.
* @param capability {string}
* @returns {boolean}
*/
function isCoreCapability(capability) {
return capability === "core" || capability.startsWith("core-");
}
/**
* Compare function that places core capabilities first.
* @param lhs {string}
* @param rhs {string}
* @returns {number}
*/
function sortCoreFirst(lhs, rhs) {
if (isCoreCapability(lhs)) {
if (!isCoreCapability(rhs)) {
return -1;
}
} else if (isCoreCapability(rhs)) {
return 1;
}
if (lhs === rhs) {
return 0;
}
return lhs < rhs ? -1 : 1;
}
const allVersions = /** @type {import("../src/types").ProfileVersion[]} */ (
Object.keys(preset).reverse()
);
const allCapabilities = /** @type {import("@rnx-kit/config").Capability[]} */ (
Object.keys(preset[allVersions[0]]).sort(sortCoreFirst)
);
const table = markdownTable([
["Capability", ...allVersions],
...allCapabilities.map((capability) => {
return [
capability,
...allVersions.map((profileVersion) => {
const pkg = preset[profileVersion][capability];
if ("version" in pkg) {
const { name, version } = pkg;
return `${name}@${version}`;
} else {
return `Meta package for installing ${pkg.capabilities
.map((name) => `\`${name}\``)
.join(", ")}`;
}
}),
];
}),
]);
const readme = fs.readFileSync(README, { encoding: "utf-8" });
const updatedReadme = readme.replace(
new RegExp(`${TOKEN_START}([^]+)${TOKEN_END}`),
`${TOKEN_START}\n\n${table}\n\n${TOKEN_END}`
);
if (updatedReadme !== readme) {
fs.writeFileSync(README, updatedReadme);
}

Просмотреть файл

@ -6,7 +6,7 @@ import { diffLinesUnified } from "jest-diff";
import path from "path";
import { getRequirements } from "./dependencies";
import { findBadPackages } from "./findBadPackages";
import { modifyManifest } from "./helpers";
import { modifyManifest, printMigrationMessage } from "./helpers";
import { updatePackageManifest } from "./manifest";
import { getProfilesFor, resolveCustomProfiles } from "./profiles";
import type { CheckConfig, CheckOptions, Command } from "./types";
@ -144,10 +144,14 @@ export function checkPackageManifest(
const url = chalk.bold("https://aka.ms/dep-check");
info(`Visit ${url} for more information about dep-check.`);
printMigrationMessage();
return 1;
}
}
printMigrationMessage();
return 0;
}

Просмотреть файл

@ -1,3 +1,4 @@
import { warn } from "@rnx-kit/console";
import type { PackageManifest } from "@rnx-kit/tools-node/package";
import { writePackage } from "@rnx-kit/tools-node/package";
import detectIndent from "detect-indent";
@ -40,3 +41,18 @@ export function omitEmptySections(manifest: PackageManifest): PackageManifest {
}
return manifest;
}
export function printMigrationMessage(): void {
const banner =
"⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️";
warn(banner);
warn("'@rnx-kit/dep-check' has been renamed to '@rnx-kit/align-deps'!");
warn(
"You can replace '@rnx-kit/dep-check' with '@rnx-kit/align-deps' in your 'package.json' and your configurations will be automatically upgraded."
);
warn(
"For reasoning and more details, you can read the RFC: https://github.com/microsoft/rnx-kit/pull/1757"
);
warn(banner);
}

Просмотреть файл

@ -1,6 +1,6 @@
import { readPackage } from "@rnx-kit/tools-node/package";
import { capabilitiesFor } from "./capabilities";
import { modifyManifest } from "./helpers";
import { modifyManifest, printMigrationMessage } from "./helpers";
import type { CapabilitiesOptions } from "./types";
export function initializeConfig(
@ -28,4 +28,6 @@ export function initializeConfig(
},
};
modifyManifest(packageManifest, updatedManifest);
printMigrationMessage();
}

14
packages/dep-check/test/__fixtures__/awesome-repo-extended/node_modules/conan/package.json сгенерированный поставляемый
Просмотреть файл

@ -1,14 +0,0 @@
{
"name": "conan",
"version": "1.0.0",
"dependencies": {
"t-800": "1.0.0"
},
"rnx-kit": {
"reactNativeVersion": "^0.63 || ^0.64",
"capabilities": [
"core-android",
"core-ios"
]
}
}

16
packages/dep-check/test/__fixtures__/awesome-repo-extended/node_modules/dutch/package.json сгенерированный поставляемый
Просмотреть файл

@ -1,16 +0,0 @@
{
"name": "dutch",
"version": "1.0.0",
"peerDependencies": {
"react": "17.0.1",
"react-native": "0.64.0"
},
"rnx-kit": {
"reactNativeVersion": "^0.63 || ^0.64",
"capabilities": [
"core-android",
"core-ios",
"netinfo"
]
}
}

15
packages/dep-check/test/__fixtures__/awesome-repo-extended/node_modules/john/package.json сгенерированный поставляемый
Просмотреть файл

@ -1,15 +0,0 @@
{
"name": "john",
"version": "1.0.0",
"dependencies": {
"dutch": "1.0.0"
},
"rnx-kit": {
"reactNativeVersion": "^0.63 || ^0.64",
"capabilities": [
"core-android",
"core-ios",
"storage"
]
}
}

16
packages/dep-check/test/__fixtures__/awesome-repo-extended/node_modules/quaid/package.json сгенерированный поставляемый
Просмотреть файл

@ -1,16 +0,0 @@
{
"name": "quaid",
"version": "1.0.0",
"peerDependencies": {
"react": "17.0.1",
"react-native": "0.64.0"
},
"rnx-kit": {
"reactNativeVersion": "^0.63 || ^0.64",
"capabilities": [
"core-android",
"core-ios",
"webview"
]
}
}

Просмотреть файл

@ -1,7 +0,0 @@
{
"name": "react-native",
"version": "0.64.0",
"dependencies": {
"react": "17.0.1"
}
}

4
packages/dep-check/test/__fixtures__/awesome-repo-extended/node_modules/react/package.json сгенерированный поставляемый
Просмотреть файл

@ -1,4 +0,0 @@
{
"name": "react",
"version": "17.0.1"
}

18
packages/dep-check/test/__fixtures__/awesome-repo-extended/node_modules/t-800/package.json сгенерированный поставляемый
Просмотреть файл

@ -1,18 +0,0 @@
{
"name": "t-800",
"version": "1.0.0",
"dependencies": {
"john": "1.0.0"
},
"rnx-kit": {
"reactNativeVersion": "^0.63 || ^0.64",
"capabilities": [
"core-android",
"core-ios",
"animation",
"cyberdyne",
"skynet",
"test-app"
]
}
}

Просмотреть файл

@ -1,19 +0,0 @@
{
"name": "awesome-repo-extended",
"version": "1.0.0",
"dependencies": {
"conan": "1.0.0",
"quaid": "1.0.0",
"react": "17.0.1",
"react-native": "0.64.0"
},
"rnx-kit": {
"reactNativeVersion": "^0.63 || ^0.64",
"kitType": "app",
"capabilities": [
"core-android",
"hermes",
"lazy-index"
]
}
}

14
packages/dep-check/test/__fixtures__/awesome-repo/node_modules/conan/package.json сгенерированный поставляемый
Просмотреть файл

@ -1,14 +0,0 @@
{
"name": "conan",
"version": "1.0.0",
"dependencies": {
"t-800": "1.0.0"
},
"rnx-kit": {
"reactNativeVersion": "^0.63 || ^0.64",
"capabilities": [
"core-android",
"core-ios"
]
}
}

16
packages/dep-check/test/__fixtures__/awesome-repo/node_modules/dutch/package.json сгенерированный поставляемый
Просмотреть файл

@ -1,16 +0,0 @@
{
"name": "dutch",
"version": "1.0.0",
"peerDependencies": {
"react": "17.0.1",
"react-native": "0.64.0"
},
"rnx-kit": {
"reactNativeVersion": "^0.63 || ^0.64",
"capabilities": [
"core-android",
"core-ios",
"netinfo"
]
}
}

15
packages/dep-check/test/__fixtures__/awesome-repo/node_modules/john/package.json сгенерированный поставляемый
Просмотреть файл

@ -1,15 +0,0 @@
{
"name": "john",
"version": "1.0.0",
"dependencies": {
"dutch": "1.0.0"
},
"rnx-kit": {
"reactNativeVersion": "^0.63 || ^0.64",
"capabilities": [
"core-android",
"core-ios",
"storage"
]
}
}

16
packages/dep-check/test/__fixtures__/awesome-repo/node_modules/quaid/package.json сгенерированный поставляемый
Просмотреть файл

@ -1,16 +0,0 @@
{
"name": "quaid",
"version": "1.0.0",
"peerDependencies": {
"react": "17.0.1",
"react-native": "0.64.0"
},
"rnx-kit": {
"reactNativeVersion": "^0.63 || ^0.64",
"capabilities": [
"core-android",
"core-ios",
"webview"
]
}
}

7
packages/dep-check/test/__fixtures__/awesome-repo/node_modules/react-native/package.json сгенерированный поставляемый
Просмотреть файл

@ -1,7 +0,0 @@
{
"name": "react-native",
"version": "0.64.0",
"dependencies": {
"react": "17.0.1"
}
}

4
packages/dep-check/test/__fixtures__/awesome-repo/node_modules/react/package.json сгенерированный поставляемый
Просмотреть файл

@ -1,4 +0,0 @@
{
"name": "react",
"version": "17.0.1"
}

16
packages/dep-check/test/__fixtures__/awesome-repo/node_modules/t-800/package.json сгенерированный поставляемый
Просмотреть файл

@ -1,16 +0,0 @@
{
"name": "t-800",
"version": "1.0.0",
"dependencies": {
"john": "1.0.0"
},
"rnx-kit": {
"reactNativeVersion": "^0.63 || ^0.64",
"capabilities": [
"core-android",
"core-ios",
"animation",
"test-app"
]
}
}

Просмотреть файл

@ -1,19 +0,0 @@
{
"name": "awesome-repo",
"version": "1.0.0",
"dependencies": {
"conan": "1.0.0",
"quaid": "1.0.0",
"react": "17.0.1",
"react-native": "0.64.0"
},
"rnx-kit": {
"reactNativeVersion": "^0.63 || ^0.64",
"kitType": "app",
"capabilities": [
"core-android",
"hermes",
"lazy-index"
]
}
}

Просмотреть файл

@ -1,22 +0,0 @@
module.exports = {
0.64: {
core: {
name: "react-native",
version: "0.64.3",
},
react: {
name: "react",
version: "17.0.2",
},
},
0.65: {
core: {
name: "react-native",
version: "0.65.2",
},
react: {
name: "react",
version: "17.0.2",
},
},
};

Просмотреть файл

Просмотреть файл

Просмотреть файл

@ -1,5 +0,0 @@
{
"name": "custom-profiles-package",
"version": "1.0.0",
"main": "index.js"
}

Просмотреть файл

@ -1,26 +0,0 @@
module.exports = {
format: {
name: "prettier",
version: "^2.5.1",
devOnly: true,
},
0.66: {
test: {
name: "jest",
version: "26.0",
devOnly: true,
},
},
0.67: {
format: {
name: "prettier",
version: "3.0",
devOnly: true,
},
test: {
name: "jest",
version: "27.0",
devOnly: true,
},
},
};

Просмотреть файл

@ -1,23 +0,0 @@
module.exports = {
0.65: {
format: {
name: "prettier",
version: "^2.5.1",
devOnly: true,
},
},
0.66: {
format: {
name: "prettier",
version: "^2.5.1",
devOnly: true,
},
},
0.67: {
format: {
name: "prettier",
version: "^2.5.1",
devOnly: true,
},
},
};

Просмотреть файл

@ -1,17 +0,0 @@
{
"name": "conan",
"version": "1.0.0",
"peerDependencies": {
"react-native": "^0.63.2 || ^0.64.2"
},
"devDependencies": {
"react-native": "^0.63.2"
},
"rnx-kit": {
"reactNativeVersion": "^0.63 || ^0.64",
"capabilities": [
"core-android",
"core-ios"
]
}
}

Просмотреть файл

@ -1,7 +0,0 @@
{
"name": "react-native",
"version": "0.64.2",
"dependencies": {
"react": "17.0.1"
}
}

Просмотреть файл

@ -1,4 +0,0 @@
{
"name": "react",
"version": "17.0.1"
}

Просмотреть файл

@ -1,17 +0,0 @@
{
"name": "t-800",
"version": "1.0.0",
"peerDependencies": {
"react-native": "^0.63.2"
},
"devDependencies": {
"react-native": "^0.63.2"
},
"rnx-kit": {
"reactNativeVersion": "^0.63",
"capabilities": [
"core-android",
"core-ios"
]
}
}

Просмотреть файл

@ -1,18 +0,0 @@
{
"name": "no-profile-satisfying-deps",
"version": "1.0.0",
"dependencies": {
"conan": "1.0.0",
"react": "17.0.1",
"react-native": "^0.64.2",
"t-800": "1.0.0"
},
"rnx-kit": {
"reactNativeVersion": "^0.64",
"kitType": "app",
"capabilities": [
"core-android",
"core-ios"
]
}
}

Просмотреть файл

@ -1,13 +0,0 @@
const rnxKitConfig = jest.createMockFromModule("@rnx-kit/config");
const actualKitConfig = jest.requireActual("@rnx-kit/config");
let kitConfig = "";
rnxKitConfig.__setMockConfig = (config) => {
kitConfig = config;
};
rnxKitConfig.getKitCapabilities = actualKitConfig.getKitCapabilities;
rnxKitConfig.getKitConfig = () => kitConfig;
module.exports = rnxKitConfig;

Просмотреть файл

@ -1,16 +0,0 @@
const chalk = jest.createMockFromModule("chalk");
function passthrough(s) {
return s;
}
chalk.bold = passthrough;
chalk.cyan = passthrough;
chalk.cyan.bold = passthrough;
chalk.dim = passthrough;
chalk.green = passthrough;
chalk.red = passthrough;
chalk.red.bold = passthrough;
chalk.yellow = passthrough;
module.exports = chalk;

Просмотреть файл

@ -1,19 +0,0 @@
const fs = jest.createMockFromModule("fs");
const actualFs = jest.requireActual("fs");
let data = "";
fs.__setMockContent = (content, space = 2) => {
data = JSON.stringify(content, undefined, space) + "\n";
};
fs.__setMockFileWriter = (writer) => {
fs.writeFileSync = writer;
};
fs.lstatSync = (...args) => actualFs.lstatSync(...args);
fs.readFileSync = (...args) => data || actualFs.readFileSync(...args);
fs.statSync = actualFs.statSync; // used by cosmiconfig
fs.writeFileSync = undefined;
module.exports = fs;

Просмотреть файл

@ -1,31 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`checkPackageManifest({ kitType: 'app' }) adds required dependencies 1`] = `
"{
\\"name\\": \\"awesome-repo\\",
\\"version\\": \\"1.0.0\\",
\\"dependencies\\": {
\\"@react-native-async-storage/async-storage\\": \\"^1.15.8\\",
\\"@react-native-community/async-storage\\": \\"^1.12.1\\",
\\"@react-native-community/netinfo\\": \\"^6.0.2\\",
\\"conan\\": \\"1.0.0\\",
\\"hermes-engine\\": \\"~0.7.0\\",
\\"quaid\\": \\"1.0.0\\",
\\"react\\": \\"17.0.1\\",
\\"react-native\\": \\"^0.64.2\\",
\\"react-native-lazy-index\\": \\"^2.1.1\\",
\\"react-native-reanimated\\": \\"^2.1.0\\",
\\"react-native-webview\\": \\"^11.4.2\\"
},
\\"rnx-kit\\": {
\\"reactNativeVersion\\": \\"^0.63 || ^0.64\\",
\\"kitType\\": \\"app\\",
\\"capabilities\\": [
\\"core-android\\",
\\"hermes\\",
\\"lazy-index\\"
]
}
}
"
`;

Просмотреть файл

@ -1,37 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`checkPackageManifest({ kitType: 'library' }) preserves indentation in 'package.json' 1`] = `
"{
\\"name\\": \\"@rnx-kit/dep-check\\",
\\"version\\": \\"0.0.1\\",
\\"peerDependencies\\": {
\\"react\\": \\"17.0.1\\",
\\"react-native\\": \\"^0.64.2\\"
},
\\"devDependencies\\": {
\\"react\\": \\"17.0.1\\",
\\"react-native\\": \\"^0.64.2\\"
}
}
"
`;
exports[`checkPackageManifest({ kitType: 'library' }) prints warnings when detecting bad packages (with version range) 1`] = `
Array [
Array [
"warn",
"Known bad packages are found in '@rnx-kit/dep-check':
react-native-linear-gradient@<2.6.0: This package causes significant degradation in app start up time prior to 2.6.0.",
],
]
`;
exports[`checkPackageManifest({ kitType: 'library' }) prints warnings when detecting bad packages 1`] = `
Array [
Array [
"warn",
"Known bad packages are found in '@rnx-kit/dep-check':
react-native-linear-gradient@<2.6.0: This package causes significant degradation in app start up time prior to 2.6.0.",
],
]
`;

Просмотреть файл

@ -1,112 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`loadCustomProfiles() loads valid custom profiles 1`] = `
Object {
"0.65": Object {
"format": Object {
"devOnly": true,
"name": "prettier",
"version": "^2.5.1",
},
},
"0.66": Object {
"format": Object {
"devOnly": true,
"name": "prettier",
"version": "^2.5.1",
},
},
"0.67": Object {
"format": Object {
"devOnly": true,
"name": "prettier",
"version": "^2.5.1",
},
},
}
`;
exports[`loadCustomProfiles() prepends root-level capabilities to all profiles 1`] = `
Object {
"0.61": Object {
"format": Object {
"devOnly": true,
"name": "prettier",
"version": "^2.5.1",
},
},
"0.62": Object {
"format": Object {
"devOnly": true,
"name": "prettier",
"version": "^2.5.1",
},
},
"0.63": Object {
"format": Object {
"devOnly": true,
"name": "prettier",
"version": "^2.5.1",
},
},
"0.64": Object {
"format": Object {
"devOnly": true,
"name": "prettier",
"version": "^2.5.1",
},
},
"0.65": Object {
"format": Object {
"devOnly": true,
"name": "prettier",
"version": "^2.5.1",
},
},
"0.66": Object {
"format": Object {
"devOnly": true,
"name": "prettier",
"version": "^2.5.1",
},
"test": Object {
"devOnly": true,
"name": "jest",
"version": "26.0",
},
},
"0.67": Object {
"format": Object {
"devOnly": true,
"name": "prettier",
"version": "3.0",
},
"test": Object {
"devOnly": true,
"name": "jest",
"version": "27.0",
},
},
"0.68": Object {
"format": Object {
"devOnly": true,
"name": "prettier",
"version": "^2.5.1",
},
},
"0.69": Object {
"format": Object {
"devOnly": true,
"name": "prettier",
"version": "^2.5.1",
},
},
"0.70": Object {
"format": Object {
"devOnly": true,
"name": "prettier",
"version": "^2.5.1",
},
},
}
`;

Просмотреть файл

@ -1,249 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`buildManifestProfile() builds a package manifest for a single profile version 1`] = `
Object {
"dependencies": Object {
"@react-native-async-storage/async-storage": "^1.15.8",
"@react-native-clipboard/clipboard": "^1.8.3",
"@react-native-community/checkbox": "^0.5.8",
"@react-native-community/datetimepicker": "^3.4.6",
"@react-native-community/hooks": "^2.6.0",
"@react-native-community/netinfo": "^6.0.2",
"@react-native-masked-view/masked-view": "^0.2.4",
"@react-navigation/native": "^5.9.8",
"@react-navigation/stack": "^5.14.9",
"hermes-engine": "~0.7.0",
"jest": "^26.5.2",
"metro": "^0.64.0",
"metro-config": "^0.64.0",
"metro-core": "^0.64.0",
"metro-react-native-babel-preset": "^0.64.0",
"metro-react-native-babel-transformer": "^0.64.0",
"metro-resolver": "^0.64.0",
"metro-runtime": "^0.64.0",
"react": "17.0.1",
"react-dom": "17.0.1",
"react-native": "^0.64.2",
"react-native-base64": "^0.2.1",
"react-native-floating-action": "^1.21.0",
"react-native-fs": "^2.17.0",
"react-native-gesture-handler": "^1.10.3",
"react-native-lazy-index": "^2.1.1",
"react-native-macos": "^0.64.0",
"react-native-modal": "^11.10.0",
"react-native-popover-view": "^4.0.3",
"react-native-reanimated": "^2.1.0",
"react-native-render-html": "^5.1.1",
"react-native-safe-area-context": "^3.2.0",
"react-native-screens": "^3.1.1",
"react-native-shimmer": "^0.5.0",
"react-native-sqlite-storage": "^5.0.0",
"react-native-svg": "^12.1.1",
"react-native-test-app": "^0.11.4",
"react-native-webview": "^11.4.2",
"react-native-windows": "^0.64.0",
"react-test-renderer": "17.0.1",
},
"devDependencies": Object {
"@react-native-async-storage/async-storage": "^1.15.8",
"@react-native-clipboard/clipboard": "^1.8.3",
"@react-native-community/checkbox": "^0.5.8",
"@react-native-community/datetimepicker": "^3.4.6",
"@react-native-community/hooks": "^2.6.0",
"@react-native-community/netinfo": "^6.0.2",
"@react-native-masked-view/masked-view": "^0.2.4",
"@react-navigation/native": "^5.9.8",
"@react-navigation/stack": "^5.14.9",
"hermes-engine": "~0.7.0",
"jest": "^26.5.2",
"metro": "^0.64.0",
"metro-config": "^0.64.0",
"metro-core": "^0.64.0",
"metro-react-native-babel-preset": "^0.64.0",
"metro-react-native-babel-transformer": "^0.64.0",
"metro-resolver": "^0.64.0",
"metro-runtime": "^0.64.0",
"react": "17.0.1",
"react-dom": "17.0.1",
"react-native": "^0.64.2",
"react-native-base64": "^0.2.1",
"react-native-floating-action": "^1.21.0",
"react-native-fs": "^2.17.0",
"react-native-gesture-handler": "^1.10.3",
"react-native-lazy-index": "^2.1.1",
"react-native-macos": "^0.64.0",
"react-native-modal": "^11.10.0",
"react-native-popover-view": "^4.0.3",
"react-native-reanimated": "^2.1.0",
"react-native-render-html": "^5.1.1",
"react-native-safe-area-context": "^3.2.0",
"react-native-screens": "^3.1.1",
"react-native-shimmer": "^0.5.0",
"react-native-sqlite-storage": "^5.0.0",
"react-native-svg": "^12.1.1",
"react-native-test-app": "^0.11.4",
"react-native-webview": "^11.4.2",
"react-native-windows": "^0.64.0",
"react-test-renderer": "17.0.1",
},
"name": "@rnx-kit/dep-check",
"peerDependencies": Object {
"@react-native-async-storage/async-storage": "^1.15.8",
"@react-native-clipboard/clipboard": "^1.8.3",
"@react-native-community/checkbox": "^0.5.8",
"@react-native-community/datetimepicker": "^3.4.6",
"@react-native-community/hooks": "^2.6.0",
"@react-native-community/netinfo": "^6.0.2",
"@react-native-masked-view/masked-view": "^0.2.4",
"@react-navigation/native": "^5.9.8",
"@react-navigation/stack": "^5.14.9",
"hermes-engine": "~0.7.0",
"react": "17.0.1",
"react-dom": "17.0.1",
"react-native": "^0.64.2",
"react-native-base64": "^0.2.1",
"react-native-floating-action": "^1.21.0",
"react-native-fs": "^2.17.0",
"react-native-gesture-handler": "^1.10.3",
"react-native-lazy-index": "^2.1.1",
"react-native-macos": "^0.64.0",
"react-native-modal": "^11.10.0",
"react-native-popover-view": "^4.0.3",
"react-native-reanimated": "^2.1.0",
"react-native-render-html": "^5.1.1",
"react-native-safe-area-context": "^3.2.0",
"react-native-screens": "^3.1.1",
"react-native-shimmer": "^0.5.0",
"react-native-sqlite-storage": "^5.0.0",
"react-native-svg": "^12.1.1",
"react-native-webview": "^11.4.2",
"react-native-windows": "^0.64.0",
},
"version": "1.0.0-test",
}
`;
exports[`buildManifestProfile() builds a package manifest for multiple profile versions 1`] = `
Object {
"dependencies": Object {
"@react-native-async-storage/async-storage": "^1.15.8",
"@react-native-clipboard/clipboard": "^1.8.3",
"@react-native-community/checkbox": "^0.5.8",
"@react-native-community/datetimepicker": "^3.4.6",
"@react-native-community/hooks": "^2.6.0",
"@react-native-community/netinfo": "^6.0.2",
"@react-native-masked-view/masked-view": "^0.2.4",
"@react-navigation/native": "^5.9.8",
"@react-navigation/stack": "^5.14.9",
"hermes-engine": "~0.7.0",
"jest": "^26.5.2",
"metro": "^0.64.0",
"metro-config": "^0.64.0",
"metro-core": "^0.64.0",
"metro-react-native-babel-preset": "^0.64.0",
"metro-react-native-babel-transformer": "^0.64.0",
"metro-resolver": "^0.64.0",
"metro-runtime": "^0.64.0",
"react": "17.0.1",
"react-dom": "17.0.1",
"react-native": "^0.64.2",
"react-native-base64": "^0.2.1",
"react-native-floating-action": "^1.21.0",
"react-native-fs": "^2.17.0",
"react-native-gesture-handler": "^1.10.3",
"react-native-lazy-index": "^2.1.1",
"react-native-macos": "^0.64.0",
"react-native-modal": "^11.10.0",
"react-native-popover-view": "^4.0.3",
"react-native-reanimated": "^2.1.0",
"react-native-render-html": "^5.1.1",
"react-native-safe-area-context": "^3.2.0",
"react-native-screens": "^3.1.1",
"react-native-shimmer": "^0.5.0",
"react-native-sqlite-storage": "^5.0.0",
"react-native-svg": "^12.1.1",
"react-native-test-app": "^0.11.4",
"react-native-webview": "^11.4.2",
"react-native-windows": "^0.64.0",
"react-test-renderer": "17.0.1",
},
"devDependencies": Object {
"@react-native-async-storage/async-storage": "^1.15.8",
"@react-native-clipboard/clipboard": "^1.8.3",
"@react-native-community/checkbox": "^0.5.8",
"@react-native-community/datetimepicker": "^3.4.6",
"@react-native-community/hooks": "^2.6.0",
"@react-native-community/netinfo": "^6.0.2",
"@react-native-masked-view/masked-view": "^0.2.4",
"@react-navigation/native": "^5.9.8",
"@react-navigation/stack": "^5.14.9",
"hermes-engine": "~0.7.0",
"jest": "^26.5.2",
"metro": "^0.64.0",
"metro-config": "^0.64.0",
"metro-core": "^0.64.0",
"metro-react-native-babel-preset": "^0.64.0",
"metro-react-native-babel-transformer": "^0.64.0",
"metro-resolver": "^0.64.0",
"metro-runtime": "^0.64.0",
"react": "17.0.1",
"react-dom": "17.0.1",
"react-native": "^0.64.2",
"react-native-base64": "^0.2.1",
"react-native-floating-action": "^1.21.0",
"react-native-fs": "^2.17.0",
"react-native-gesture-handler": "^1.10.3",
"react-native-lazy-index": "^2.1.1",
"react-native-macos": "^0.64.0",
"react-native-modal": "^11.10.0",
"react-native-popover-view": "^4.0.3",
"react-native-reanimated": "^2.1.0",
"react-native-render-html": "^5.1.1",
"react-native-safe-area-context": "^3.2.0",
"react-native-screens": "^3.1.1",
"react-native-shimmer": "^0.5.0",
"react-native-sqlite-storage": "^5.0.0",
"react-native-svg": "^12.1.1",
"react-native-test-app": "^0.11.4",
"react-native-webview": "^11.4.2",
"react-native-windows": "^0.64.0",
"react-test-renderer": "17.0.1",
},
"name": "@rnx-kit/dep-check",
"peerDependencies": Object {
"@react-native-async-storage/async-storage": "^1.15.8",
"@react-native-clipboard/clipboard": "^1.8.3",
"@react-native-community/async-storage": "^1.12.1",
"@react-native-community/checkbox": "^0.5.7 || ^0.5.8",
"@react-native-community/clipboard": "^1.5.1",
"@react-native-community/datetimepicker": "^3.0.9 || ^3.4.6",
"@react-native-community/hooks": "^2.6.0",
"@react-native-community/netinfo": "^5.9.10 || ^6.0.2",
"@react-native-masked-view/masked-view": "^0.2.4",
"@react-navigation/native": "^5.9.4 || ^5.9.8",
"@react-navigation/stack": "^5.14.4 || ^5.14.9",
"hermes-engine": "~0.5.0 || ~0.7.0",
"react": "16.13.1 || 17.0.1",
"react-dom": "16.13.1 || 17.0.1",
"react-native": "^0.63.2 || ^0.64.2",
"react-native-base64": "^0.2.1",
"react-native-floating-action": "^1.21.0",
"react-native-fs": "^2.16.6 || ^2.17.0",
"react-native-gesture-handler": "^1.10.3",
"react-native-lazy-index": "^2.1.1",
"react-native-macos": "^0.63.0 || ^0.64.0",
"react-native-modal": "^11.5.6 || ^11.10.0",
"react-native-popover-view": "^3.1.1 || ^4.0.3",
"react-native-reanimated": "^1.13.3 || ^2.1.0",
"react-native-render-html": "^5.1.0 || ^5.1.1",
"react-native-safe-area-context": "^3.2.0",
"react-native-screens": "^2.18.1 || ^3.1.1",
"react-native-shimmer": "^0.5.0",
"react-native-sqlite-storage": "^3.3.11 || ^5.0.0",
"react-native-svg": "^12.1.1",
"react-native-webview": "^11.4.2",
"react-native-windows": "^0.63.0 || ^0.64.0",
},
"version": "1.0.0-test",
}
`;

Просмотреть файл

@ -1,276 +0,0 @@
import type { Capability } from "@rnx-kit/config";
import { capabilitiesFor, resolveCapabilities } from "../src/capabilities";
import profile_0_62 from "../src/presets/microsoft/profile-0.62";
import profile_0_63 from "../src/presets/microsoft/profile-0.63";
import profile_0_64 from "../src/presets/microsoft/profile-0.64";
import { getProfilesFor } from "../src/profiles";
import { pickPackage } from "./helpers";
describe("capabilitiesFor()", () => {
test("returns `undefined` when react-native is not a dependency", () => {
expect(
capabilitiesFor({ name: "@rnx-kit/dep-check", version: "1.0.0" })
).toBeUndefined();
expect(
capabilitiesFor({
name: "@rnx-kit/dep-check",
version: "1.0.0",
dependencies: {
react: "^17.0.1",
},
})
).toBeUndefined();
});
test("returns capabilities when react-native is under dependencies", () => {
const manifest = {
name: "@rnx-kit/dep-check",
version: "1.0.0",
dependencies: {
"react-native": "^0.64.1",
},
};
expect(capabilitiesFor(manifest)).toEqual({
reactNativeVersion: "^0.64",
reactNativeDevVersion: "0.64.0",
kitType: "library",
capabilities: ["core", "core-android", "core-ios"],
});
});
test("returns capabilities when react-native is under peerDependencies", () => {
const manifest = {
name: "@rnx-kit/dep-check",
version: "1.0.0",
peerDependencies: {
"react-native": "^0.64.1",
},
};
expect(capabilitiesFor(manifest)).toEqual({
reactNativeVersion: "^0.64",
reactNativeDevVersion: "0.64.0",
kitType: "library",
capabilities: ["core", "core-android", "core-ios"],
});
});
test("returns capabilities when react-native is under devDependencies", () => {
const manifest = {
name: "@rnx-kit/dep-check",
version: "1.0.0",
devDependencies: {
"react-native": "^0.64.1",
},
};
expect(capabilitiesFor(manifest)).toEqual({
reactNativeVersion: "^0.64",
reactNativeDevVersion: "^0.64.1",
kitType: "library",
capabilities: ["core", "core-android", "core-ios"],
});
});
test("returns kit config with app type instead of dev version", () => {
const manifest = {
name: "@rnx-kit/dep-check",
version: "1.0.0",
peerDependencies: {
"react-native": "^0.64.1",
},
};
expect(capabilitiesFor(manifest, { kitType: "app" })).toEqual({
reactNativeVersion: "^0.64",
kitType: "app",
capabilities: ["core", "core-android", "core-ios"],
});
});
test("ignores packages that are not managed by dep-check", () => {
const manifest = {
name: "@rnx-kit/dep-check",
version: "1.0.0",
peerDependencies: {
react: "17.0.1",
"react-native": "^0.64.1",
},
devDependencies: {
"@rnx-kit/babel-preset-metro-react-native": "*",
"@rnx-kit/cli": "*",
},
};
expect(capabilitiesFor(manifest, { kitType: "app" })).toEqual({
reactNativeVersion: "^0.64",
kitType: "app",
capabilities: ["core", "core-android", "core-ios", "react"],
});
});
});
describe("resolveCapabilities()", () => {
const consoleWarnSpy = jest.spyOn(global.console, "warn");
beforeEach(() => {
consoleWarnSpy.mockReset();
});
afterAll(() => {
jest.clearAllMocks();
});
test("dedupes packages", () => {
const packages = resolveCapabilities(
["core", "core", "test-app"],
[profile_0_64]
);
const { name } = profile_0_64["core"];
const { name: reactName } = profile_0_64["react"];
const { name: testAppName } = profile_0_64["test-app"];
expect(packages).toEqual({
[name]: [profile_0_64["core"]],
[reactName]: [profile_0_64["react"]],
[testAppName]: [profile_0_64["test-app"]],
});
expect(consoleWarnSpy).not.toBeCalled();
});
test("dedupes package versions", () => {
const packages = resolveCapabilities(
["webview"],
[profile_0_62, profile_0_63, profile_0_64]
);
const { name } = profile_0_64["webview"];
expect(packages).toEqual({
[name]: [profile_0_62["webview"], profile_0_64["webview"]],
});
expect(consoleWarnSpy).not.toBeCalled();
});
test("ignores missing/unknown capabilities", () => {
const packages = resolveCapabilities(
["skynet" as Capability, "svg"],
[profile_0_62, profile_0_63, profile_0_64]
);
const { name } = profile_0_64["svg"];
expect(packages).toEqual({ [name]: [profile_0_64["svg"]] });
expect(consoleWarnSpy).toBeCalledTimes(1);
});
test("resolves custom capabilities", () => {
const skynet = { name: "skynet", version: "1.0.0" };
jest.mock(
"mock-custom-profiles-module",
() => ({ "0.62": { [skynet.name]: skynet } }),
{ virtual: true }
);
const profiles = getProfilesFor(
"^0.62 || ^0.63 || ^0.64",
"mock-custom-profiles-module"
);
const packages = resolveCapabilities(
["skynet" as Capability, "svg"],
profiles
);
const { name } = profile_0_64["svg"];
expect(packages).toEqual({
[name]: [profile_0_64["svg"]],
[skynet.name]: [skynet],
});
});
test("resolves capabilities required by capabilities", () => {
const packages = resolveCapabilities(
["core-windows"],
[profile_0_63, profile_0_64]
);
expect(packages).toEqual({
react: [
pickPackage(profile_0_63, "react"),
pickPackage(profile_0_64, "react"),
],
"react-native": [
pickPackage(profile_0_63, "core"),
pickPackage(profile_0_64, "core"),
],
"react-native-windows": [
pickPackage(profile_0_63, "core-windows"),
pickPackage(profile_0_64, "core-windows"),
],
});
expect(consoleWarnSpy).not.toBeCalled();
});
test("resolves meta packages", () => {
jest.mock(
"mock-meta-package",
() => ({
"0.64": {
"core/all": {
name: "#meta",
capabilities: [
"core-android",
"core-ios",
"core-macos",
"core-windows",
],
},
},
}),
{ virtual: true }
);
const packages = resolveCapabilities(
["core/all" as Capability],
getProfilesFor("^0.64", "mock-meta-package")
);
expect(packages).toEqual({
react: [pickPackage(profile_0_64, "react")],
"react-native": [pickPackage(profile_0_64, "core")],
"react-native-macos": [pickPackage(profile_0_64, "core-macos")],
"react-native-windows": [pickPackage(profile_0_64, "core-windows")],
});
});
test("resolves meta packages with loops", () => {
jest.mock(
"mock-meta-package-loop",
() => ({
"0.64": {
connor: {
name: "#meta",
capabilities: ["core", "reese"],
},
reese: {
name: "#meta",
capabilities: ["t-800"],
},
"t-800": {
name: "#meta",
capabilities: ["connor"],
},
},
}),
{ virtual: true }
);
const packages = resolveCapabilities(
["reese" as Capability],
getProfilesFor("^0.64", "mock-meta-package-loop")
);
expect(packages).toEqual({
react: [pickPackage(profile_0_64, "react")],
"react-native": [pickPackage(profile_0_64, "core")],
});
});
});

Просмотреть файл

@ -1,40 +0,0 @@
import path from "path";
import { checkPackageManifest } from "../src/check";
jest.mock("fs");
jest.unmock("@rnx-kit/config");
function fixturePath(name: string) {
return path.join(process.cwd(), "test", "__fixtures__", name);
}
describe("checkPackageManifest({ kitType: 'app' })", () => {
const fs = require("fs");
const consoleWarnSpy = jest.spyOn(global.console, "warn");
beforeEach(() => {
consoleWarnSpy.mockReset();
});
afterAll(() => {
jest.clearAllMocks();
});
test("adds required dependencies", () => {
const manifestPath = path.join(fixturePath("awesome-repo"), "package.json");
let destination = "";
let updatedManifest = "";
fs.__setMockFileWriter((dest, content) => {
destination = dest;
updatedManifest = content;
});
expect(
checkPackageManifest(manifestPath, { loose: false, write: true })
).toBe(0);
expect(consoleWarnSpy).not.toBeCalled();
expect(destination).toBe(manifestPath);
expect(updatedManifest).toMatchSnapshot();
});
});

Просмотреть файл

@ -1,359 +0,0 @@
import semverCoerce from "semver/functions/coerce";
import { checkPackageManifest, getCheckConfig } from "../src/check";
import profile_0_62 from "../src/presets/microsoft/profile-0.62";
import profile_0_63 from "../src/presets/microsoft/profile-0.63";
import profile_0_64 from "../src/presets/microsoft/profile-0.64";
import { packageVersion } from "./helpers";
jest.mock("fs");
describe("checkPackageManifest({ kitType: 'library' })", () => {
const rnxKitConfig = require("@rnx-kit/config");
const fs = require("fs");
const consoleErrorSpy = jest.spyOn(global.console, "error");
const consoleLogSpy = jest.spyOn(global.console, "log");
const consoleWarnSpy = jest.spyOn(global.console, "warn");
const defaultOptions = { loose: false, write: false };
const mockManifest = {
name: "@rnx-kit/dep-check",
version: "0.0.1",
};
const react_v62_v63_v64 = [
packageVersion(profile_0_62, "react"),
packageVersion(profile_0_63, "react"),
packageVersion(profile_0_64, "react"),
].join(" || ");
const v62_v63_v64 = [
packageVersion(profile_0_62, "core"),
packageVersion(profile_0_63, "core"),
packageVersion(profile_0_64, "core"),
].join(" || ");
beforeEach(() => {
consoleErrorSpy.mockReset();
consoleLogSpy.mockReset();
consoleWarnSpy.mockReset();
fs.__setMockContent({});
rnxKitConfig.__setMockConfig();
});
afterAll(() => {
jest.clearAllMocks();
});
test("returns error code when reading invalid manifests", () => {
expect(checkPackageManifest("package.json", defaultOptions)).not.toBe(0);
expect(consoleErrorSpy).toBeCalledTimes(1);
});
test("returns early if 'rnx-kit' is missing from the manifest", () => {
fs.__setMockContent({
...mockManifest,
dependencies: { "react-native-linear-gradient": "0.0.0" },
});
const options = { ...defaultOptions, uncheckedReturnCode: -1 };
expect(checkPackageManifest("package.json", options)).toBe(-1);
expect(consoleWarnSpy).toBeCalled();
});
test("prints warnings when detecting bad packages", () => {
fs.__setMockContent({
...mockManifest,
dependencies: { "react-native-linear-gradient": "0.0.0" },
peerDependencies: {
"react-native": profile_0_64["core"],
},
devDependencies: {
"react-native": profile_0_64["core"],
},
});
rnxKitConfig.__setMockConfig({ reactNativeVersion: "0.64.0" });
expect(checkPackageManifest("package.json", defaultOptions)).toBe(0);
expect(consoleErrorSpy).not.toBeCalled();
expect(consoleWarnSpy.mock.calls).toMatchSnapshot();
});
test("prints warnings when detecting bad packages (with version range)", () => {
fs.__setMockContent({
...mockManifest,
dependencies: { "react-native-linear-gradient": "0.0.0" },
});
rnxKitConfig.__setMockConfig({ reactNativeVersion: "^0.63.0 || ^0.64.0" });
expect(checkPackageManifest("package.json", defaultOptions)).toBe(0);
expect(consoleErrorSpy).not.toBeCalled();
expect(consoleWarnSpy.mock.calls).toMatchSnapshot();
});
test("returns early if no capabilities are defined", () => {
fs.__setMockContent(mockManifest);
rnxKitConfig.__setMockConfig({ reactNativeVersion: "0.64.0" });
expect(checkPackageManifest("package.json", defaultOptions)).toBe(0);
expect(consoleErrorSpy).not.toBeCalled();
expect(consoleWarnSpy).not.toBeCalled();
});
test("returns if no changes are needed", () => {
fs.__setMockContent({
...mockManifest,
peerDependencies: {
react: packageVersion(profile_0_64, "react"),
"react-native": packageVersion(profile_0_64, "core"),
},
devDependencies: {
react: packageVersion(profile_0_64, "react"),
"react-native": packageVersion(profile_0_64, "core"),
},
});
rnxKitConfig.__setMockConfig({
reactNativeVersion: "0.64.0",
capabilities: ["core-ios"],
});
expect(checkPackageManifest("package.json", defaultOptions)).toBe(0);
expect(consoleErrorSpy).not.toBeCalled();
expect(consoleLogSpy).not.toBeCalled();
expect(consoleWarnSpy).not.toBeCalled();
});
test("returns if no changes are needed (write: true)", () => {
let didWriteToPath = false;
fs.__setMockContent({
...mockManifest,
peerDependencies: {
react: packageVersion(profile_0_64, "react"),
"react-native": packageVersion(profile_0_64, "core"),
},
devDependencies: {
react: packageVersion(profile_0_64, "react"),
"react-native": packageVersion(profile_0_64, "core"),
},
});
fs.__setMockFileWriter((p, _content) => {
didWriteToPath = p;
});
rnxKitConfig.__setMockConfig({
reactNativeVersion: "0.64.0",
capabilities: ["core-ios"],
});
expect(
checkPackageManifest("package.json", { loose: false, write: true })
).toBe(0);
expect(didWriteToPath).toBe(false);
expect(consoleErrorSpy).not.toBeCalled();
expect(consoleLogSpy).not.toBeCalled();
expect(consoleWarnSpy).not.toBeCalled();
});
test("returns error code if changes are needed", () => {
fs.__setMockContent(mockManifest);
rnxKitConfig.__setMockConfig({
reactNativeVersion: "0.64.0",
capabilities: ["core-ios"],
});
expect(checkPackageManifest("package.json", defaultOptions)).not.toBe(0);
expect(consoleErrorSpy).toBeCalledTimes(1);
expect(consoleWarnSpy).not.toBeCalled();
expect(consoleLogSpy).toBeCalledTimes(2);
});
test("writes changes back to 'package.json'", () => {
let didWriteToPath = false;
fs.__setMockContent(mockManifest);
fs.__setMockFileWriter((p, _content) => {
didWriteToPath = p;
});
rnxKitConfig.__setMockConfig({
reactNativeVersion: "0.64.0",
capabilities: ["core-ios"],
});
expect(
checkPackageManifest("package.json", { loose: false, write: true })
).toBe(0);
expect(didWriteToPath).toBe("package.json");
expect(consoleErrorSpy).not.toBeCalled();
expect(consoleWarnSpy).not.toBeCalled();
expect(consoleLogSpy).not.toBeCalled();
});
test("preserves indentation in 'package.json'", () => {
let output = "";
fs.__setMockContent(mockManifest, "\t");
fs.__setMockFileWriter((_, content) => {
output = content;
});
rnxKitConfig.__setMockConfig({
reactNativeVersion: "0.64.0",
capabilities: ["core-ios"],
});
expect(
checkPackageManifest("package.json", { loose: false, write: true })
).toBe(0);
expect(output).toMatchSnapshot();
expect(consoleErrorSpy).not.toBeCalled();
expect(consoleWarnSpy).not.toBeCalled();
expect(consoleLogSpy).not.toBeCalled();
});
test("uses minimum supported version as development version", () => {
fs.__setMockContent({
...mockManifest,
peerDependencies: {
react: react_v62_v63_v64,
"react-native": v62_v63_v64,
},
devDependencies: {
react: packageVersion(profile_0_62, "react"),
"react-native": packageVersion(profile_0_62, "core"),
},
});
rnxKitConfig.__setMockConfig({
reactNativeVersion: "^0.62 || ^0.63 || ^0.64",
capabilities: ["core-ios"],
});
expect(checkPackageManifest("package.json", defaultOptions)).toBe(0);
expect(consoleErrorSpy).not.toBeCalled();
expect(consoleLogSpy).not.toBeCalled();
expect(consoleWarnSpy).not.toBeCalled();
});
test("uses declared development version", () => {
fs.__setMockContent({
...mockManifest,
peerDependencies: {
react: react_v62_v63_v64,
"react-native": v62_v63_v64,
},
devDependencies: {
react: packageVersion(profile_0_63, "react"),
"react-native": packageVersion(profile_0_63, "core"),
},
});
rnxKitConfig.__setMockConfig({
reactNativeVersion: "^0.62 || ^0.63 || ^0.64",
reactNativeDevVersion: "0.63.4",
capabilities: ["core-ios"],
});
expect(checkPackageManifest("package.json", defaultOptions)).toBe(0);
expect(consoleErrorSpy).not.toBeCalled();
expect(consoleLogSpy).not.toBeCalled();
expect(consoleWarnSpy).not.toBeCalled();
});
test("handles development version ranges", () => {
fs.__setMockContent({
...mockManifest,
peerDependencies: {
react: react_v62_v63_v64,
"react-native": v62_v63_v64,
},
devDependencies: {
react: packageVersion(profile_0_63, "react"),
"react-native": packageVersion(profile_0_63, "core"),
},
});
rnxKitConfig.__setMockConfig({
reactNativeVersion: "^0.62 || ^0.63 || ^0.64",
reactNativeDevVersion: "^0.63.4",
capabilities: ["core-ios"],
});
expect(checkPackageManifest("package.json", defaultOptions)).toBe(0);
expect(consoleErrorSpy).not.toBeCalled();
expect(consoleLogSpy).not.toBeCalled();
expect(consoleWarnSpy).not.toBeCalled();
});
});
describe("getCheckConfig", () => {
const rnxKitConfig = require("@rnx-kit/config");
const fs = require("fs");
const mockManifest = {
name: "@rnx-kit/dep-check",
version: "0.0.1",
};
const defaultOptions = { loose: false, write: false };
beforeEach(() => {
fs.__setMockContent(mockManifest);
rnxKitConfig.__setMockConfig();
});
afterAll(() => {
jest.clearAllMocks();
});
test("returns early if the package is unconfigured", () => {
expect(getCheckConfig("package.json", defaultOptions)).toBe(0);
});
test("returns default values given react-native version", () => {
const reactNativeVersion = "^0.64";
rnxKitConfig.__setMockConfig({ reactNativeVersion });
expect(getCheckConfig("package.json", defaultOptions)).toEqual({
capabilities: [],
kitType: "library",
manifest: mockManifest,
reactNativeVersion,
reactNativeDevVersion: semverCoerce(reactNativeVersion).version,
});
});
test("uses react-native version provided by vigilant flag if unspecified", () => {
const reactNativeVersion = "^0.64";
rnxKitConfig.__setMockConfig({ customProfiles: "" });
expect(
getCheckConfig("package.json", {
...defaultOptions,
supportedVersions: reactNativeVersion,
targetVersion: reactNativeVersion,
})
).toEqual({
capabilities: [],
kitType: "library",
manifest: mockManifest,
reactNativeVersion,
reactNativeDevVersion: reactNativeVersion,
});
});
test("does not overwrite existing config with the version provided by vigilant flag", () => {
const reactNativeVersion = "^0.64";
rnxKitConfig.__setMockConfig({ reactNativeVersion });
expect(
getCheckConfig("package.json", {
...defaultOptions,
supportedVersions: "1000.0",
targetVersion: "1000.0",
})
).toEqual({
capabilities: [],
kitType: "library",
manifest: mockManifest,
reactNativeVersion,
reactNativeDevVersion: semverCoerce(reactNativeVersion).version,
});
});
});

Просмотреть файл

@ -1,239 +0,0 @@
import { PackageManifest, readPackage } from "@rnx-kit/tools-node/package";
import path from "path";
import { getRequirements, visitDependencies } from "../src/dependencies";
jest.unmock("@rnx-kit/config");
function fixturePath(name: string) {
return path.join(process.cwd(), "test", "__fixtures__", name);
}
function useFixture(name: string): [string, PackageManifest] {
const fixture = fixturePath(name);
return [fixture, readPackage(fixture)];
}
describe("visitDependencies()", () => {
const consoleWarnSpy = jest.spyOn(global.console, "warn");
const currentWorkingDir = process.cwd();
beforeEach(() => {
consoleWarnSpy.mockReset();
});
afterEach(() => {
process.chdir(currentWorkingDir);
});
afterAll(() => {
jest.clearAllMocks();
});
test("returns if there are no direct dependencies", () => {
const visited = new Set<string>();
visitDependencies(
{ name: "@rnx-kit/dep-check", version: "1.0.0" },
process.cwd(),
() => 0,
visited
);
expect(visited.size).toBe(0);
const dependencies = { "react-native": "1000.0.0" };
visitDependencies(
{
name: "@rnx-kit/dep-check",
version: "1.0.0",
peerDependencies: dependencies,
devDependencies: dependencies,
},
process.cwd(),
() => 0,
visited
);
expect(visited.size).toBe(0);
expect(consoleWarnSpy).not.toBeCalled();
});
test("traverse transitive dependencies", () => {
const fixture = fixturePath("awesome-repo");
process.chdir(fixture);
const visited: string[] = [];
visitDependencies(
require(path.join(fixture, "package.json")),
process.cwd(),
(module) => {
visited.push(module);
}
);
expect(visited.sort()).toEqual([
"conan",
"dutch",
"john",
"quaid",
"react",
"react-native",
"t-800",
]);
expect(consoleWarnSpy).not.toBeCalled();
});
test("skips unresolved packages", () => {
const visited: string[] = [];
visitDependencies(
{
name: "@rnx-kit/dep-check",
version: "1.0.0",
dependencies: {
"this-does-not-exist": "1.0.0",
},
},
process.cwd(),
(module) => visited.push(module)
);
expect(visited.length).toBe(0);
expect(consoleWarnSpy).toBeCalledTimes(1);
});
});
describe("getRequirements()", () => {
const consoleErrorSpy = jest.spyOn(global.console, "error");
const consoleWarnSpy = jest.spyOn(global.console, "warn");
const defaultOptions = { loose: false };
afterEach(() => {
consoleErrorSpy.mockReset();
consoleWarnSpy.mockReset();
});
test("gets requirements from all dependencies", () => {
const [fixture, manifest] = useFixture("awesome-repo");
const { reactNativeVersion, capabilities } = getRequirements(
"^0.63 || ^0.64",
"app",
manifest,
fixture,
undefined,
defaultOptions
);
expect(reactNativeVersion).toBe("^0.63 || ^0.64");
expect(capabilities.sort()).toEqual([
"animation",
"netinfo",
"storage",
"webview",
]);
expect(consoleErrorSpy).not.toBeCalled();
expect(consoleWarnSpy).not.toBeCalled();
});
test("gets requirements from all dependencies with custom profiles", () => {
const cyberdyne = { name: "cyberdyne", version: "1.0.0", devOnly: true };
const skynet = { name: "skynet", version: "1.0.0" };
jest.mock(
"awesome-dep-check-profiles",
() => ({
"0.63": {
[cyberdyne.name]: cyberdyne,
[skynet.name]: skynet,
},
"0.64": {
[cyberdyne.name]: cyberdyne,
[skynet.name]: skynet,
},
}),
{ virtual: true }
);
const [fixture, manifest] = useFixture("awesome-repo-extended");
const { reactNativeVersion, capabilities } = getRequirements(
"^0.63 || ^0.64",
"app",
manifest,
fixture,
"awesome-dep-check-profiles",
defaultOptions
);
expect(reactNativeVersion).toBe("^0.63 || ^0.64");
expect(capabilities.sort()).toEqual([
"animation",
"netinfo",
"skynet",
"storage",
"webview",
]);
expect(consoleErrorSpy).not.toBeCalled();
expect(consoleWarnSpy).not.toBeCalled();
});
test("throws if no profiles can satisfy required React Native version", () => {
expect(() =>
getRequirements(
"0.60.6",
"app",
{
name: "@rnx-kit/dep-check",
version: "1.0.0",
},
"",
undefined,
defaultOptions
)
).toThrow();
expect(consoleErrorSpy).not.toBeCalled();
expect(consoleWarnSpy).not.toBeCalled();
});
test("throws if no profiles can satisfy requirement of dependencies", () => {
const [fixture, manifest] = useFixture("no-profile-satisfying-deps");
expect(() =>
getRequirements(
"^0.64",
"app",
manifest,
fixture,
undefined,
defaultOptions
)
).toThrowError("No React Native profile could satisfy all dependencies");
expect(consoleErrorSpy).toBeCalledWith(
"error",
expect.stringContaining(
"No React Native profile could satisfy all dependencies"
)
);
expect(consoleWarnSpy).not.toBeCalled();
});
test("does not throw if no profiles can satisfy requirement of dependencies in loose mode", () => {
const [fixture, manifest] = useFixture("no-profile-satisfying-deps");
expect(() =>
getRequirements("^0.64", "app", manifest, fixture, undefined, {
loose: true,
})
).not.toThrow();
expect(consoleErrorSpy).not.toBeCalled();
expect(consoleWarnSpy).toBeCalledWith(
"warn",
expect.stringContaining(
"No React Native profile could satisfy all dependencies"
)
);
});
});

Просмотреть файл

@ -1,115 +0,0 @@
import { findBadPackages } from "../src/findBadPackages";
describe("findBadPackages()", () => {
const dependenciesWithOneBadPackage = {
"react-native": "0.0.0",
"react-native-linear-gradient": "0.0.0",
};
const dependenciesWithMoreBadPackages = {
"react-native": "0.0.0",
"react-native-linear-gradient": "0.0.0",
"react-native-netinfo": "0.0.0",
};
test("finds bad packages in all dependencies", () => {
expect(
findBadPackages({
name: "Test",
version: "0.0.1",
})
).toBeUndefined();
expect(
findBadPackages({
name: "Test",
version: "0.0.1",
dependencies: dependenciesWithOneBadPackage,
})?.length
).toBe(1);
expect(
findBadPackages({
name: "Test",
version: "0.0.1",
peerDependencies: dependenciesWithOneBadPackage,
})?.length
).toBe(1);
expect(
findBadPackages({
name: "Test",
version: "0.0.1",
devDependencies: dependenciesWithOneBadPackage,
})?.length
).toBe(1);
});
test("dedupes bad packages", () => {
expect(
findBadPackages({
name: "Test",
version: "0.0.1",
dependencies: dependenciesWithOneBadPackage,
peerDependencies: dependenciesWithOneBadPackage,
})?.length
).toBe(1);
expect(
findBadPackages({
name: "Test",
version: "0.0.1",
dependencies: dependenciesWithOneBadPackage,
devDependencies: dependenciesWithOneBadPackage,
})?.length
).toBe(1);
expect(
findBadPackages({
name: "Test",
version: "0.0.1",
peerDependencies: dependenciesWithOneBadPackage,
devDependencies: dependenciesWithOneBadPackage,
})?.length
).toBe(1);
expect(
findBadPackages({
name: "Test",
version: "0.0.1",
dependencies: dependenciesWithOneBadPackage,
peerDependencies: dependenciesWithOneBadPackage,
devDependencies: dependenciesWithOneBadPackage,
})?.length
).toBe(1);
});
test("finds all bad packages", () => {
expect(
findBadPackages({
name: "Test",
version: "0.0.1",
dependencies: dependenciesWithMoreBadPackages,
})?.length
).toBe(2);
expect(
findBadPackages({
name: "Test",
version: "0.0.1",
dependencies: dependenciesWithOneBadPackage,
peerDependencies: dependenciesWithMoreBadPackages,
})?.length
).toBe(2);
expect(
findBadPackages({
name: "Test",
version: "0.0.1",
dependencies: dependenciesWithOneBadPackage,
peerDependencies: dependenciesWithMoreBadPackages,
devDependencies: dependenciesWithMoreBadPackages,
})?.length
).toBe(2);
});
});

Просмотреть файл

@ -1,17 +0,0 @@
import { compare } from "../src/helpers";
describe("compare", () => {
test("compares values", () => {
expect(compare(0, 0)).toBe(0);
expect(compare(0, 1)).toBe(-1);
expect(compare(1, 0)).toBe(1);
expect(compare("dutch", "dutch")).toBe(0);
expect(compare("dutch", "quaid")).toBe(-1);
expect(compare("quaid", "dutch")).toBe(1);
expect(compare("dutch", "dutchess")).toBe(-1);
expect(compare("dutchess", "dutch")).toBe(1);
expect(compare("hyphen-before-lowbar", "hyphen_before_lowbar")).toBe(-1);
});
});

Просмотреть файл

@ -1,20 +0,0 @@
import type { Capability } from "@rnx-kit/config";
import type { Profile, Package } from "../src/types";
export function pickPackage(profile: Profile, capability: string): Package {
const pkg = profile[capability];
if (!pkg) {
throw new Error(`Could not resolve '${capability}'`);
} else if (!("version" in pkg)) {
throw new Error(`'${capability}' is a meta package`);
}
return pkg;
}
export function packageVersion(
profile: Profile,
capability: Capability
): string {
return pickPackage(profile, capability).version;
}

Просмотреть файл

@ -1,170 +0,0 @@
import { initializeConfig } from "../src/initialize";
jest.mock("fs");
describe("initializeConfig()", () => {
const fs = require("fs");
const bundle = {
entryPath: "src/index.ts",
distPath: "dist",
assetsPath: "dist",
bundlePrefix: "main",
targets: ["ios", "android", "macos", "windows"],
platforms: {
android: {
assetsPath: "dist/res",
},
},
};
const mockManifest = {
dependencies: {
"react-native": "^0.64.1",
},
peerDependencies: {
"@react-native-community/netinfo": "^5.9.10",
"react-native-webview": "^10.10.2",
},
"rnx-kit": {
bundle,
},
};
const mockCapabilities = [
"core",
"core-android",
"core-ios",
"netinfo",
"webview",
];
beforeEach(() => {
const unset = () => {
throw new Error("unset");
};
fs.__setMockContent(unset);
fs.__setMockFileWriter(unset);
});
test("returns early if capabilities are declared", () => {
fs.__setMockContent({ "rnx-kit": { capabilities: [] } });
let didWrite = false;
fs.__setMockFileWriter(() => {
didWrite = true;
});
initializeConfig("package.json", {});
expect(didWrite).toBe(false);
});
test("returns early if no capabilities are found", () => {
fs.__setMockContent({ name: "@rnx-kit/dep-check", version: "1.0.0-test" });
let didWrite = false;
fs.__setMockFileWriter(() => {
didWrite = true;
});
initializeConfig("package.json", {});
expect(didWrite).toBe(false);
});
test("keeps existing config", () => {
fs.__setMockContent({
dependencies: {
"react-native": "^0.64.1",
},
"rnx-kit": {
platformBundle: false,
bundle,
},
});
let content = {};
fs.__setMockFileWriter((_: string, data: string) => {
content = JSON.parse(data);
});
initializeConfig("package.json", {});
const kitConfig = content["rnx-kit"];
if (!kitConfig) {
fail();
}
expect(kitConfig["platformBundle"]).toBe(false);
expect(kitConfig["bundle"]).toEqual(bundle);
});
test('adds config with type "app"', () => {
fs.__setMockContent(mockManifest);
let content = {};
fs.__setMockFileWriter((_: string, data: string) => {
content = JSON.parse(data);
});
initializeConfig("package.json", { kitType: "app" });
const kitConfig = content["rnx-kit"];
if (!kitConfig) {
fail();
}
expect(kitConfig["bundle"]).toEqual(bundle);
expect(kitConfig["reactNativeVersion"]).toEqual("^0.64");
expect(kitConfig["reactNativeDevVersion"]).toBeUndefined();
expect(kitConfig["kitType"]).toEqual("app");
expect(kitConfig["capabilities"]).toEqual(mockCapabilities);
expect(kitConfig["customProfiles"]).toBeUndefined();
});
test('adds config with type "library"', () => {
fs.__setMockContent(mockManifest);
let content = {};
fs.__setMockFileWriter((_: string, data: string) => {
content = JSON.parse(data);
});
initializeConfig("package.json", { kitType: "library" });
const kitConfig = content["rnx-kit"];
if (!kitConfig) {
fail();
}
expect(kitConfig["bundle"]).toEqual(bundle);
expect(kitConfig["reactNativeVersion"]).toEqual("^0.64");
expect(kitConfig["reactNativeDevVersion"]).toEqual("0.64.0");
expect(kitConfig["kitType"]).toEqual("library");
expect(kitConfig["capabilities"]).toEqual(mockCapabilities);
expect(kitConfig["customProfiles"]).toBeUndefined();
});
// Test disabled because custom profile now depends on align-deps
xtest("adds config with custom profiles", () => {
fs.__setMockContent(mockManifest);
let content = {};
fs.__setMockFileWriter((_: string, data: string) => {
content = JSON.parse(data);
});
initializeConfig("package.json", {
kitType: "library",
customProfilesPath: "@rnx-kit/scripts/align-deps-preset.js",
});
const kitConfig = content["rnx-kit"];
if (!kitConfig) {
fail();
}
expect(kitConfig["customProfiles"]).toEqual(
"@rnx-kit/scripts/align-deps-preset.js"
);
});
});

Просмотреть файл

@ -1,312 +0,0 @@
import {
removeKeys,
updateDependencies,
updatePackageManifest,
} from "../src/manifest";
import profile_0_63 from "../src/presets/microsoft/profile-0.63";
import profile_0_64 from "../src/presets/microsoft/profile-0.64";
import type { Package } from "../src/types";
import { packageVersion, pickPackage } from "./helpers";
const mockDependencies = {
typescript: "0.0.0",
react: "0.0.0",
"react-native-test-app": "0.0.0",
"react-native": "0.0.0",
};
describe("removeKeys()", () => {
test("returns a new copy of object with specified keys removed", () => {
const original = { x: "1", y: "2", z: "3" };
const originalKeys = Object.keys(original);
const modified = removeKeys(original, ["x", "z"]);
expect(modified).not.toBe(original);
expect(Object.keys(original)).toEqual(originalKeys);
expect(modified).toEqual({ y: original.y });
});
test("returns a new copy of object even if no keys are removed", () => {
const original = { x: "1", y: "2", z: "3" };
const originalKeys = Object.keys(original);
const modified = removeKeys(original, ["a", "b"]);
expect(modified).not.toBe(original);
expect(Object.keys(original)).toEqual(originalKeys);
expect(modified).toEqual(original);
});
test("handles undefined objects", () => {
expect(removeKeys(undefined, ["x", "y"])).toBeUndefined();
});
});
describe("updateDependencies()", () => {
const resolvedPackages: Record<string, Package[]> = {
react: [
pickPackage(profile_0_63, "react"),
pickPackage(profile_0_64, "react"),
],
"react-native": [
pickPackage(profile_0_63, "core"),
pickPackage(profile_0_64, "core"),
],
"react-native-macos": [
pickPackage(profile_0_63, "core-macos"),
pickPackage(profile_0_64, "core-macos"),
],
"react-native-test-app": [pickPackage(profile_0_64, "test-app")],
"react-native-windows": [
pickPackage(profile_0_63, "core-windows"),
pickPackage(profile_0_64, "core-windows"),
],
};
test("bumps dependencies to maximum supported version", () => {
const updated = updateDependencies(
mockDependencies,
resolvedPackages,
"direct"
);
expect(updated).toEqual({
react: packageVersion(profile_0_64, "react"),
"react-native": packageVersion(profile_0_64, "core"),
"react-native-macos": packageVersion(profile_0_64, "core-macos"),
"react-native-test-app": "0.0.0",
"react-native-windows": packageVersion(profile_0_64, "core-windows"),
typescript: "0.0.0",
});
});
test("bumps dependencies to minimum supported version", () => {
const updated = updateDependencies(
mockDependencies,
resolvedPackages,
"development"
);
expect(updated).toEqual({
react: packageVersion(profile_0_63, "react"),
"react-native": packageVersion(profile_0_63, "core"),
"react-native-macos": packageVersion(profile_0_63, "core-macos"),
"react-native-test-app": packageVersion(profile_0_63, "test-app"),
"react-native-windows": packageVersion(profile_0_63, "core-windows"),
typescript: "0.0.0",
});
});
test("bumps dependencies to widest possible version range", () => {
const updated = updateDependencies(
mockDependencies,
resolvedPackages,
"peer"
);
expect(updated).toEqual({
react: `${packageVersion(profile_0_63, "react")} || ${packageVersion(
profile_0_64,
"react"
)}`,
"react-native": `${packageVersion(
profile_0_63,
"core"
)} || ${packageVersion(profile_0_64, "core")}`,
"react-native-macos": `${packageVersion(
profile_0_63,
"core-macos"
)} || ${packageVersion(profile_0_64, "core-macos")}`,
"react-native-test-app": "0.0.0",
"react-native-windows": `${packageVersion(
profile_0_63,
"core-windows"
)} || ${packageVersion(profile_0_64, "core-windows")}`,
typescript: "0.0.0",
});
});
test("sorts keys", () => {
const updated = updateDependencies(
mockDependencies,
resolvedPackages,
"development"
);
const updatedKeys = Object.keys(updated);
const originalKeys = Object.keys(mockDependencies);
expect(updatedKeys).not.toEqual(originalKeys);
expect(updatedKeys.sort()).not.toEqual(originalKeys);
});
test("sets undefined dependencies", () => {
expect(
updateDependencies(undefined, resolvedPackages, "development")
).toEqual({
react: packageVersion(profile_0_63, "react"),
"react-native": packageVersion(profile_0_63, "core"),
"react-native-macos": packageVersion(profile_0_63, "core-macos"),
"react-native-test-app": packageVersion(profile_0_63, "test-app"),
"react-native-windows": packageVersion(profile_0_63, "core-windows"),
});
});
});
describe("updatePackageManifest()", () => {
test("sets direct dependencies for apps", () => {
const { dependencies, devDependencies, peerDependencies } =
updatePackageManifest(
{
name: "Test",
version: "0.0.1",
dependencies: mockDependencies,
peerDependencies: {},
devDependencies: {},
},
["core-android", "core-ios"],
[profile_0_63, profile_0_64],
[profile_0_64],
"app"
);
expect(dependencies).toEqual({
...mockDependencies,
react: packageVersion(profile_0_64, "react"),
"react-native": packageVersion(profile_0_64, "core"),
});
expect(peerDependencies).toBeUndefined();
expect(devDependencies).toBeUndefined();
});
test("removes dependencies from devDependencies for apps", () => {
const { dependencies, devDependencies, peerDependencies } =
updatePackageManifest(
{
name: "Test",
version: "0.0.1",
dependencies: {},
peerDependencies: {},
devDependencies: mockDependencies,
},
["core-android", "core-ios", "react"],
[profile_0_63, profile_0_64],
[profile_0_64],
"app"
);
expect(dependencies).toEqual({
react: packageVersion(profile_0_64, "react"),
"react-native": packageVersion(profile_0_64, "core"),
});
expect(peerDependencies).toBeUndefined();
expect(devDependencies).toEqual({
"react-native-test-app": "0.0.0",
typescript: "0.0.0",
});
});
test("removes dependencies from peerDependencies for apps", () => {
const { dependencies, devDependencies, peerDependencies } =
updatePackageManifest(
{
name: "Test",
version: "0.0.1",
dependencies: {},
peerDependencies: mockDependencies,
devDependencies: {},
},
["core-android", "core-ios", "react"],
[profile_0_63, profile_0_64],
[profile_0_64],
"app"
);
expect(dependencies).toEqual({
react: packageVersion(profile_0_64, "react"),
"react-native": packageVersion(profile_0_64, "core"),
});
expect(peerDependencies).toEqual({
"react-native-test-app": "0.0.0",
typescript: "0.0.0",
});
expect(devDependencies).toBeUndefined();
});
test("sets dev/peer dependencies for libraries", () => {
const { dependencies, devDependencies, peerDependencies } =
updatePackageManifest(
{
name: "Test",
version: "0.0.1",
dependencies: { "@rnx-kit/dep-check": "^1.0.0" },
peerDependencies: mockDependencies,
},
["core-android", "core-ios"],
[profile_0_63, profile_0_64],
[profile_0_64],
"library"
);
expect(dependencies).toEqual({ "@rnx-kit/dep-check": "^1.0.0" });
expect(peerDependencies).toEqual({
...mockDependencies,
react: [
packageVersion(profile_0_63, "react"),
packageVersion(profile_0_64, "react"),
].join(" || "),
"react-native": [
packageVersion(profile_0_63, "core"),
packageVersion(profile_0_64, "core"),
].join(" || "),
});
expect(devDependencies).toEqual({
react: packageVersion(profile_0_64, "react"),
"react-native": packageVersion(profile_0_64, "core"),
});
});
test("removes dependencies from direct dependencies for libraries", () => {
const { dependencies, devDependencies, peerDependencies } =
updatePackageManifest(
{
name: "Test",
version: "0.0.1",
dependencies: {
"@rnx-kit/dep-check": "^1.0.0",
"react-native": "0.0.0",
},
peerDependencies: mockDependencies,
devDependencies: {},
},
["core-android", "core-ios"],
[profile_0_64],
[profile_0_64],
"library"
);
expect(dependencies).toEqual({ "@rnx-kit/dep-check": "^1.0.0" });
expect(peerDependencies).toEqual({
...mockDependencies,
react: packageVersion(profile_0_64, "react"),
"react-native": packageVersion(profile_0_64, "core"),
});
expect(devDependencies).toEqual({
react: packageVersion(profile_0_64, "react"),
"react-native": packageVersion(profile_0_64, "core"),
});
});
test("always sets dev-only dependencies", () => {
const { dependencies, devDependencies, peerDependencies } =
updatePackageManifest(
{
name: "Test",
version: "0.0.1",
dependencies: mockDependencies,
peerDependencies: {},
devDependencies: {},
},
["core-android", "core-ios", "test-app"],
[profile_0_63, profile_0_64],
[profile_0_64],
"app"
);
expect(dependencies).toEqual({
...mockDependencies,
react: packageVersion(profile_0_64, "react"),
"react-native": packageVersion(profile_0_64, "core"),
});
expect(peerDependencies).toBeUndefined();
expect(devDependencies).toEqual({
"react-native-test-app": packageVersion(profile_0_64, "test-app"),
});
});
});

Просмотреть файл

@ -1,265 +0,0 @@
import * as path from "path";
import semver from "semver";
import preset from "../src/presets/microsoft";
import profile_0_62 from "../src/presets/microsoft/profile-0.62";
import profile_0_63 from "../src/presets/microsoft/profile-0.63";
import profile_0_64 from "../src/presets/microsoft/profile-0.64";
import {
getProfilesFor,
getProfileVersionsFor,
loadCustomProfiles,
profilesSatisfying,
resolveCustomProfiles,
} from "../src/profiles";
import { ProfileVersion } from "../src/types";
describe("Microsoft preset", () => {
test("matches react-native versions", () => {
const includePrerelease = {
includePrerelease: true,
};
Object.entries(preset).forEach(([version, capabilities]) => {
const versionRange = "^" + version;
Object.entries(capabilities).forEach(([capability, pkg]) => {
if (capability === "core") {
expect(
"version" in pkg &&
semver.subset(pkg.version, versionRange, includePrerelease)
).toBe(true);
}
});
});
});
});
describe("getProfileVersionsFor()", () => {
test("returns profile versions for specific version", () => {
expect(getProfileVersionsFor("0.61.5")).toEqual(["0.61"]);
expect(getProfileVersionsFor("0.62.2")).toEqual(["0.62"]);
expect(getProfileVersionsFor("0.63.4")).toEqual(["0.63"]);
expect(getProfileVersionsFor("0.64.0")).toEqual(["0.64"]);
});
test("returns profile for one version range", () => {
expect(getProfileVersionsFor("^0.62.2")).toEqual(["0.62"]);
expect(getProfileVersionsFor("^0.63.4")).toEqual(["0.63"]);
expect(getProfileVersionsFor("^0.64.0")).toEqual(["0.64"]);
});
test("returns profiles for bigger version ranges", () => {
const profiles = getProfileVersionsFor(">=0.66.4");
expect(profiles).toEqual(["0.66", "0.67", "0.68", "0.69", "0.70"]);
});
test("returns profiles for multiple version ranges", () => {
const profiles = getProfileVersionsFor("^0.63.0 || ^0.64.0");
expect(profiles).toEqual(["0.63", "0.64"]);
});
test("returns an empty array when an unsupported version is provided", () => {
expect(getProfileVersionsFor("^0.60.6")).toEqual([]);
});
test("throws when an invalid version is provided", () => {
expect(() => getProfileVersionsFor("invalid")).toThrowError(
"Invalid 'react-native' version"
);
});
});
describe("getProfilesFor()", () => {
const consoleErrorSpy = jest.spyOn(global.console, "error");
beforeEach(() => {
consoleErrorSpy.mockReset();
});
afterAll(() => {
jest.clearAllMocks();
});
test("returns profile for specific version", () => {
const profiles = getProfilesFor("0.64.0", undefined);
expect(profiles).toEqual([profile_0_64]);
expect(consoleErrorSpy).not.toBeCalled();
});
test("returns profile for one version range", () => {
expect(getProfilesFor("^0.62.2", undefined)).toEqual([profile_0_62]);
expect(getProfilesFor("^0.63.4", undefined)).toEqual([profile_0_63]);
expect(getProfilesFor("^0.64.0", undefined)).toEqual([profile_0_64]);
});
test("returns profiles for bigger version ranges", () => {
const profiles = getProfilesFor(">=0.62.2", undefined);
expect(profiles.slice(0, 3)).toEqual([
profile_0_62,
profile_0_63,
profile_0_64,
]);
expect(consoleErrorSpy).not.toBeCalled();
});
test("returns profiles for multiple version ranges", () => {
const profiles = getProfilesFor("^0.63.0 || ^0.64.0", undefined);
expect(profiles).toEqual([profile_0_63, profile_0_64]);
expect(consoleErrorSpy).not.toBeCalled();
});
test("throws when an unsupported version is provided", () => {
expect(() => getProfilesFor("^0.60.6", undefined)).toThrowError(
"Unsupported 'react-native' version"
);
expect(consoleErrorSpy).not.toBeCalled();
});
test("throws when an invalid version is provided", () => {
expect(() => getProfilesFor("invalid", undefined)).toThrowError(
"Invalid 'react-native' version"
);
expect(consoleErrorSpy).not.toBeCalled();
});
test("throws if custom profiles module does not exist", () => {
expect(() =>
getProfilesFor("^0.59", "non-existent-profiles-module")
).toThrow(`Cannot find module 'non-existent-profiles-module'`);
});
test("throws if custom profiles module does not default export an object", () => {
jest.mock("bad-profiles-module", () => null, { virtual: true });
expect(() => getProfilesFor("^0.59", "bad-profiles-module")).toThrow(
"'bad-profiles-module' doesn't default export profiles"
);
expect(consoleErrorSpy).toBeCalledTimes(1);
});
test("appends custom profiles", () => {
const skynet = { name: "skynet", version: "1.0.0" };
jest.mock(
"good-profiles-module",
() => ({ "0.62": { [skynet.name]: skynet } }),
{ virtual: true }
);
const [profile_0_62, profile_0_63] = getProfilesFor(
"^0.62 || ^0.63",
"good-profiles-module"
);
expect(skynet.name in profile_0_62).toBe(true);
expect(skynet.name in profile_0_63).toBe(false);
expect(consoleErrorSpy).not.toBeCalled();
});
});
describe("loadCustomProfiles()", () => {
test("returns any empty object if no custom profiles are specified", () => {
expect(loadCustomProfiles(undefined)).toEqual({});
});
test("throws if custom profiles are not the right shape", () => {
expect(() =>
loadCustomProfiles(
path.join(
__dirname,
"__fixtures__",
"custom-profiles",
"local-profiles.js"
)
)
).toThrow("doesn't default export profiles");
});
test("loads valid custom profiles", () => {
expect(
loadCustomProfiles(
path.join(
__dirname,
"__fixtures__",
"custom-profiles",
"valid-profiles.js"
)
)
).toMatchSnapshot();
});
test("prepends root-level capabilities to all profiles", () => {
expect(
loadCustomProfiles(
path.join(
__dirname,
"__fixtures__",
"custom-profiles",
"root-level-profiles.js"
)
)
).toMatchSnapshot();
});
});
describe("profilesSatisfying()", () => {
const profileVersions: ProfileVersion[] = ["0.61", "0.62", "0.63", "0.64"];
test("returns an empty array when no profiles can satisfy requirements", () => {
expect(profilesSatisfying([], "^0.63.0 || ^0.64.0")).toEqual([]);
expect(profilesSatisfying(["0.61"], "^0.63.0 || ^0.64.0")).toEqual([]);
});
test("returns all profiles satisfying version", () => {
expect(profilesSatisfying(profileVersions, "0.63.2")).toEqual(["0.63"]);
expect(profilesSatisfying(profileVersions, "0.64.0-rc1")).toEqual(["0.64"]);
expect(profilesSatisfying(profileVersions, "0.64.0")).toEqual(["0.64"]);
});
test("returns all profiles satisfying version range", () => {
expect(
profilesSatisfying(profileVersions, "^0.63.0-0 || ^0.64.0-0")
).toEqual(["0.63", "0.64"]);
expect(profilesSatisfying(profileVersions, "^0.63.0 || ^0.64.0")).toEqual([
"0.63",
"0.64",
]);
});
});
describe("resolveCustomProfiles()", () => {
const projectRoot = `${__dirname}/__fixtures__/custom-profiles`;
test("handles undefined and empty strings", () => {
expect(resolveCustomProfiles(projectRoot, undefined)).toBeUndefined();
expect(resolveCustomProfiles(projectRoot, "")).toBeUndefined();
});
test("throws if the module cannot be resolved", () => {
expect(() =>
resolveCustomProfiles(projectRoot, "non-existent-custom-profiles")
).toThrow("Cannot resolve module");
});
test("returns absolute path for module ids", () => {
const profilesPkg = "custom-profiles-package";
expect(resolveCustomProfiles(projectRoot, profilesPkg)).toEqual(
expect.stringContaining(
path.join(
"__fixtures__",
"custom-profiles",
"node_modules",
profilesPkg,
"index.js"
)
)
);
});
test("returns absolute path for relative paths", () => {
expect(resolveCustomProfiles(projectRoot, "./local-profiles.js")).toEqual(
expect.stringContaining(
path.join("__fixtures__", "custom-profiles", "local-profiles.js")
)
);
});
});

Просмотреть файл

@ -1,204 +0,0 @@
import type { PackageManifest } from "@rnx-kit/tools-node/package";
import prompts from "prompts";
import { makeSetVersionCommand } from "../src/setVersion";
jest.mock("fs");
type Result = {
didWrite: boolean;
manifest: Record<string, unknown>;
};
describe("makeSetVersionCommand()", () => {
const rnxKitConfig = require("@rnx-kit/config");
const fs = require("fs");
function setupMocks(manifest: PackageManifest): Result {
fs.__setMockContent(manifest);
const result: Result = { didWrite: false, manifest: {} };
fs.__setMockFileWriter((_: string, content: string) => {
const updatedManifest = JSON.parse(content);
fs.__setMockContent(updatedManifest);
rnxKitConfig.__setMockConfig(updatedManifest["rnx-kit"]);
result.didWrite = true;
result.manifest = updatedManifest;
});
return result;
}
const mockManifest = {
name: "@rnx-kit/dep-check",
version: "1.0.0-test",
dependencies: {
react: "16.13.1",
"react-native": "^0.63.2",
},
devDependencies: {},
"rnx-kit": {
reactNativeVersion: "^0.63",
kitType: "app",
capabilities: ["core"],
},
};
afterEach(() => {
fs.__setMockContent({});
rnxKitConfig.__setMockConfig();
jest.clearAllMocks();
});
test("rejects unsupported versions `react-native`", async () => {
expect(makeSetVersionCommand("0.59")).rejects.toEqual(
expect.objectContaining({
message: expect.stringContaining(
"Unsupported 'react-native' version/range:"
),
})
);
});
test("updates dependencies", async () => {
const result = setupMocks({
...mockManifest,
"rnx-kit": {
...mockManifest["rnx-kit"],
kitType: "library",
reactNativeDevVersion: "^0.63.0",
},
});
const command = await makeSetVersionCommand("0.64,0.63");
expect(typeof command).toBe("function");
expect(command("package.json")).toBe(0);
expect(result.manifest).toEqual({
...mockManifest,
dependencies: undefined,
devDependencies: {
react: "17.0.1",
"react-native": "^0.64.2",
},
peerDependencies: {
react: "16.13.1 || 17.0.1",
"react-native": "^0.63.2 || ^0.64.2",
},
"rnx-kit": {
...mockManifest["rnx-kit"],
kitType: "library",
reactNativeVersion: "^0.63.0 || ^0.64.0",
reactNativeDevVersion: "^0.64.0",
},
});
});
test("removes `reactNativeDevVersion` if `kitType` is `app`", async () => {
const result = setupMocks({
...mockManifest,
"rnx-kit": {
...mockManifest["rnx-kit"],
reactNativeDevVersion: "^0.63.0",
},
});
const command = await makeSetVersionCommand("0.64,0.63");
expect(typeof command).toBe("function");
expect(command("package.json")).toBe(0);
expect(result.manifest).toEqual({
...mockManifest,
dependencies: {
react: "17.0.1",
"react-native": "^0.64.2",
},
devDependencies: undefined,
"rnx-kit": {
...mockManifest["rnx-kit"],
reactNativeVersion: "^0.63.0 || ^0.64.0",
},
});
});
test("prompts the user if no version is specified", async () => {
const result = setupMocks(mockManifest);
prompts.inject([["0.63", "0.64"], "0.64"]);
const command = await makeSetVersionCommand("");
expect(typeof command).toBe("function");
expect(command("package.json")).toBe(0);
expect(result.manifest).toEqual({
...mockManifest,
dependencies: {
react: "17.0.1",
"react-native": "^0.64.2",
},
devDependencies: undefined,
"rnx-kit": {
...mockManifest["rnx-kit"],
reactNativeVersion: "^0.63 || ^0.64",
},
});
});
test("skips the second prompt if only one version is supported", async () => {
const result = setupMocks(mockManifest);
prompts.inject([["0.64"]]);
const command = await makeSetVersionCommand("");
expect(typeof command).toBe("function");
expect(command("package.json")).toBe(0);
expect(result.manifest).toEqual({
...mockManifest,
dependencies: {
react: "17.0.1",
"react-native": "^0.64.2",
},
devDependencies: undefined,
"rnx-kit": {
...mockManifest["rnx-kit"],
reactNativeVersion: "^0.64",
},
});
});
test('skips "dirty" packages', async () => {
rnxKitConfig.__setMockConfig(mockManifest["rnx-kit"]);
const result = setupMocks({
...mockManifest,
dependencies: {
"react-native": "^0.62.3",
},
});
prompts.inject([["0.64"]]);
const command = await makeSetVersionCommand("");
expect(typeof command).toBe("function");
expect(command("package.json")).not.toBe(0);
expect(result.didWrite).toBe(false);
});
test("skips unconfigured packages", async () => {
const result = setupMocks({
...mockManifest,
"rnx-kit": undefined,
} as PackageManifest);
prompts.inject([["0.64"]]);
const command = await makeSetVersionCommand("");
expect(typeof command).toBe("function");
expect(command("package.json")).toBe(0);
expect(result.didWrite).toBe(false);
});
test("exits if the user cancels during prompts", async () => {
prompts.inject([undefined]);
expect(await makeSetVersionCommand("")).toBeUndefined();
prompts.inject([["0.63", "0.64"], undefined]);
expect(await makeSetVersionCommand("")).toBeUndefined();
});
});

Просмотреть файл

@ -1,477 +0,0 @@
import { parseProfilesString } from "../src/profiles";
import {
buildManifestProfile,
buildProfileFromConfig,
inspect,
makeVigilantCommand,
} from "../src/vigilant";
jest.mock("fs");
describe("buildManifestProfile()", () => {
const testVersion = "1.0.0-test";
test("builds a package manifest for a single profile version", () => {
const profiles = parseProfilesString("0.64", undefined);
const profile = buildManifestProfile(profiles);
profile.version = testVersion;
expect(profile).toMatchSnapshot();
});
test("builds a package manifest for multiple profile versions", () => {
const profiles = parseProfilesString("0.64,0.63", undefined);
const profile = buildManifestProfile(profiles);
profile.version = testVersion;
expect(profile).toMatchSnapshot();
});
test("includes devOnly packages under `dependencies`", () => {
const profiles = parseProfilesString("0.64", undefined);
const { dependencies, devDependencies, peerDependencies } =
buildManifestProfile(profiles);
expect("react-native-test-app" in dependencies).toBe(true);
expect("react-native-test-app" in peerDependencies).toBe(false);
expect("react-native-test-app" in devDependencies).toBe(true);
});
test("includes custom profiles", () => {
const skynet = { name: "skynet", version: "1.0.0" };
jest.mock(
"vigilant-custom-profiles",
() => ({ "0.64": { [skynet.name]: skynet } }),
{ virtual: true }
);
const profiles = parseProfilesString("0.64", "vigilant-custom-profiles");
const { dependencies, devDependencies, peerDependencies } =
buildManifestProfile(profiles);
expect(skynet.name in dependencies).toBe(true);
expect(skynet.name in peerDependencies).toBe(true);
expect(skynet.name in devDependencies).toBe(true);
});
test("throws when no profiles match the requested versions", () => {
expect(() => parseProfilesString("0.59", undefined)).toThrow();
expect(() => parseProfilesString("0.59,0.64", undefined)).toThrow();
});
});
describe("buildProfileFromConfig()", () => {
const profiles = parseProfilesString("0.64", undefined);
const defaultProfile = buildManifestProfile(profiles);
test("returns default profile if there is no config", () => {
expect(buildProfileFromConfig(0, defaultProfile)).toBe(defaultProfile);
});
test("filters out managed capabilities", () => {
const dependencies = [
"dependencies",
"peerDependencies",
"devDependencies",
] as const;
const config = {
kitType: "library" as const,
reactNativeVersion: "0.64",
reactNativeDevVersion: "0.64",
capabilities: [],
manifest: {
name: "@rnx-kit/dep-check",
version: "1.0.0",
dependencies: {
"react-native": "^0.64.0",
},
},
};
const profile = buildProfileFromConfig(config, defaultProfile);
dependencies.forEach((section) => {
expect(Object.keys(profile[section])).toContain("react");
expect(Object.keys(profile[section])).toContain("react-native");
});
const withCapabilities = buildProfileFromConfig(
{
...config,
capabilities: ["core-android", "core-ios"],
},
defaultProfile
);
dependencies.forEach((section) => {
expect(Object.keys(withCapabilities[section])).not.toContain("react");
expect(Object.keys(withCapabilities[section])).not.toContain(
"react-native"
);
});
});
});
describe("inspect()", () => {
const mockManifestProfile = {
name: "@rnx-kit/dep-check",
version: "1.0.0",
dependencies: {
"react-native": "^0.63.2",
},
peerDependencies: {
"react-native": "^0.63 || ^0.64",
},
devDependencies: {
"react-native": "^0.63.2",
},
};
test("handles empty dependencies", () => {
const manifest = {
name: "@rnx-kit/dep-check",
version: "1.0.0",
};
expect(inspect(manifest, mockManifestProfile, false)).toEqual([]);
expect(inspect(manifest, mockManifestProfile, true)).toEqual([]);
});
test("ignores unmanaged dependencies", () => {
const dependencies = {
"@babel/core": "^7.0.0",
"react-native": "0.63.2",
};
const manifest = {
name: "@rnx-kit/dep-check",
version: "1.0.0",
dependencies,
};
const expectedChanges = [
{
name: "react-native",
from: manifest.dependencies["react-native"],
to: mockManifestProfile.dependencies["react-native"],
section: "dependencies",
},
];
expect(inspect(manifest, mockManifestProfile, false)).toEqual(
expectedChanges
);
expect(manifest.dependencies).toEqual(dependencies);
});
test("inspects all dependency types", () => {
const manifest = {
name: "@rnx-kit/dep-check",
version: "1.0.0",
dependencies: {
"@babel/core": "^7.0.0",
},
peerDependencies: {
"react-native": "0.63.2",
},
devDependencies: {
"react-native": "0.63.2",
},
};
const expectedChanges = [
{
name: "react-native",
from: manifest.peerDependencies["react-native"],
to: mockManifestProfile.peerDependencies["react-native"],
section: "peerDependencies",
},
{
name: "react-native",
from: manifest.devDependencies["react-native"],
to: mockManifestProfile.devDependencies["react-native"],
section: "devDependencies",
},
];
expect(inspect(manifest, mockManifestProfile, false)).toEqual(
expectedChanges
);
});
test("modifies the manifest when `write: true`", () => {
const dependencies = {
"@babel/core": "^7.0.0",
"react-native": "0.63.2",
};
const manifest = {
name: "@rnx-kit/dep-check",
version: "1.0.0",
dependencies: { ...dependencies },
};
const expectedChanges = [
{
name: "react-native",
from: manifest.dependencies["react-native"],
to: mockManifestProfile.dependencies["react-native"],
section: "dependencies",
},
];
expect(inspect(manifest, mockManifestProfile, true)).toEqual(
expectedChanges
);
expect(manifest.dependencies).not.toEqual(dependencies);
});
test("does not rewrite peerDependencies if superset", () => {
const manifest = {
name: "@rnx-kit/dep-check",
version: "1.0.0",
dependencies: {
react: "^16.8.1",
},
peerDependencies: {
metro: "*",
react: ">=16.8.0 <18.0.0",
"react-native": ">=0.64",
},
devDependencies: {},
};
const profile = {
name: "@rnx-kit/dep-check",
version: "1.0.0",
dependencies: {
react: "~17.0.1",
},
peerDependencies: {
metro: "^0.66.2",
react: "~17.0.1",
"react-native": "^0.66.0-0",
},
devDependencies: {},
};
const expectedChanges = [
{
name: "react",
from: manifest.dependencies["react"],
to: profile.dependencies["react"],
section: "dependencies",
},
];
expect(inspect(manifest, profile, false)).toEqual(expectedChanges);
});
});
describe("makeVigilantCommand()", () => {
const rnxKitConfig = require("@rnx-kit/config");
const fs = require("fs");
const consoleErrorSpy = jest.spyOn(global.console, "error");
beforeEach(() => {
consoleErrorSpy.mockReset();
fs.__setMockContent({});
fs.__setMockFileWriter(() => {
throw new Error("mock for fs.writeFileSync is not set");
});
});
afterAll(() => {
jest.clearAllMocks();
});
test("returns no command if no versions are specified", () => {
expect(
makeVigilantCommand({ versions: "", loose: false, write: false })
).toBeUndefined();
});
test("returns exit code 0 when there are no violations", () => {
fs.__setMockContent({
name: "@rnx-kit/dep-check",
version: "1.0.0",
dependencies: {
"react-native": "^0.63.2",
},
});
let didWrite = false;
fs.__setMockFileWriter(() => {
didWrite = true;
});
const result = makeVigilantCommand({
versions: "0.63",
loose: false,
write: false,
})("package.json");
expect(result).toBe(0);
expect(didWrite).toBe(false);
expect(consoleErrorSpy).not.toBeCalled();
});
test("returns non-zero exit code when there are violations", () => {
fs.__setMockContent({
name: "@rnx-kit/dep-check",
version: "1.0.0",
dependencies: {
"react-native": "0.63.2",
},
});
let didWrite = false;
fs.__setMockFileWriter(() => {
didWrite = true;
});
const result = makeVigilantCommand({
versions: "0.63",
loose: false,
write: false,
})("package.json");
expect(result).not.toBe(0);
expect(didWrite).toBe(false);
expect(consoleErrorSpy).toBeCalledTimes(1);
});
test("returns exit code 0 when writing", () => {
fs.__setMockContent({
name: "@rnx-kit/dep-check",
version: "1.0.0",
dependencies: {
"react-native": "0.63.2",
},
});
let didWrite = false;
fs.__setMockFileWriter(() => {
didWrite = true;
});
const result = makeVigilantCommand({
versions: "0.63",
loose: false,
write: true,
})("package.json");
expect(result).toBe(0);
expect(didWrite).toBe(true);
expect(consoleErrorSpy).not.toBeCalled();
});
test("excludes specified packages", () => {
fs.__setMockContent({
name: "@rnx-kit/dep-check",
version: "1.0.0",
dependencies: {
"react-native": "0.59.10",
},
});
let didWrite = false;
fs.__setMockFileWriter(() => {
didWrite = true;
});
const result = makeVigilantCommand({
versions: "0.63",
write: false,
excludePackages: "@rnx-kit/dep-check",
loose: false,
})("package.json");
expect(result).toBe(0);
expect(didWrite).toBe(false);
expect(consoleErrorSpy).not.toBeCalled();
});
test("uses package-specific custom profiles", () => {
const fixture = `${__dirname}/__fixtures__/config-custom-profiles-only`;
const kitConfig = {
customProfiles: `${fixture}/packageSpecificProfiles.js`,
};
const inputManifest = {
name: "@rnx-kit/dep-check",
version: "1.0.0",
peerDependencies: {
react: "17.0.1",
"react-native": "0.64.0",
},
devDependencies: {
react: "17.0.1",
"react-native": "0.64.0",
},
"rnx-kit": kitConfig,
};
rnxKitConfig.__setMockConfig(kitConfig);
fs.__setMockContent(inputManifest);
let manifest = undefined;
fs.__setMockFileWriter((_, content) => {
manifest = JSON.parse(content);
});
const result = makeVigilantCommand({
versions: "0.64,0.65",
loose: false,
write: true,
})("package.json");
expect(result).toBe(0);
expect(consoleErrorSpy).not.toBeCalled();
expect(manifest).toEqual({
...inputManifest,
devDependencies: {
react: "17.0.2",
"react-native": "0.64.3",
},
peerDependencies: {
react: "17.0.2",
"react-native": "0.64.3 || 0.65.2 || ^0.64.2 || ^0.65.0",
},
});
});
test("prefers package-specific React Native versions", () => {
const fixture = `${__dirname}/__fixtures__/config-custom-profiles-only`;
const kitConfig = {
reactNativeVersion: "0.64",
customProfiles: `${fixture}/packageSpecificProfiles.js`,
};
const inputManifest = {
name: "@rnx-kit/dep-check",
version: "1.0.0",
peerDependencies: {
react: "17.0.1",
"react-native": "0.64.0",
},
devDependencies: {
react: "17.0.1",
"react-native": "0.64.0",
},
"rnx-kit": kitConfig,
};
rnxKitConfig.__setMockConfig(kitConfig);
fs.__setMockContent(inputManifest);
let manifest = undefined;
fs.__setMockFileWriter((_, content) => {
manifest = JSON.parse(content);
});
const result = makeVigilantCommand({
versions: "0.64,0.65",
loose: false,
write: true,
})("package.json");
expect(result).toBe(0);
expect(consoleErrorSpy).not.toBeCalled();
expect(manifest).toEqual({
...inputManifest,
devDependencies: {
react: "17.0.2",
"react-native": "0.64.3",
},
peerDependencies: {
react: "17.0.2",
"react-native": "0.64.3 || ^0.64.2",
},
});
});
});