Skip to content

Commit

Permalink
fix(jest-preset): support ESM config files (#3433)
Browse files Browse the repository at this point in the history
  • Loading branch information
tido64 authored Dec 3, 2024
1 parent 42fa275 commit cc2556c
Show file tree
Hide file tree
Showing 13 changed files with 141 additions and 128 deletions.
5 changes: 5 additions & 0 deletions .changeset/short-eyes-fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rnx-kit/tools-react-native": patch
---

Decouple `getAvailablePlatforms()` from `@react-native-community/cli`
5 changes: 5 additions & 0 deletions .changeset/strong-cups-sparkle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rnx-kit/jest-preset": patch
---

Support ESM config files
5 changes: 0 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -147,11 +147,6 @@
"tsx"
]
},
"packages/eslint-plugin": {
"ignoreDependencies": [
"eslint-plugin-react-hooks"
]
},
"packages/jest-preset": {
"ignoreDependencies": [
"react",
Expand Down
3 changes: 1 addition & 2 deletions packages/eslint-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@
},
"devDependencies": {
"@rnx-kit/eslint-config": "*",
"@rnx-kit/jest-preset": "*",
"@rnx-kit/scripts": "*",
"@rnx-kit/tsconfig": "*",
"@types/eslint": "^9.0.0",
Expand All @@ -73,6 +72,6 @@
"node": ">=16.17"
},
"jest": {
"preset": "@rnx-kit/jest-preset/private"
"preset": "../jest-preset/private/jest-preset.js"
}
}
1 change: 1 addition & 0 deletions packages/jest-preset/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"@babel/preset-typescript": "^7.0.0",
"@rnx-kit/tools-react-native": "^2.0.2",
"find-up": "^5.0.0"
},
"peerDependencies": {
Expand Down
77 changes: 29 additions & 48 deletions packages/jest-preset/src/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const findUp = require("find-up");
const path = require("path");
const path = require("node:path");

/**
* @typedef {import("@jest/types").Config.HasteConfig} HasteConfig
Expand All @@ -9,24 +9,6 @@ const path = require("path");
* @typedef {[string | undefined, string | undefined]} PlatformPath
*/

/**
* Resolve the path to a dependency given a chain of dependencies leading up to
* it.
*
* Note: This is a copy of the function in `@rnx-kit/tools-node` to avoid
* circular dependency.
*
* @param {string[]} chain Chain of dependencies leading up to the target dependency.
* @param {string=} startDir Optional starting directory for the search. If not given, the current directory is used.
* @returns Path to the final dependency's directory.
*/
function resolveDependencyChain(chain, startDir = process.cwd()) {
return chain.reduce((startDir, module) => {
const p = require.resolve(`${module}/package.json`, { paths: [startDir] });
return path.dirname(p);
}, startDir);
}

/**
* Returns the current package directory.
* @returns {string | undefined}
Expand All @@ -47,15 +29,26 @@ function getReactNativePlatformPath(rootDir = getPackageDirectory()) {
throw new Error("Failed to resolve current package root");
}

const fs = require("fs");
// This should only throw because we haven't built `tools-react-native` yet,
// which can happen if we're running repo-wide tools (like Knip).
const readReactNativeConfig = (() => {
try {
const {
readReactNativeConfig,
} = require("@rnx-kit/tools-react-native/context");
return readReactNativeConfig;
} catch (_) {
return () => undefined;
}
})();

const rnConfigPath = path.join(rootDir, "react-native.config.js");
if (!fs.existsSync(rnConfigPath)) {
const config = readReactNativeConfig(rootDir);
if (!config) {
return [undefined, undefined];
}

const { platforms, reactNativePath } = require(rnConfigPath);
if (reactNativePath) {
const { platforms, reactNativePath } = config;
if (reactNativePath && typeof reactNativePath === "string") {
const resolvedPath = /^\.?\.[/\\]/.test(reactNativePath)
? path.resolve(rootDir, reactNativePath)
: path.dirname(
Expand All @@ -68,15 +61,16 @@ function getReactNativePlatformPath(rootDir = getPackageDirectory()) {
}
}

if (platforms) {
const names = Object.keys(platforms).filter(
(name) => typeof platforms[name].npmPackageName === "string"
if (platforms && typeof platforms === "object") {
const names = Object.entries(platforms).filter(
([, info]) => typeof info.npmPackageName === "string"
);
if (names.length > 1) {
console.warn(`Multiple platforms found; picking the first one: ${names}`);
const found = names.map(([key]) => key).join(", ");
console.warn(`Multiple platforms found; picking the first one: ${found}`);
}

return [names[0], rootDir];
return [names[0][0], rootDir];
}

console.warn("No platforms found");
Expand All @@ -96,33 +90,20 @@ function getTargetPlatform(defaultPlatform, searchPaths) {
return getReactNativePlatformPath();
}

/** @type {(config?: {projectRoot?: string; selectedPlatform?: string; }) => CLIConfig} */
const loadConfig = (() => {
const rnCliPath = resolveDependencyChain([
"react-native",
"@react-native-community/cli",
]);
return (
require(rnCliPath).loadConfig ||
require(`${rnCliPath}/build/tools/config`).default
);
})();

// .length on a function returns the number of formal parameters.
// fixes https://github.com/react-native-community/cli/pull/2379 changing the number of parameters.
const platforms =
loadConfig.length == 1 ? loadConfig({}).platforms : loadConfig().platforms;
const {
getAvailablePlatforms,
} = require("@rnx-kit/tools-react-native/platform");

const targetPlatformConfig = platforms[defaultPlatform];
if (!targetPlatformConfig) {
const platforms = getAvailablePlatforms(searchPaths.paths[0]);
const npmPackageName = platforms[defaultPlatform];
if (typeof npmPackageName !== "string") {
const availablePlatforms = Object.keys(platforms).join(", ");
throw new Error(
`'${defaultPlatform}' was not found among available platforms: ${availablePlatforms}`
);
}

// `npmPackageName` is unset if target platform is in core.
const { npmPackageName } = targetPlatformConfig;
return [
defaultPlatform,
npmPackageName
Expand Down
1 change: 1 addition & 0 deletions packages/tools-react-native/context.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export {
loadContext,
loadContextAsync,
readReactNativeConfig,
resolveCommunityCLI,
} from "./lib/context";
9 changes: 5 additions & 4 deletions packages/tools-react-native/src/cache.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import type { Config } from "@react-native-community/cli-types";
import { findUp } from "@rnx-kit/tools-node/path";
import * as crypto from "crypto";
import * as nodefs from "fs";
import * as path from "path";
import * as crypto from "node:crypto";
import * as nodefs from "node:fs";
import * as path from "node:path";
import { REACT_NATIVE_CONFIG_FILES } from "./context";

const HASH_ALGO = "sha256";
const UTF8 = { encoding: "utf-8" as const };
Expand Down Expand Up @@ -45,7 +46,7 @@ function updateHash(
export function getCurrentState(projectRoot: string): string {
const sha2 = crypto.createHash(HASH_ALGO);

const configFiles = ["package.json", "react-native.config.js"];
const configFiles = ["package.json", ...REACT_NATIVE_CONFIG_FILES];
updateHash(sha2, configFiles, projectRoot, "all");

const lockfiles = [
Expand Down
42 changes: 42 additions & 0 deletions packages/tools-react-native/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import {
findPackageDependencyDir,
readPackage,
} from "@rnx-kit/tools-node/package";
import { spawnSync } from "node:child_process";
import * as fs from "node:fs";
import * as path from "node:path";
import {
getCurrentState,
getSavedState,
Expand All @@ -15,6 +18,13 @@ import {
// `react-native`. Consumers have to take a direct dependency on CLI instead.
const RN_CLI_DECOUPLED = 76;

export const REACT_NATIVE_CONFIG_FILES = [
"react-native.config.ts",
"react-native.config.mjs",
"react-native.config.cjs",
"react-native.config.js",
];

function toNumber(version: string): number {
const [major, minor = 0] = version.split(".");
return Number(major) * 1000 + Number(minor);
Expand Down Expand Up @@ -115,3 +125,35 @@ export async function loadContextAsync(
saveConfigToCache(projectRoot, state, config);
return config;
}

export function readReactNativeConfig(
packageDir: string,
cwd = process.cwd()
): Record<string, unknown> | undefined {
for (const configFile of REACT_NATIVE_CONFIG_FILES) {
const configPath = path.join(packageDir, configFile);
if (fs.existsSync(configPath)) {
const url =
process.platform === "win32"
? `file://${configPath.replaceAll("\\", "/")}`
: configPath;
const args = [
"--no-warnings",
"--eval",
`import("${url}").then((config) => console.log(JSON.stringify(config.default ?? config)));`,
];

const { stderr, stdout } = spawnSync(process.argv0, args, { cwd });

const errors = stderr?.toString().trim();
if (errors) {
console.error(`${configPath}: ${errors}`);
}

const json = stdout?.toString().trim();
return json ? JSON.parse(json) : undefined;
}
}

return undefined;
}
7 changes: 6 additions & 1 deletion packages/tools-react-native/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
export { loadContext, loadContextAsync, resolveCommunityCLI } from "./context";
export {
loadContext,
loadContextAsync,
readReactNativeConfig,
resolveCommunityCLI,
} from "./context";
export {
findMetroPath,
getMetroVersion,
Expand Down
Loading

0 comments on commit cc2556c

Please sign in to comment.