Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 59 additions & 29 deletions packages/app/example/test/config.test.mjs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
// @ts-check
import { equal, match, notEqual, ok } from "node:assert/strict";
import { deepEqual, equal, match, notEqual, ok } from "node:assert/strict";
import * as fs from "node:fs";
import { createRequire } from "node:module";
import * as path from "node:path";
import { test } from "node:test";
import { after, before, describe, it } from "node:test";
import { configureProjects } from "../../scripts/configure-projects.js";
import { readJSONFile } from "../../scripts/helpers.js";

/**
Expand Down Expand Up @@ -45,13 +47,24 @@ function requiresDependency(spec, projectRoot) {
return Object.hasOwn(dependencies, spec);
}

test("react-native config", async (t) => {
describe("react-native config", async () => {
const currentDir = process.cwd();
const loadConfig = await getLoadConfig(currentDir);

const reactNativePath = path.join(currentDir, "node_modules", "react-native");

await t.test("contains Android config", () => {
before(() => {
global.require = createRequire(
new URL("../../scripts/configure-projects.js", import.meta.url)
);
});

after(() => {
// @ts-expect-error reset `require`
global.require = undefined;
});

it("contains Android config", () => {
const sourceDir = path.join(currentDir, "android");
const config = loadConfig();

Expand All @@ -71,33 +84,29 @@ test("react-native config", async (t) => {
equal(config.project.android.packageName, "com.microsoft.reacttestapp");
});

await t.test(
"contains iOS config",
{ skip: process.platform === "win32" },
() => {
const sourceDir = path.join(currentDir, "ios");
const config = loadConfig();

equal(typeof config, "object");
match(config.root, regexp(currentDir));
match(config.reactNativePath, regexp(reactNativePath));
equal(
config.dependencies["react-native-test-app"].name,
"react-native-test-app"
);
notEqual(config.platforms.ios, undefined);
match(config.project.ios.sourceDir, regexp(sourceDir));
it("contains iOS config", { skip: process.platform === "win32" }, () => {
const sourceDir = path.join(currentDir, "ios");
const config = loadConfig();

if (fs.existsSync("ios/Pods")) {
equal(config.project.ios.xcodeProject.name, "Example.xcworkspace");
ok(config.project.ios.xcodeProject.isWorkspace);
} else {
equal(config.project.ios.xcodeProject, null);
}
equal(typeof config, "object");
match(config.root, regexp(currentDir));
match(config.reactNativePath, regexp(reactNativePath));
equal(
config.dependencies["react-native-test-app"].name,
"react-native-test-app"
);
notEqual(config.platforms.ios, undefined);
match(config.project.ios.sourceDir, regexp(sourceDir));

if (fs.existsSync("ios/Pods")) {
equal(config.project.ios.xcodeProject.name, "Example.xcworkspace");
ok(config.project.ios.xcodeProject.isWorkspace);
} else {
equal(config.project.ios.xcodeProject, null);
}
);
});

await t.test(
it(
"contains macOS config",
{
skip:
Expand Down Expand Up @@ -127,7 +136,7 @@ test("react-native config", async (t) => {
}
);

await t.test(
it(
"contains Windows config",
{
skip:
Expand Down Expand Up @@ -165,3 +174,24 @@ test("react-native config", async (t) => {
}
);
});

describe("configureProjects()", () => {
before(() => {
global.require = createRequire(
new URL("../../scripts/configure-projects.js", import.meta.url)
);
});

after(() => {
// @ts-expect-error reset `require`
global.require = undefined;
});

it("returns externally provided platform config", () => {
deepEqual(configureProjects({ web: true }), {
web: {
"@rnx-kit/react-native-template-web": true,
},
});
});
});
25 changes: 14 additions & 11 deletions packages/app/scripts/configure-projects.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ const { configure: configureIOS } = require("../ios/template.config.mjs");
const {
configure: configureWindows,
} = require("../windows/template.config.mjs");
const { findNearest } = require("./helpers");
const { findNearest } = require("./helpers.js");
const { loadPlatformTemplates } = require("./template.mjs");

/**
* Finds `react-native.config.[ts,mjs,cjs,js]`.
Expand Down Expand Up @@ -76,24 +77,26 @@ function findReactNativeConfig(fs = nodefs) {
}

/**
* @param {ProjectConfig} configuration
* @param {ProjectConfig} projectConfig
* @returns {Partial<ProjectParams>}
*/
function configureProjects({ android, ios, windows }, fs = nodefs) {
function configureProjects(projectConfig, fs = nodefs) {
const reactNativeConfig = findReactNativeConfig(fs);

/** @type {Partial<ProjectParams>} */
const config = {};

const projectRoot = path.dirname(reactNativeConfig);
if (android) {
config.android = configureAndroid(projectRoot, android, fs);
}
if (ios) {
config.ios = configureIOS(projectRoot, ios, fs);
}
if (windows) {
config.windows = configureWindows(projectRoot, windows, fs);
const params = {
packagePath: projectRoot,
testAppPath: path.dirname(__dirname),
};
const templates = loadPlatformTemplates(params, fs);
for (const [platform, { configure }] of Object.entries(templates)) {
const platformConfig = projectConfig[platform];
if (platformConfig) {
config[platform] = configure(projectRoot, platformConfig, fs);
}
}

return config;
Expand Down
75 changes: 9 additions & 66 deletions packages/app/scripts/configure.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@
* Configuration,
* ConfigureParams,
* FileCopy,
* Manifest,
* Platform,
* PlatformConfiguration,
* PlatformPackage,
* } from "./types.js";
*/
import { loadContext } from "@rnx-kit/tools-react-native/context";
import * as nodefs from "node:fs";
import { createRequire } from "node:module";
import * as path from "node:path";
Expand All @@ -21,7 +19,6 @@ import semverSatisfies from "semver/functions/satisfies.js";
import {
getPackageVersion,
isMain,
memo,
readJSONFile,
readTextFile,
} from "./helpers.js";
Expand All @@ -30,6 +27,8 @@ import {
bundleConfig,
copyFrom,
findGitIgnore,
loadPlatformTemplates,
readManifest,
serialize,
} from "./template.mjs";
import * as colors from "./utils/colors.mjs";
Expand All @@ -48,11 +47,6 @@ function mergeObjects(lhs, rhs) {
: sortByKeys(rhs);
}

/** @type {() => Required<Manifest>} */
const readManifest = memo(() =>
readJSONFile(new URL("../package.json", import.meta.url))
);

/**
* Prints an error message to the console.
* @param {string} message
Expand Down Expand Up @@ -99,17 +93,6 @@ export function mergeConfig(lhs, rhs) {
};
}

/**
* @param {string} root
* @param {string} subpath
* @returns {string | false}
*/
function resolvePath(root, subpath) {
const resolved = path.resolve(root, subpath);
const rel = path.relative(root, resolved);
return !path.isAbsolute(rel) && !rel.startsWith("..") && resolved;
}

/**
* Sort the keys in specified object.
* @param {Record<string, unknown>} obj
Expand Down Expand Up @@ -223,51 +206,6 @@ export function reactNativeConfig({ name, testAppPath }, fs = nodefs) {
return readTextFile(config, fs).replaceAll("Example", name);
}

/**
* @param {ConfigureParams} params
* @param {NodeJS.Require} require
* @param {PlatformConfiguration} configuration
* @returns {PlatformConfiguration}
*/
function loadPlatformTemplates(params, require, configuration, fs = nodefs) {
const { packagePath, testAppPath } = params;
const { defaultPlatformPackages } = readManifest();
const platformPackages = { ...defaultPlatformPackages };

try {
const config = loadContext(packagePath);
for (const [, { root }] of Object.entries(config.dependencies)) {
const manifest = readJSONFile(path.join(root, "package.json"), fs);
const { reactNativeTemplateConfig: config } = manifest;
if (config && typeof config === "object" && !Array.isArray(config)) {
console.log(
"Loading template config:",
path.relative(packagePath, root)
);
for (const [key, { template, ...rest }] of Object.entries(config)) {
const resolved = resolvePath(root, template);
if (resolved) {
platformPackages[key] = { ...rest, template: resolved };
}
}
}
}
} catch (_) {
// If this was executed outside any projects, `@react-native-community/cli`
// will not be available and error here.
}

for (const [platform, { template }] of Object.entries(platformPackages)) {
const templatePath = template.startsWith(".")
? path.resolve(testAppPath, template)
: template;
const { getTemplate } = require(templatePath);
configuration[platform] = getTemplate(params, fs);
}

return configuration;
}

/**
* Returns a {@link Configuration} object for specified platform.
*
Expand Down Expand Up @@ -304,7 +242,7 @@ export const getConfig = (() => {
path.dirname(require.resolve("react-native/template/package.json"))
);

configuration = loadPlatformTemplates(params, require, {
configuration = {
common: {
files: {
".gitignore": findGitIgnore(path.join(testAppPath, "example"), fs),
Expand Down Expand Up @@ -337,7 +275,12 @@ export const getConfig = (() => {
},
dependencies: {},
},
});
};

const templates = loadPlatformTemplates(params, fs);
for (const [platform, { getTemplate }] of Object.entries(templates)) {
configuration[platform] = getTemplate(params, fs);
}
}
return configuration[platform];
};
Expand Down
Loading
Loading