You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
358 lines
12 KiB
358 lines
12 KiB
"use strict";
|
|
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
exports.exportAssetsAsync = exportAssetsAsync;
|
|
exports.publishAssetsAsync = publishAssetsAsync;
|
|
exports.resolveGoogleServicesFile = resolveGoogleServicesFile;
|
|
exports.resolveManifestAssets = resolveManifestAssets;
|
|
function _assert() {
|
|
const data = _interopRequireDefault(require("assert"));
|
|
_assert = function () {
|
|
return data;
|
|
};
|
|
return data;
|
|
}
|
|
function _formData() {
|
|
const data = _interopRequireDefault(require("form-data"));
|
|
_formData = function () {
|
|
return data;
|
|
};
|
|
return data;
|
|
}
|
|
function _fsExtra() {
|
|
const data = _interopRequireDefault(require("fs-extra"));
|
|
_fsExtra = function () {
|
|
return data;
|
|
};
|
|
return data;
|
|
}
|
|
function _chunk() {
|
|
const data = _interopRequireDefault(require("lodash/chunk"));
|
|
_chunk = function () {
|
|
return data;
|
|
};
|
|
return data;
|
|
}
|
|
function _get() {
|
|
const data = _interopRequireDefault(require("lodash/get"));
|
|
_get = function () {
|
|
return data;
|
|
};
|
|
return data;
|
|
}
|
|
function _set() {
|
|
const data = _interopRequireDefault(require("lodash/set"));
|
|
_set = function () {
|
|
return data;
|
|
};
|
|
return data;
|
|
}
|
|
function _uniqBy() {
|
|
const data = _interopRequireDefault(require("lodash/uniqBy"));
|
|
_uniqBy = function () {
|
|
return data;
|
|
};
|
|
return data;
|
|
}
|
|
function _md5hex() {
|
|
const data = _interopRequireDefault(require("md5hex"));
|
|
_md5hex = function () {
|
|
return data;
|
|
};
|
|
return data;
|
|
}
|
|
function _minimatch() {
|
|
const data = _interopRequireDefault(require("minimatch"));
|
|
_minimatch = function () {
|
|
return data;
|
|
};
|
|
return data;
|
|
}
|
|
function _path() {
|
|
const data = _interopRequireDefault(require("path"));
|
|
_path = function () {
|
|
return data;
|
|
};
|
|
return data;
|
|
}
|
|
function _urlJoin() {
|
|
const data = _interopRequireDefault(require("url-join"));
|
|
_urlJoin = function () {
|
|
return data;
|
|
};
|
|
return data;
|
|
}
|
|
function _internal() {
|
|
const data = require("./internal");
|
|
_internal = function () {
|
|
return data;
|
|
};
|
|
return data;
|
|
}
|
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
const EXPO_CDN = 'https://classic-assets.eascdn.net';
|
|
async function resolveGoogleServicesFile(projectRoot, manifest) {
|
|
var _manifest$android, _manifest$ios;
|
|
if ((_manifest$android = manifest.android) !== null && _manifest$android !== void 0 && _manifest$android.googleServicesFile) {
|
|
const contents = await _fsExtra().default.readFile(_path().default.resolve(projectRoot, manifest.android.googleServicesFile), 'utf8');
|
|
manifest.android.googleServicesFile = contents;
|
|
}
|
|
if ((_manifest$ios = manifest.ios) !== null && _manifest$ios !== void 0 && _manifest$ios.googleServicesFile) {
|
|
const contents = await _fsExtra().default.readFile(_path().default.resolve(projectRoot, manifest.ios.googleServicesFile), 'base64');
|
|
manifest.ios.googleServicesFile = contents;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get all fields in the manifest that match assets, then filter the ones that aren't set.
|
|
*
|
|
* @param manifest
|
|
* @returns Asset fields that the user has set like ["icon", "splash.image", ...]
|
|
*/
|
|
async function getAssetFieldPathsForManifestAsync(manifest) {
|
|
// String array like ["icon", "notification.icon", "loading.icon", "loading.backgroundImage", "ios.icon", ...]
|
|
const sdkAssetFieldPaths = await _internal().ExpSchema.getAssetSchemasAsync(manifest.sdkVersion);
|
|
return sdkAssetFieldPaths.filter(assetSchema => (0, _get().default)(manifest, assetSchema));
|
|
}
|
|
async function resolveManifestAssets({
|
|
projectRoot,
|
|
manifest,
|
|
resolver,
|
|
strict = false
|
|
}) {
|
|
try {
|
|
// Asset fields that the user has set like ["icon", "splash.image"]
|
|
const assetSchemas = await getAssetFieldPathsForManifestAsync(manifest);
|
|
// Get the URLs
|
|
const urls = await Promise.all(assetSchemas.map(async manifestField => {
|
|
const pathOrURL = (0, _get().default)(manifest, manifestField);
|
|
if (/^https?:\/\//.test(pathOrURL)) {
|
|
// It's a remote URL
|
|
return pathOrURL;
|
|
} else if (_fsExtra().default.existsSync(_path().default.resolve(projectRoot, pathOrURL))) {
|
|
return await resolver(pathOrURL);
|
|
} else {
|
|
const err = new Error('Could not resolve local asset.');
|
|
err.localAssetPath = pathOrURL;
|
|
err.manifestField = manifestField;
|
|
throw err;
|
|
}
|
|
}));
|
|
|
|
// Set the corresponding URL fields
|
|
assetSchemas.forEach((manifestField, index) => (0, _set().default)(manifest, `${manifestField}Url`, urls[index]));
|
|
} catch (e) {
|
|
let logMethod = _internal().ProjectUtils.logWarning;
|
|
if (strict) {
|
|
logMethod = _internal().ProjectUtils.logError;
|
|
}
|
|
if (e.localAssetPath) {
|
|
logMethod(projectRoot, 'expo', `Unable to resolve asset "${e.localAssetPath}" from "${e.manifestField}" in your app.json or app.config.js`);
|
|
} else {
|
|
logMethod(projectRoot, 'expo', `Warning: Unable to resolve manifest assets. Icons might not work. ${e.message}.`);
|
|
}
|
|
if (strict) {
|
|
throw new Error('Resolving assets failed.');
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Configures exp, preparing it for asset export
|
|
*
|
|
* @modifies {exp}
|
|
*
|
|
*/
|
|
async function _configureExpForAssets(projectRoot, exp, assets) {
|
|
// Add google services file if it exists
|
|
await resolveGoogleServicesFile(projectRoot, exp);
|
|
|
|
// Convert asset patterns to a list of asset strings that match them.
|
|
// Assets strings are formatted as `asset_<hash>.<type>` and represent
|
|
// the name that the file will have in the app bundle. The `asset_` prefix is
|
|
// needed because android doesn't support assets that start with numbers.
|
|
if (exp.assetBundlePatterns) {
|
|
const fullPatterns = exp.assetBundlePatterns.map(p => _path().default.join(projectRoot, p));
|
|
// Only log the patterns in debug mode, if they aren't already defined in the app.json, then all files will be targeted.
|
|
_internal().Logger.global.info('\nProcessing asset bundle patterns:');
|
|
fullPatterns.forEach(p => _internal().Logger.global.info('- ' + p));
|
|
|
|
// The assets returned by the RN packager has duplicates so make sure we
|
|
// only bundle each once.
|
|
const bundledAssets = new Set();
|
|
for (const asset of assets) {
|
|
const file = asset.files && asset.files[0];
|
|
const shouldBundle = '__packager_asset' in asset && asset.__packager_asset && file && fullPatterns.some(p => (0, _minimatch().default)(file, p));
|
|
_internal().ProjectUtils.logDebug(projectRoot, 'expo', `${shouldBundle ? 'Include' : 'Exclude'} asset ${file}`);
|
|
if (shouldBundle) {
|
|
asset.fileHashes.forEach(hash => bundledAssets.add('asset_' + hash + ('type' in asset && asset.type ? '.' + asset.type : '')));
|
|
}
|
|
}
|
|
exp.bundledAssets = [...bundledAssets];
|
|
delete exp.assetBundlePatterns;
|
|
}
|
|
return exp;
|
|
}
|
|
async function publishAssetsAsync(options) {
|
|
return exportAssetsAsync({
|
|
...options,
|
|
hostedUrl: EXPO_CDN,
|
|
assetPath: '~assets'
|
|
});
|
|
}
|
|
async function exportAssetsAsync({
|
|
projectRoot,
|
|
exp,
|
|
hostedUrl,
|
|
assetPath,
|
|
outputDir,
|
|
bundles,
|
|
experimentalBundle
|
|
}) {
|
|
_internal().Logger.global.info('Analyzing assets');
|
|
let assets;
|
|
if (experimentalBundle) {
|
|
(0, _assert().default)(outputDir, 'outputDir must be specified when exporting to EAS');
|
|
assets = (0, _uniqBy().default)(Object.values(bundles).flatMap(bundle => bundle.assets), asset => asset.hash);
|
|
} else {
|
|
const assetCdnPath = (0, _urlJoin().default)(hostedUrl, assetPath);
|
|
assets = await collectAssets(projectRoot, exp, assetCdnPath, bundles);
|
|
}
|
|
_internal().Logger.global.info('Saving assets');
|
|
if (assets.length > 0 && assets[0].fileHashes) {
|
|
if (outputDir) {
|
|
await saveAssetsAsync(projectRoot, assets, outputDir);
|
|
} else {
|
|
// No output directory defined, use remote url.
|
|
await uploadAssetsAsync(projectRoot, assets);
|
|
}
|
|
} else {
|
|
_internal().Logger.global.info({
|
|
quiet: true
|
|
}, 'No assets to upload, skipped.');
|
|
}
|
|
|
|
// Updates the manifest to reflect additional asset bundling + configs
|
|
await _configureExpForAssets(projectRoot, exp, assets);
|
|
return {
|
|
exp,
|
|
assets
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Collect list of assets missing on host
|
|
*
|
|
* @param paths asset paths found locally that need to be uploaded.
|
|
*/
|
|
async function fetchMissingAssetsAsync(paths) {
|
|
const user = await _internal().UserManager.ensureLoggedInAsync();
|
|
const api = _internal().ApiV2.clientForUser(user);
|
|
const result = await api.postAsync('assets/metadata', {
|
|
keys: paths
|
|
});
|
|
const metas = result.metadata;
|
|
const missing = paths.filter(key => !metas[key].exists);
|
|
return missing;
|
|
}
|
|
function logAssetTask(projectRoot, action, pathName) {
|
|
_internal().ProjectUtils.logDebug(projectRoot, 'expo', `${action} ${pathName}`);
|
|
const relativePath = pathName.replace(projectRoot, '');
|
|
_internal().Logger.global.info({
|
|
quiet: true
|
|
}, `${action} ${relativePath}`);
|
|
}
|
|
|
|
// TODO(jesse): Add analytics for upload
|
|
async function uploadAssetsAsync(projectRoot, assets) {
|
|
// Collect paths by key, also effectively handles duplicates in the array
|
|
const paths = collectAssetPaths(assets);
|
|
const missing = await fetchMissingAssetsAsync(Object.keys(paths));
|
|
if (missing.length === 0) {
|
|
_internal().Logger.global.info({
|
|
quiet: true
|
|
}, `No assets changed, skipped.`);
|
|
return;
|
|
}
|
|
const keyChunks = (0, _chunk().default)(missing, 5);
|
|
|
|
// Upload them in chunks of 5 to prevent network and system issues.
|
|
for (const keys of keyChunks) {
|
|
const formData = new (_formData().default)();
|
|
for (const key of keys) {
|
|
const pathName = paths[key];
|
|
logAssetTask(projectRoot, 'uploading', pathName);
|
|
formData.append(key, _fsExtra().default.createReadStream(pathName), pathName);
|
|
}
|
|
|
|
// TODO: Document what's going on
|
|
const user = await _internal().UserManager.ensureLoggedInAsync();
|
|
const api = _internal().ApiV2.clientForUser(user);
|
|
await api.uploadFormDataAsync('assets/upload', formData);
|
|
}
|
|
}
|
|
function collectAssetPaths(assets) {
|
|
// Collect paths by key, also effectively handles duplicates in the array
|
|
const paths = {};
|
|
assets.forEach(asset => {
|
|
asset.files.forEach((path, index) => {
|
|
paths[asset.fileHashes[index]] = path;
|
|
});
|
|
});
|
|
return paths;
|
|
}
|
|
async function saveAssetsAsync(projectRoot, assets, outputDir) {
|
|
// Collect paths by key, also effectively handles duplicates in the array
|
|
const paths = collectAssetPaths(assets);
|
|
|
|
// save files one chunk at a time
|
|
const keyChunks = (0, _chunk().default)(Object.keys(paths), 5);
|
|
for (const keys of keyChunks) {
|
|
const promises = [];
|
|
for (const key of keys) {
|
|
const pathName = paths[key];
|
|
logAssetTask(projectRoot, 'saving', pathName);
|
|
const assetPath = _path().default.resolve(outputDir, 'assets', key);
|
|
|
|
// copy file over to assetPath
|
|
promises.push(_fsExtra().default.copy(pathName, assetPath));
|
|
}
|
|
await Promise.all(promises);
|
|
}
|
|
_internal().Logger.global.info('Files successfully saved.');
|
|
}
|
|
|
|
/**
|
|
* Collects all the assets declared in the android app, ios app and manifest
|
|
*
|
|
* @param {string} hostedAssetPrefix
|
|
* The path where assets are hosted (ie) http://xxx.cloudfront.com/assets/
|
|
*
|
|
* @modifies {exp} Replaces relative asset paths in the manifest with hosted URLS
|
|
*
|
|
*/
|
|
async function collectAssets(projectRoot, exp, hostedAssetPrefix, bundles) {
|
|
// Resolve manifest assets to their hosted URL and add them to the list of assets to
|
|
// be uploaded. Modifies exp.
|
|
const manifestAssets = [];
|
|
await resolveManifestAssets({
|
|
projectRoot,
|
|
manifest: exp,
|
|
async resolver(assetPath) {
|
|
const absolutePath = _path().default.resolve(projectRoot, assetPath);
|
|
const contents = await _fsExtra().default.readFile(absolutePath);
|
|
const hash = (0, _md5hex().default)(contents);
|
|
manifestAssets.push({
|
|
files: [absolutePath],
|
|
fileHashes: [hash],
|
|
hash
|
|
});
|
|
return (0, _urlJoin().default)(hostedAssetPrefix, hash);
|
|
},
|
|
strict: true
|
|
});
|
|
return [...Object.values(bundles).flatMap(bundle => bundle.assets), ...manifestAssets];
|
|
}
|
|
//# sourceMappingURL=ProjectAssets.js.map
|