From 52b61218458044e8386a99fe90c371c0b86bf3ef Mon Sep 17 00:00:00 2001 From: uditDewan Date: Wed, 14 Oct 2026 17:58:51 -0400 Subject: [PATCH] chore(scripts): validate react-native export conditions in constraints 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 --- .changeset/tester-react-native-export.md | 5 ++++ apps/fluent-tester/package.json | 2 +- scripts/src/tasks/constrain.ts | 31 ++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 .changeset/tester-react-native-export.md diff --git a/.changeset/tester-react-native-export.md b/.changeset/tester-react-native-export.md new file mode 100644 index 00000000000..0649ab190f4 --- /dev/null +++ b/.changeset/tester-react-native-export.md @@ -0,0 +1,5 @@ +--- +'@fluentui-react-native/tester': patch +--- + +Point the `react-native` export condition at `./src/index.tsx`; it referenced `./src/index.ts`, which does not exist. diff --git a/apps/fluent-tester/package.json b/apps/fluent-tester/package.json index fc4bf900dab..5e0905636bf 100644 --- a/apps/fluent-tester/package.json +++ b/apps/fluent-tester/package.json @@ -16,7 +16,7 @@ "exports": { ".": { "types": "./lib/index.d.ts", - "react-native": "./src/index.ts", + "react-native": "./src/index.tsx", "import": "./lib/index.js", "default": "./src/index.tsx" } diff --git a/scripts/src/tasks/constrain.ts b/scripts/src/tasks/constrain.ts index 3dfc21250ad..d852827c11a 100644 --- a/scripts/src/tasks/constrain.ts +++ b/scripts/src/tasks/constrain.ts @@ -45,6 +45,7 @@ export function constrain(workspace: Yarn.Constraints.Workspace): void { } assertDeprecations(ctx); useCatalogs(ctx); + ensureReactNativeExports(ctx); } export function constrainRoot(workspace: Yarn.Constraints.Workspace): void { @@ -81,6 +82,36 @@ function useCatalogs(ctx: PackageContext): void { } } +/** + * Metro resolves the "react-native" export condition to TypeScript source. Ensure published packages point that condition + * at a file that exists, and that React Native libraries provide it for every export. + */ +function ensureReactNativeExports(ctx: PackageContext): void { + const { exports, peerDependencies, private: isPrivate } = ctx.manifest; + if (isPrivate || !exports || typeof exports !== 'object' || Array.isArray(exports)) { + return; + } + const requireCondition = Boolean(peerDependencies?.['react-native']); + for (const [subpath, conditions] of Object.entries(exports)) { + if (!conditions || typeof conditions !== 'object' || Array.isArray(conditions)) { + continue; + } + const target = (conditions as Record)['react-native']; + if (typeof target === 'string') { + if (!repoCtx.hasFile(ctx.root, target)) { + const swapped = target.endsWith('.tsx') ? target.slice(0, -1) : target.endsWith('.ts') ? `${target}x` : undefined; + if (swapped && repoCtx.hasFile(ctx.root, swapped)) { + ctx.enforce(['exports', subpath, 'react-native'], swapped); + } else { + ctx.error(`exports["${subpath}"]["react-native"] points to ${target}, which does not exist`); + } + } + } else if (requireCondition) { + ctx.error(`exports["${subpath}"] is missing a "react-native" condition pointing to its TypeScript source`); + } + } +} + function assertDeprecations(ctx: PackageContext): void { // placeholder to add deprecated packages or parts of package.json to remove them ctx.enforce('scripts.lint-package', undefined);