xdl
Version:
The Expo Development Library
511 lines (405 loc) • 20.4 kB
JavaScript
// Copyright 2015-present 650 Industries. All rights reserved.
;
// Set EXPO_VIEW_DIR to universe/exponent to test locally
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.prepareDetachedBuildAsync = exports.detachAsync = undefined;
let detachAsync = exports.detachAsync = (() => {
var _ref = _asyncToGenerator(function* (projectRoot, options) {
let user = yield (_User || _load_User()).default.ensureLoggedInAsync();
if (!user) {
throw new Error('Internal error -- somehow detach is being run in offline mode.');
}
let username = user.username;
let { exp } = yield (_ProjectUtils || _load_ProjectUtils()).readConfigJsonAsync(projectRoot);
let experienceName = `@${username}/${exp.slug}`;
let experienceUrl = `exp://exp.host/${experienceName}`;
// Check to make sure project isn't fully detached already
let hasIosDirectory = (0, (_ExponentTools || _load_ExponentTools()).isDirectory)(_path.default.join(projectRoot, 'ios'));
let hasAndroidDirectory = (0, (_ExponentTools || _load_ExponentTools()).isDirectory)(_path.default.join(projectRoot, 'android'));
if (hasIosDirectory && hasAndroidDirectory) {
throw new (_XDLError || _load_XDLError()).default((_ErrorCode || _load_ErrorCode()).default.DIRECTORY_ALREADY_EXISTS, 'Error detaching. `ios` and `android` directories already exist.');
}
// Project was already detached on Windows or Linux
if (!hasIosDirectory && hasAndroidDirectory && process.platform === 'darwin') {
let response = yield yesnoAsync(`This will add an Xcode project and leave your existing Android project alone. Enter 'yes' to continue:`);
if (!response) {
console.log('Exiting...');
return false;
}
}
if (hasIosDirectory && !hasAndroidDirectory) {
throw new Error('`ios` directory already exists. Please remove it and try again.');
}
console.log('Validating project manifest...');
const configName = yield (_ProjectUtils || _load_ProjectUtils()).configFilenameAsync(projectRoot);
if (!exp.name) {
throw new Error(`${configName} is missing \`name\``);
}
if (!exp.android || !exp.android.package) {
throw new Error(`${configName} is missing android.package field. See https://docs.expo.io/versions/latest/guides/configuration.html#package`);
}
if (!exp.sdkVersion) {
throw new Error(`${configName} is missing \`sdkVersion\``);
}
let majorSdkVersion = (0, (_ExponentTools || _load_ExponentTools()).parseSdkMajorVersion)(exp.sdkVersion);
if (majorSdkVersion < 16) {
throw new Error(`${configName} must be updated to SDK 16.0.0 or newer to be detached.`);
}
const versions = yield (_Versions || _load_Versions()).versionsAsync();
let sdkVersionConfig = versions.sdkVersions[exp.sdkVersion];
if (!sdkVersionConfig || !sdkVersionConfig.androidExpoViewUrl || !sdkVersionConfig.iosExpoViewUrl) {
if (process.env.EXPO_VIEW_DIR) {
console.warn(`Detaching is not supported for SDK ${exp.sdkVersion}; ignoring this because you provided EXPO_VIEW_DIR`);
sdkVersionConfig = {};
} else {
throw new Error(`Detaching is not supported for SDK version ${exp.sdkVersion}`);
}
}
// Modify exp.json
exp.isDetached = true;
if (!exp.detach) {
exp.detach = {};
}
if (!exp.detach.scheme) {
let detachedUUID = (_uuid || _load_uuid()).default.v4().replace(/-/g, '');
exp.detach.scheme = `exp${detachedUUID}`;
}
let expoDirectory = _path.default.join(projectRoot, '.expo-source');
(_mkdirp || _load_mkdirp()).default.sync(expoDirectory);
// iOS
let isIosSupported = true;
if (process.platform !== 'darwin') {
if (options && options.force) {
console.warn(`You are not running macOS, but have provided the --force option, so we will attempt to generate an iOS project anyway. This might fail.`);
} else {
console.warn(`Skipping iOS because you are not running macOS.`);
isIosSupported = false;
}
}
if (!hasIosDirectory && isIosSupported) {
const context = (_StandaloneContext || _load_StandaloneContext()).default.createUserContext(projectRoot, exp, experienceUrl);
yield detachIOSAsync(context);
exp = (_IosWorkspace || _load_IosWorkspace()).addDetachedConfigToExp(exp, context);
exp.detach.iosExpoViewUrl = sdkVersionConfig.iosExpoViewUrl;
}
// Android
if (!hasAndroidDirectory) {
let androidDirectory = _path.default.join(expoDirectory, 'android');
(_rimraf || _load_rimraf()).default.sync(androidDirectory);
(_mkdirp || _load_mkdirp()).default.sync(androidDirectory);
yield detachAndroidAsync(projectRoot, androidDirectory, exp.sdkVersion, experienceUrl, exp, sdkVersionConfig.androidExpoViewUrl);
exp.detach.androidExpoViewUrl = sdkVersionConfig.androidExpoViewUrl;
}
console.log('Writing ExpoKit configuration...');
// Update exp.json/app.json
// if we're writing to app.json, we need to place the configuration under the expo key
const nameToWrite = yield (_ProjectUtils || _load_ProjectUtils()).configFilenameAsync(projectRoot);
if (nameToWrite === 'app.json') {
exp = { expo: exp };
}
yield _fs.default.promise.writeFile(_path.default.join(projectRoot, nameToWrite), JSON.stringify(exp, null, 2));
console.log('Finished detaching your project! Look in the `android` and `ios` directories for the respective native projects. Follow the ExpoKit guide at https://docs.expo.io/versions/latest/guides/expokit.html to get your project running.\n');
return true;
});
return function detachAsync(_x, _x2) {
return _ref.apply(this, arguments);
};
})();
/**
* Create a detached Expo iOS app pointing at the given project.
*/
let detachIOSAsync = (() => {
var _ref2 = _asyncToGenerator(function* (context) {
yield (_IosWorkspace || _load_IosWorkspace()).createDetachedAsync(context);
console.log('Configuring iOS project...');
yield (_IosNSBundle || _load_IosNSBundle()).configureAsync(context);
console.log(`iOS detach is complete!`);
return;
});
return function detachIOSAsync(_x3) {
return _ref2.apply(this, arguments);
};
})();
let regexFileAsync = (() => {
var _ref3 = _asyncToGenerator(function* (filename, regex, replace) {
let file = yield _fs.default.promise.readFile(filename);
let fileString = file.toString();
yield _fs.default.promise.writeFile(filename, fileString.replace(regex, replace));
});
return function regexFileAsync(_x4, _x5, _x6) {
return _ref3.apply(this, arguments);
};
})();
let renamePackageAsync = (() => {
var _ref4 = _asyncToGenerator(function* (directory, originalPkg, destPkg) {
let originalSplitPackage = originalPkg.split('.');
let originalDeepDirectory = directory;
for (let i = 0; i < originalSplitPackage.length; i++) {
originalDeepDirectory = _path.default.join(originalDeepDirectory, originalSplitPackage[i]);
}
// copy files into temp directory
let tmpDirectory = _path.default.join(directory, 'tmp-exponent-directory');
(_mkdirp || _load_mkdirp()).default.sync(tmpDirectory);
yield (_Utils || _load_Utils()).ncpAsync(originalDeepDirectory, tmpDirectory);
// delete old package
(_rimraf || _load_rimraf()).default.sync(_path.default.join(directory, originalSplitPackage[0]));
// make new package
let newSplitPackage = destPkg.split('.');
let newDeepDirectory = directory;
for (let i = 0; i < newSplitPackage.length; i++) {
newDeepDirectory = _path.default.join(newDeepDirectory, newSplitPackage[i]);
(_mkdirp || _load_mkdirp()).default.sync(newDeepDirectory);
}
// copy from temp to new package
yield (_Utils || _load_Utils()).ncpAsync(tmpDirectory, newDeepDirectory);
// delete temp
(_rimraf || _load_rimraf()).default.sync(tmpDirectory);
});
return function renamePackageAsync(_x7, _x8, _x9) {
return _ref4.apply(this, arguments);
};
})();
let detachAndroidAsync = (() => {
var _ref5 = _asyncToGenerator(function* (projectRoot, expoDirectory, sdkVersion, experienceUrl, manifest, expoViewUrl) {
let tmpExpoDirectory;
if (process.env.EXPO_VIEW_DIR) {
// Only for testing
tmpExpoDirectory = process.env.EXPO_VIEW_DIR;
} else {
tmpExpoDirectory = _path.default.join(projectRoot, 'temp-android-directory');
(_mkdirp || _load_mkdirp()).default.sync(tmpExpoDirectory);
console.log('Downloading Android code...');
yield (_Api || _load_Api()).default.downloadAsync(expoViewUrl, tmpExpoDirectory, { extract: true });
}
let androidProjectDirectory = _path.default.join(projectRoot, 'android');
console.log('Moving Android project files...');
yield (_Utils || _load_Utils()).ncpAsync(_path.default.join(tmpExpoDirectory, 'android', 'maven'), _path.default.join(expoDirectory, 'maven'));
yield (_Utils || _load_Utils()).ncpAsync(_path.default.join(tmpExpoDirectory, 'android', 'detach-scripts'), _path.default.join(expoDirectory, 'detach-scripts'));
yield (_Utils || _load_Utils()).ncpAsync(_path.default.join(tmpExpoDirectory, 'exponent-view-template', 'android'), androidProjectDirectory);
if (process.env.EXPO_VIEW_DIR) {
(_rimraf || _load_rimraf()).default.sync(_path.default.join(androidProjectDirectory, 'build'));
(_rimraf || _load_rimraf()).default.sync(_path.default.join(androidProjectDirectory, 'app', 'build'));
}
// Fix up app/build.gradle
console.log('Configuring Android project...');
let appBuildGradle = _path.default.join(androidProjectDirectory, 'app', 'build.gradle');
yield regexFileAsync(appBuildGradle, /\/\* UNCOMMENT WHEN DISTRIBUTING/g, '');
yield regexFileAsync(appBuildGradle, /END UNCOMMENT WHEN DISTRIBUTING \*\//g, '');
yield regexFileAsync(appBuildGradle, `compile project(':expoview')`, '');
// Fix AndroidManifest
let androidManifest = _path.default.join(androidProjectDirectory, 'app', 'src', 'main', 'AndroidManifest.xml');
yield regexFileAsync(androidManifest, 'PLACEHOLDER_DETACH_SCHEME', manifest.detach.scheme);
// Fix MainActivity
let mainActivity = _path.default.join(androidProjectDirectory, 'app', 'src', 'main', 'java', 'detach', 'app', 'template', 'pkg', 'name', 'MainActivity.java');
yield regexFileAsync(mainActivity, 'TEMPLATE_INITIAL_URL', experienceUrl);
// Fix package name
let packageName = manifest.android.package;
yield renamePackageAsync(_path.default.join(androidProjectDirectory, 'app', 'src', 'main', 'java'), ANDROID_TEMPLATE_PKG, packageName);
yield renamePackageAsync(_path.default.join(androidProjectDirectory, 'app', 'src', 'test', 'java'), ANDROID_TEMPLATE_PKG, packageName);
yield renamePackageAsync(_path.default.join(androidProjectDirectory, 'app', 'src', 'androidTest', 'java'), ANDROID_TEMPLATE_PKG, packageName);
let packageNameMatches = yield (_glob || _load_glob()).default.promise(androidProjectDirectory + '/**/*.@(java|gradle|xml)');
if (packageNameMatches) {
let oldPkgRegex = new RegExp(`${ANDROID_TEMPLATE_PKG.replace(/\./g, '\\.')}`, 'g');
for (let i = 0; i < packageNameMatches.length; i++) {
yield regexFileAsync(packageNameMatches[i], oldPkgRegex, packageName);
}
}
// Fix app name
console.log('Naming Android project...');
let appName = manifest.name;
yield regexFileAsync(_path.default.resolve(androidProjectDirectory, 'app', 'src', 'main', 'res', 'values', 'strings.xml'), ANDROID_TEMPLATE_NAME, appName);
// Fix image
let icon = manifest.android && manifest.android.icon ? manifest.android.icon : manifest.icon;
if (icon) {
let iconMatches = yield (_glob || _load_glob()).default.promise(_path.default.join(androidProjectDirectory, 'app', 'src', 'main', 'res') + '/**/ic_launcher.png');
if (iconMatches) {
for (let i = 0; i < iconMatches.length; i++) {
yield _fs.default.promise.unlink(iconMatches[i]);
// TODO: make more efficient
yield (0, (_ExponentTools || _load_ExponentTools()).saveImageToPathAsync)(projectRoot, icon, iconMatches[i]);
}
}
}
// Clean up
console.log('Cleaning up Android...');
if (!process.env.EXPO_VIEW_DIR) {
(0, (_ExponentTools || _load_ExponentTools()).rimrafDontThrow)(tmpExpoDirectory);
}
console.log('Android detach is complete!\n');
});
return function detachAndroidAsync(_x10, _x11, _x12, _x13, _x14, _x15) {
return _ref5.apply(this, arguments);
};
})();
let ensureBuildConstantsExistsIOSAsync = (() => {
var _ref6 = _asyncToGenerator(function* (configFilePath) {
// EXBuildConstants is included in newer ExpoKit projects.
// create it if it doesn't exist.
const doesBuildConstantsExist = _fs.default.existsSync(_path.default.join(configFilePath, 'EXBuildConstants.plist'));
if (!doesBuildConstantsExist) {
yield (_IosPlist || _load_IosPlist()).createBlankAsync(configFilePath, 'EXBuildConstants');
console.log('Created `EXBuildConstants.plist` because it did not exist yet');
}
return;
});
return function ensureBuildConstantsExistsIOSAsync(_x16) {
return _ref6.apply(this, arguments);
};
})();
let prepareDetachedBuildIosAsync = (() => {
var _ref7 = _asyncToGenerator(function* (projectDir, exp, args) {
const context = (_StandaloneContext || _load_StandaloneContext()).default.createUserContext(projectDir, exp);
let { iosProjectDirectory, supportingDirectory } = (_IosWorkspace || _load_IosWorkspace()).getPaths(context);
console.log(`Preparing iOS build at ${iosProjectDirectory}...`);
// These files cause @providesModule naming collisions
// but are not available until after `pod install` has run.
let podsDirectory = _path.default.join(iosProjectDirectory, 'Pods');
if (!(0, (_ExponentTools || _load_ExponentTools()).isDirectory)(podsDirectory)) {
throw new Error(`Can't find directory ${podsDirectory}, make sure you've run pod install.`);
}
let rnPodDirectory = _path.default.join(podsDirectory, 'React');
if ((0, (_ExponentTools || _load_ExponentTools()).isDirectory)(rnPodDirectory)) {
let rnFilesToDelete = yield (_glob || _load_glob()).default.promise(rnPodDirectory + '/**/*.@(js|json)');
if (rnFilesToDelete) {
for (let i = 0; i < rnFilesToDelete.length; i++) {
yield _fs.default.promise.unlink(rnFilesToDelete[i]);
}
}
}
// insert expo development url into iOS config
if (!args.skipXcodeConfig) {
// populate EXPO_RUNTIME_VERSION from ExpoKit pod version
let expoKitVersion = '';
const podfileLockPath = _path.default.join(iosProjectDirectory, 'Podfile.lock');
try {
const podfileLock = yield _fs.default.promise.readFile(podfileLockPath, 'utf8');
const expoKitVersionRegex = /ExpoKit\/Core\W?\(([0-9\.]+)\)/gi;
let match = expoKitVersionRegex.exec(podfileLock);
expoKitVersion = match[1];
} catch (e) {
throw new Error(`Unable to read ExpoKit version from Podfile.lock. Make sure your project depends on ExpoKit. (${e})`);
}
// populate development url
let devUrl = yield (_UrlUtils || _load_UrlUtils()).constructManifestUrlAsync(projectDir);
yield ensureBuildConstantsExistsIOSAsync(supportingDirectory);
yield (_IosPlist || _load_IosPlist()).modifyAsync(supportingDirectory, 'EXBuildConstants', function (constantsConfig) {
constantsConfig.developmentUrl = devUrl;
constantsConfig.EXPO_RUNTIME_VERSION = expoKitVersion;
return constantsConfig;
});
}
});
return function prepareDetachedBuildIosAsync(_x17, _x18, _x19) {
return _ref7.apply(this, arguments);
};
})();
let prepareDetachedBuildAsync = exports.prepareDetachedBuildAsync = (() => {
var _ref8 = _asyncToGenerator(function* (projectDir, args) {
let { exp } = yield (_ProjectUtils || _load_ProjectUtils()).readConfigJsonAsync(projectDir);
if (args.platform === 'ios') {
yield prepareDetachedBuildIosAsync(projectDir, exp, args);
} else {
let androidProjectDirectory = _path.default.join(projectDir, 'android');
let expoBuildConstantsMatches = yield (_glob || _load_glob()).default.promise(androidProjectDirectory + '/**/ExponentBuildConstants.java');
if (expoBuildConstantsMatches && expoBuildConstantsMatches.length) {
let expoBuildConstants = expoBuildConstantsMatches[0];
let devUrl = yield (_UrlUtils || _load_UrlUtils()).constructManifestUrlAsync(projectDir);
yield regexFileAsync(expoBuildConstants, /DEVELOPMENT_URL \= \"[^\"]*\"\;/, `DEVELOPMENT_URL = "${devUrl}";`);
}
}
});
return function prepareDetachedBuildAsync(_x20, _x21) {
return _ref8.apply(this, arguments);
};
})();
require('instapromise');
var _mkdirp;
function _load_mkdirp() {
return _mkdirp = _interopRequireDefault(require('mkdirp'));
}
var _fs = _interopRequireDefault(require('fs'));
var _path = _interopRequireDefault(require('path'));
var _rimraf;
function _load_rimraf() {
return _rimraf = _interopRequireDefault(require('rimraf'));
}
var _glob;
function _load_glob() {
return _glob = _interopRequireDefault(require('glob'));
}
var _uuid;
function _load_uuid() {
return _uuid = _interopRequireDefault(require('uuid'));
}
var _yesno;
function _load_yesno() {
return _yesno = _interopRequireDefault(require('yesno'));
}
var _ExponentTools;
function _load_ExponentTools() {
return _ExponentTools = require('./ExponentTools');
}
var _IosPlist;
function _load_IosPlist() {
return _IosPlist = _interopRequireWildcard(require('./IosPlist'));
}
var _IosNSBundle;
function _load_IosNSBundle() {
return _IosNSBundle = _interopRequireWildcard(require('./IosNSBundle'));
}
var _IosWorkspace;
function _load_IosWorkspace() {
return _IosWorkspace = _interopRequireWildcard(require('./IosWorkspace'));
}
var _Api;
function _load_Api() {
return _Api = _interopRequireDefault(require('../Api'));
}
var _ErrorCode;
function _load_ErrorCode() {
return _ErrorCode = _interopRequireDefault(require('../ErrorCode'));
}
var _ProjectUtils;
function _load_ProjectUtils() {
return _ProjectUtils = _interopRequireWildcard(require('../project/ProjectUtils'));
}
var _User;
function _load_User() {
return _User = _interopRequireDefault(require('../User'));
}
var _XDLError;
function _load_XDLError() {
return _XDLError = _interopRequireDefault(require('../XDLError'));
}
var _StandaloneContext;
function _load_StandaloneContext() {
return _StandaloneContext = _interopRequireDefault(require('./StandaloneContext'));
}
var _UrlUtils;
function _load_UrlUtils() {
return _UrlUtils = _interopRequireWildcard(require('../UrlUtils'));
}
var _Utils;
function _load_Utils() {
return _Utils = _interopRequireWildcard(require('../Utils'));
}
var _Versions;
function _load_Versions() {
return _Versions = _interopRequireWildcard(require('../Versions'));
}
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }
const ANDROID_TEMPLATE_PKG = 'detach.app.template.pkg.name';
const ANDROID_TEMPLATE_COMPANY = 'detach.app.template.company.domain';
const ANDROID_TEMPLATE_NAME = 'DetachAppTemplate';
function yesnoAsync(question) {
return new Promise(resolve => {
(_yesno || _load_yesno()).default.ask(question, null, ok => {
resolve(ok);
});
});
}
//# sourceMappingURL=../__sourcemaps__/detach/Detach.js.map