77 getAvailablePlatforms ,
88 platformExtensions ,
99} from "@rnx-kit/tools-react-native" ;
10+ import path from "node:path" ;
1011import type ts from "typescript" ;
11- import type { BuildContext , ParsedFileName , PlatformInfo } from "./types" ;
12+ import type { BuildContext , ParsedFileReference , PlatformInfo } from "./types" ;
1213
1314// quick helper for converting a value to an array
1415function coerceArray < T > ( value : T | T [ ] | undefined ) : T [ ] {
@@ -18,22 +19,29 @@ function coerceArray<T>(value: T | T[] | undefined): T[] {
1819 return Array . isArray ( value ) ? value : [ value ] ;
1920}
2021
22+ // a list of built-in platforms to use as a fallback
23+ const fallbackPackages : Record < string , string > = {
24+ android : "react-native" ,
25+ ios : "react-native" ,
26+ macos : "react-native-macos" ,
27+ win32 : "@office-iss/react-native-win32" ,
28+ windows : "react-native-windows" ,
29+ visionos : "@callstack/react-native-visionos" ,
30+ } ;
31+
2132/**
2233 * @returns a PlatformInfo with a package name and the module suffixes set
2334 */
2435function createPlatformInfo ( platform : string , npmName ?: string ) : PlatformInfo {
25- // a list of built-in platforms to use as a fallback
26- const fallbackPackages : Record < string , string > = {
27- android : "react-native" ,
28- ios : "react-native" ,
29- macos : "react-native-macos" ,
30- win32 : "@office-iss/react-native-win32" ,
31- windows : "react-native-windows" ,
32- visionos : "@callstack/react-native-visionos" ,
33- } ;
36+ const pkgName = npmName || fallbackPackages [ platform ] ;
37+ if ( ! pkgName ) {
38+ throw new Error (
39+ `Unable to determine package name for platform ${ platform } `
40+ ) ;
41+ }
3442 return {
3543 name : platform ,
36- pkgName : npmName || fallbackPackages [ platform ] || "" ,
44+ pkgName : npmName || fallbackPackages [ platform ] ,
3745 suffixes : platformExtensions ( platform )
3846 . concat ( "" )
3947 . map ( ( s ) => `.${ s } ` ) ,
@@ -73,7 +81,7 @@ export function platformsFromKitConfig(
7381 * @param platformOverride override platform detection with a specific set of platforms
7482 * @returns a map of platform name to PlatformInfo
7583 */
76- export function loadPkgPlatformInfo (
84+ export function loadPackagePlatformInfo (
7785 pkgRoot : string ,
7886 manifest : PackageManifest ,
7987 platformOverride ?: string [ ]
@@ -96,50 +104,74 @@ export function loadPkgPlatformInfo(
96104 return result ;
97105}
98106
107+ export function parseFileReference (
108+ file : string ,
109+ extensions : Set < string > ,
110+ ignoreSuffix ?: boolean
111+ ) : ParsedFileReference {
112+ let ext = path . extname ( file ) ;
113+ let suffix = "" ;
114+ let base = file ;
115+
116+ if ( extensions . has ( ext ) ) {
117+ if ( ext === ".ts" || ext === ".tsx" ) {
118+ const dtsExt = ".d" + ext ;
119+ if ( extensions . has ( dtsExt ) && file . endsWith ( dtsExt ) ) {
120+ ext = dtsExt ;
121+ }
122+ }
123+ base = base . slice ( 0 , - ext . length ) ;
124+ suffix = path . extname ( base ) ;
125+ } else {
126+ suffix = ext ;
127+ ext = "" ;
128+ }
129+
130+ if ( suffix && ! ignoreSuffix ) {
131+ base = base . slice ( 0 , - suffix . length ) ;
132+ } else {
133+ suffix = "" ;
134+ }
135+
136+ return { base, ext, suffix } ;
137+ }
138+
139+ /**
140+ * Standard source file extensions. The reason this is a discrete list rather than allowing all extensions
141+ * is that the references being parsed can be in ./folder/file.js or ./folder/file format, depending on how they
142+ * are declared. We want to parse both and don't want to accidentally treat ./folder/file.types as having an
143+ * extension of .types
144+ */
145+ const sourceExtensions = new Set ( [
146+ ".js" ,
147+ ".jsx" ,
148+ ".cjs" ,
149+ ".mjs" ,
150+ ".ts" ,
151+ ".tsx" ,
152+ ".d.ts" ,
153+ ".d.tsx" ,
154+ ".json" ,
155+ ] ) ;
156+
99157/**
100158 * Take a file path and return the base file name, the platform extension if one exists, and the file extension.
101159 *
102- * @param file file path to parse, can be a source file or a module reference
160+ * @param file file path to parse, can be a source file (./src/foo.js) or a module style reference (./src/foo)
103161 * @param ignoreSuffix don't split out the module suffix, leaving it attached to the base
104162 * @returns an object with the base file name, the platform extension if one exists, and the file extension
105163 */
106- export function parseSourceFileDetails (
164+ export function parseSourceFileReference (
107165 file : string ,
108166 ignoreSuffix ?: boolean
109- ) : ParsedFileName {
110- // valid last extensions, will manually check for .d.ts and .d.tsx
111- const validExtensions = [ ".js" , ".jsx" , ".ts" , ".tsx" , ".json" ] ;
112- const match = / ^ ( .* ?) ( \. [ ^ . / \\ ] + ) ? ( \. [ ^ . / \\ ] + ) ? ( \. [ ^ . / \\ ] + ) ? $ /
113- . exec ( file )
114- ?. slice ( 1 )
115- . filter ( ( s ) => s ) ;
116-
117- const result : ParsedFileName = { base : file } ;
118- if ( match && match . length > 1 ) {
119- if ( validExtensions . includes ( match [ match . length - 1 ] ) ) {
120- result . ext = match . pop ( ) ;
121- if (
122- match . length > 1 &&
123- match [ match . length - 1 ] === ".d" &&
124- result . ext ?. startsWith ( ".ts" )
125- ) {
126- result . ext = match . pop ( ) + result . ext ;
127- }
128- }
129- // if there is an extra term treat it as the suffix
130- if ( match . length > 1 && ! ignoreSuffix ) {
131- result . suffix = match . pop ( ) ;
132- }
133- result . base = match . join ( "" ) ;
134- }
135- return result ;
167+ ) : ParsedFileReference {
168+ return parseFileReference ( file , sourceExtensions , ignoreSuffix ) ;
136169}
137170
138- export type FoundSuffixes = Set < string > ;
139171export type FileEntry = {
140172 file : string ;
141173 suffix ?: string ;
142- allSuffixes : FoundSuffixes ;
174+ allSuffixes : Set < string > ;
143175 built ?: boolean ;
144176} ;
145177
@@ -162,14 +194,14 @@ export function isBestMatch(entry: FileEntry, suffixes: string[]): boolean {
162194 * @returns a match array of FileEntry structures
163195 */
164196function processFileList ( files : string [ ] ) : FileEntry [ ] {
165- const lookup = new Map < string , FoundSuffixes > ( ) ;
197+ const lookup = new Map < string , Set < string > > ( ) ;
166198
167199 const ensureSuffixes = ( base : string ) => {
168200 return lookup . get ( base ) || lookup . set ( base , new Set < string > ( ) ) . get ( base ) ! ;
169201 } ;
170202
171203 return files . map ( ( file ) => {
172- const { base, suffix } = parseSourceFileDetails ( file ) ;
204+ const { base, suffix } = parseSourceFileReference ( file ) ;
173205 const allSuffixes = ensureSuffixes ( base ) ;
174206 if ( suffix ) {
175207 allSuffixes . add ( suffix ) ;
@@ -181,31 +213,31 @@ function processFileList(files: string[]): FileEntry[] {
181213/**
182214 * Given the parsed file entries, go through adding the files to the task based on the platform.
183215 * @param files processed file entry list
184- * @param defaultTask is this the task which should build any unbuilt files
216+ * @param isDefaultTask is this the task which should build any unbuilt files
185217 * @param context task to add files to
186218 */
187219function addFilesToContext (
188220 files : FileEntry [ ] ,
189- defaultTask : boolean ,
221+ isDefaultTask : boolean ,
190222 context : BuildContext
191223) {
192224 const fileNames : string [ ] = [ ] ;
193225
194- const { suffixes } = context . platform || { } ;
226+ const { suffixes = [ ] } = context . platform || { } ;
195227
196228 // if we are in checkOnly mode then task.build will be null and we will fall back to the check array
197229 const pushToBuild = ( file : string , checkOnly : boolean ) => {
198230 if ( context . build && ! checkOnly ) {
199231 context . build . push ( file ) ;
200232 } else {
201- context . check ! . push ( file ) ;
233+ context . check ? .push ( file ) ;
202234 }
203235 fileNames . push ( file ) ;
204236 } ;
205237
206238 for ( const entry of files ) {
207- const bestMatch = isBestMatch ( entry , suffixes ! ) ;
208- if ( ! entry . built && ( bestMatch || defaultTask ) ) {
239+ const bestMatch = isBestMatch ( entry , suffixes ) ;
240+ if ( ! entry . built && ( bestMatch || isDefaultTask ) ) {
209241 pushToBuild ( entry . file , false ) ;
210242 entry . built = true ;
211243 } else if ( bestMatch ) {
@@ -239,7 +271,7 @@ export function multiplexForPlatforms(
239271 const checkOnly = Boolean ( cmdLine . options . noEmit ) ;
240272 // no platforms then we have nothing to do
241273 if ( ! platforms || platforms . length === 0 ) {
242- context . platform = platforms ?. [ 0 ] ;
274+ context . platform = undefined ;
243275 context . check = checkOnly ? cmdLine . fileNames : [ ] ;
244276 context . build = checkOnly ? [ ] : cmdLine . fileNames ;
245277 return [ context ] ;
0 commit comments