1*46f023faSEvan Bacon/** 2*46f023faSEvan Bacon * Copyright © 2022 650 Industries. 3*46f023faSEvan Bacon * 4*46f023faSEvan Bacon * This source code is licensed under the MIT license found in the 5*46f023faSEvan Bacon * LICENSE file in the root directory of this source tree. 6*46f023faSEvan Bacon */ 7*46f023faSEvan Baconimport path from 'node:path'; 8*46f023faSEvan Bacon 9*46f023faSEvan Baconimport { logMetroErrorAsync } from './metroErrorInterface'; 10*46f023faSEvan Baconimport { getApiRoutesForDirectory } from './router'; 11*46f023faSEvan Baconimport { requireFileContentsWithMetro } from '../getStaticRenderFunctions'; 12*46f023faSEvan Bacon 13*46f023faSEvan Baconconst debug = require('debug')('expo:server-routes') as typeof console.log; 14*46f023faSEvan Bacon 15*46f023faSEvan Baconconst pendingRouteOperations = new Map<string, Promise<string | null>>(); 16*46f023faSEvan Bacon 17*46f023faSEvan Baconexport type ApiRouteOptions = { 18*46f023faSEvan Bacon mode?: string; 19*46f023faSEvan Bacon appDir: string; 20*46f023faSEvan Bacon port?: number; 21*46f023faSEvan Bacon shouldThrow?: boolean; 22*46f023faSEvan Bacon}; 23*46f023faSEvan Bacon 24*46f023faSEvan Bacon// Bundle the API Route with Metro and return the string contents to be evaluated in the server. 25*46f023faSEvan Baconexport async function bundleApiRoute( 26*46f023faSEvan Bacon projectRoot: string, 27*46f023faSEvan Bacon filepath: string, 28*46f023faSEvan Bacon options: ApiRouteOptions 29*46f023faSEvan Bacon): Promise<string | null | undefined> { 30*46f023faSEvan Bacon if (pendingRouteOperations.has(filepath)) { 31*46f023faSEvan Bacon return pendingRouteOperations.get(filepath); 32*46f023faSEvan Bacon } 33*46f023faSEvan Bacon 34*46f023faSEvan Bacon const devServerUrl = `http://localhost:${options.port}`; 35*46f023faSEvan Bacon 36*46f023faSEvan Bacon async function bundleAsync() { 37*46f023faSEvan Bacon try { 38*46f023faSEvan Bacon debug('Check API route:', options.appDir, filepath); 39*46f023faSEvan Bacon 40*46f023faSEvan Bacon const middleware = await requireFileContentsWithMetro(projectRoot, devServerUrl, filepath, { 41*46f023faSEvan Bacon minify: options.mode === 'production', 42*46f023faSEvan Bacon dev: options.mode !== 'production', 43*46f023faSEvan Bacon // Ensure Node.js 44*46f023faSEvan Bacon environment: 'node', 45*46f023faSEvan Bacon }); 46*46f023faSEvan Bacon 47*46f023faSEvan Bacon return middleware; 48*46f023faSEvan Bacon } catch (error: any) { 49*46f023faSEvan Bacon if (error instanceof Error) { 50*46f023faSEvan Bacon await logMetroErrorAsync({ error, projectRoot }); 51*46f023faSEvan Bacon } 52*46f023faSEvan Bacon if (options.shouldThrow) { 53*46f023faSEvan Bacon throw error; 54*46f023faSEvan Bacon } 55*46f023faSEvan Bacon // TODO: improve error handling, maybe have this be a mock function which returns the static error html 56*46f023faSEvan Bacon return null; 57*46f023faSEvan Bacon } finally { 58*46f023faSEvan Bacon // pendingRouteOperations.delete(filepath); 59*46f023faSEvan Bacon } 60*46f023faSEvan Bacon } 61*46f023faSEvan Bacon const route = bundleAsync(); 62*46f023faSEvan Bacon 63*46f023faSEvan Bacon pendingRouteOperations.set(filepath, route); 64*46f023faSEvan Bacon return route; 65*46f023faSEvan Bacon} 66*46f023faSEvan Bacon 67*46f023faSEvan Baconexport async function rebundleApiRoute( 68*46f023faSEvan Bacon projectRoot: string, 69*46f023faSEvan Bacon filepath: string, 70*46f023faSEvan Bacon options: ApiRouteOptions 71*46f023faSEvan Bacon) { 72*46f023faSEvan Bacon pendingRouteOperations.delete(filepath); 73*46f023faSEvan Bacon return bundleApiRoute(projectRoot, filepath, options); 74*46f023faSEvan Bacon} 75*46f023faSEvan Bacon 76*46f023faSEvan Baconexport async function exportAllApiRoutesAsync(projectRoot: string, options: ApiRouteOptions) { 77*46f023faSEvan Bacon const files: Map<string, string> = new Map(); 78*46f023faSEvan Bacon 79*46f023faSEvan Bacon await Promise.all( 80*46f023faSEvan Bacon getApiRoutesForDirectory(options.appDir).map(async (filepath) => { 81*46f023faSEvan Bacon const contents = await bundleApiRoute(projectRoot, filepath, options); 82*46f023faSEvan Bacon files.set(path.relative(options.appDir, filepath.replace(/\.[tj]sx?$/, '.js')), contents!); 83*46f023faSEvan Bacon }) 84*46f023faSEvan Bacon ); 85*46f023faSEvan Bacon 86*46f023faSEvan Bacon return files; 87*46f023faSEvan Bacon} 88