Skip to content

Commit 5681dd6

Browse files
authored
chore(scripts): validate react-native export conditions in constraints (#4370)
Add a yarn constraint for published packages: - each exports entry's "react-native" condition must point to a file that exists (auto-fixed when only the .ts/.tsx extension is wrong) - packages that peer-depend on react-native must provide a "react-native" condition for every export This catches @fluentui-react-native/tester, whose "react-native" condition pointed at ./src/index.ts while the entry is ./src/index.tsx; fixed via yarn constraints --fix. Part of #3936
1 parent 8fb422f commit 5681dd6

3 files changed

Lines changed: 37 additions & 1 deletion

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@fluentui-react-native/tester': patch
3+
---
4+
5+
Point the `react-native` export condition at `./src/index.tsx`; it referenced `./src/index.ts`, which does not exist.

‎apps/fluent-tester/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"exports": {
1717
".": {
1818
"types": "./lib/index.d.ts",
19-
"react-native": "./src/index.ts",
19+
"react-native": "./src/index.tsx",
2020
"import": "./lib/index.js",
2121
"default": "./src/index.tsx"
2222
}

‎scripts/src/tasks/constrain.ts‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ export function constrain(workspace: Yarn.Constraints.Workspace): void {
8282
enforceBuildInfoLocation(ctx);
8383
assertDeprecations(ctx);
8484
useCatalogs(ctx);
85+
ensureReactNativeExports(ctx);
8586
}
8687

8788
export function constrainRoot(workspace: Yarn.Constraints.Workspace): void {
@@ -118,6 +119,36 @@ function useCatalogs(ctx: PackageContext): void {
118119
}
119120
}
120121

122+
/**
123+
* Metro resolves the "react-native" export condition to TypeScript source. Ensure published packages point that condition
124+
* at a file that exists, and that React Native libraries provide it for every export.
125+
*/
126+
function ensureReactNativeExports(ctx: PackageContext): void {
127+
const { exports, peerDependencies, private: isPrivate } = ctx.manifest;
128+
if (isPrivate || !exports || typeof exports !== 'object' || Array.isArray(exports)) {
129+
return;
130+
}
131+
const requireCondition = Boolean(peerDependencies?.['react-native']);
132+
for (const [subpath, conditions] of Object.entries(exports)) {
133+
if (!conditions || typeof conditions !== 'object' || Array.isArray(conditions)) {
134+
continue;
135+
}
136+
const target = (conditions as Record<string, unknown>)['react-native'];
137+
if (typeof target === 'string') {
138+
if (!repoCtx.hasFile(ctx.root, target)) {
139+
const swapped = target.endsWith('.tsx') ? target.slice(0, -1) : target.endsWith('.ts') ? `${target}x` : undefined;
140+
if (swapped && repoCtx.hasFile(ctx.root, swapped)) {
141+
ctx.enforce(['exports', subpath, 'react-native'], swapped);
142+
} else {
143+
ctx.error(`exports["${subpath}"]["react-native"] points to ${target}, which does not exist`);
144+
}
145+
}
146+
} else if (requireCondition) {
147+
ctx.error(`exports["${subpath}"] is missing a "react-native" condition pointing to its TypeScript source`);
148+
}
149+
}
150+
}
151+
121152
function assertDeprecations(ctx: PackageContext): void {
122153
// placeholder to add deprecated packages or parts of package.json to remove them
123154
ctx.enforce('scripts.lint-package', undefined);

0 commit comments

Comments
 (0)