UNPKG

expo-updates

Version:

Fetches and manages remotely-hosted assets and updates to your app's JS bundle.

151 lines (132 loc) 5.23 kB
const { loadAsync } = require('@expo/metro-config'); const fs = require('fs'); const Server = require('metro/src/Server'); const path = require('path'); const { v4: uuidv4 } = require('uuid'); const filterPlatformAssetScales = require('./filterPlatformAssetScales'); (async function() { const platform = process.argv[2]; const possibleProjectRoot = process.argv[3]; const destinationDir = process.argv[4]; const entryFile = process.env.ENTRY_FILE || 'index.js'; // Remove projectRoot validation when we no longer support React Native <= 62 let projectRoot; if (fs.existsSync(path.join(possibleProjectRoot, entryFile))) { projectRoot = possibleProjectRoot; } else if (fs.existsSync(path.join(possibleProjectRoot, '..', entryFile))) { projectRoot = path.resolve(possibleProjectRoot, '..'); } let metroConfig; try { metroConfig = await loadAsync(projectRoot); } catch (e) { let message = `Error loading Metro config and Expo app config: ${e.message}\n\nMake sure your project is configured properly and your app.json / app.config.js is valid.`; if (process.env.EAS_BUILD) { message += '\nIf you are using environment variables in app.config.js, verify that you have set them in your EAS Build profile configuration or secrets.'; } throw new Error(message); } let assets; try { assets = await fetchAssetManifestAsync(platform, projectRoot, entryFile, metroConfig); } catch (e) { throw new Error( "Error loading assets JSON from Metro. Ensure you've followed all expo-updates installation steps correctly. " + e.message ); } const manifest = { id: uuidv4(), commitTime: new Date().getTime(), assets: [], }; assets.forEach(function(asset) { if (!asset.fileHashes) { throw new Error( 'The hashAssetFiles Metro plugin is not configured. You need to add a metro.config.js to your project that configures Metro to use this plugin. See https://github.com/expo/expo/blob/master/packages/expo-updates/README.md#metroconfigjs for an example.' ); } filterPlatformAssetScales(platform, asset.scales).forEach(function(scale, index) { const assetInfoForManifest = { name: asset.name, type: asset.type, scale, packagerHash: asset.fileHashes[index], subdirectory: asset.httpServerLocation, }; if (platform === 'ios') { assetInfoForManifest.nsBundleDir = getIosDestinationDir(asset); assetInfoForManifest.nsBundleFilename = scale === 1 ? asset.name : asset.name + '@' + scale + 'x'; } else if (platform === 'android') { assetInfoForManifest.scales = asset.scales; assetInfoForManifest.resourcesFilename = getAndroidResourceIdentifier(asset); assetInfoForManifest.resourcesFolder = getAndroidResourceFolderName(asset); } manifest.assets.push(assetInfoForManifest); }); }); fs.writeFileSync(path.join(destinationDir, 'app.manifest'), JSON.stringify(manifest)); })().catch(e => { // Wrap in regex to make it easier for log parsers (like `@expo/xcpretty`) to find this error. e.message = `@build-script-error-begin\n${e.message}\n@build-script-error-end\n`; console.error(e); process.exit(1); }); // See https://developer.android.com/guide/topics/resources/drawable-resource.html const drawableFileTypes = new Set(['gif', 'jpeg', 'jpg', 'png', 'svg', 'webp', 'xml']); function getAndroidResourceFolderName(asset) { return drawableFileTypes.has(asset.type) ? 'drawable' : 'raw'; } // copied from react-native/Libraries/Image/assetPathUtils.js function getAndroidResourceIdentifier(asset) { const folderPath = getBasePath(asset); return (folderPath + '/' + asset.name) .toLowerCase() .replace(/\//g, '_') // Encode folder structure in file name .replace(/([^a-z0-9_])/g, '') // Remove illegal chars .replace(/^assets_/, ''); // Remove "assets_" prefix } function getIosDestinationDir(asset) { // react-native-cli replaces `..` with `_` when embedding assets in the iOS app bundle // https://github.com/react-native-community/cli/blob/0a93be1a42ed1fb05bb0ebf3b82d58b2dd920614/packages/cli/src/commands/bundle/getAssetDestPathIOS.ts return getBasePath(asset).replace(/\.\.\//g, '_'); } // copied from react-native/Libraries/Image/assetPathUtils.js function getBasePath(asset) { let basePath = asset.httpServerLocation; if (basePath[0] === '/') { basePath = basePath.substr(1); } return basePath; } // Spawn a Metro server to get the asset manifest async function fetchAssetManifestAsync(platform, projectRoot, entryFile, metroConfig) { // Project-level babel config does not load unless we change to the // projectRoot before instantiating the server process.chdir(projectRoot); const server = new Server(metroConfig); const requestOpts = { entryFile, dev: false, minify: false, platform, }; let assetManifest; let error; try { assetManifest = await server.getAssets({ ...Server.DEFAULT_BUNDLE_OPTIONS, ...requestOpts, }); } catch (e) { error = e; } finally { server.end(); } if (error) { throw error; } return assetManifest; }