GT2/GT2-iOS/node_modules/xdl/build/Exp.js.flow

346 lines
10 KiB
Plaintext

/**
* @flow
*/
import promisify from 'util.promisify';
import existsAsync from 'exists-async';
import fs from 'fs';
import mkdirp from 'mkdirp';
import path from 'path';
import spawnAsync from '@expo/spawn-async';
import JsonFile from '@expo/json-file';
import joi from 'joi';
import rimraf from 'rimraf';
import * as Analytics from './Analytics';
import Api from './Api';
import * as Binaries from './Binaries';
import ErrorCode from './ErrorCode';
import * as Extract from './Extract';
import Logger from './Logger';
import NotificationCode from './NotificationCode';
import * as ProjectUtils from './project/ProjectUtils';
import UserManager from './User';
import * as UrlUtils from './UrlUtils';
import UserSettings from './UserSettings';
import XDLError from './XDLError';
import * as ProjectSettings from './ProjectSettings';
import MessageCode from './MessageCode';
// FIXME(perry) eliminate usage of this template
export const ENTRY_POINT_PLATFORM_TEMPLATE_STRING = 'PLATFORM_GOES_HERE';
export { default as convertProjectAsync } from './project/Convert';
const readFileAsync = promisify(fs.readFile);
const writeFileAsync = promisify(fs.writeFile);
const validateAsync = promisify(joi.validate);
const mkdirpAsync = promisify(mkdirp);
export async function determineEntryPointAsync(root: string) {
let { exp, pkg } = await ProjectUtils.readConfigJsonAsync(root);
// entryPoint is relative to the packager root and main is relative
// to the project root. So if your rn-cli.config.js points to a different
// root than the project root, these can be different. Most of the time
// you should use main.
let entryPoint = pkg.main || 'index.js';
if (exp && exp.entryPoint) {
entryPoint = exp.entryPoint;
}
return entryPoint;
}
function _starterAppCacheDirectory() {
let dotExpoHomeDirectory = UserSettings.dotExpoHomeDirectory();
let dir = path.join(dotExpoHomeDirectory, 'starter-app-cache');
mkdirp.sync(dir);
return dir;
}
async function _downloadStarterAppAsync(templateId, progressFunction, retryFunction) {
let versions = await Api.versionsAsync();
let templateApp = null;
for (let i = 0; i < versions.templatesv2.length; i++) {
if (templateId === versions.templatesv2[i].id) {
templateApp = versions.templatesv2[i];
}
}
if (!templateApp) {
throw new XDLError(ErrorCode.INVALID_OPTIONS, `No template app with id ${templateId}.`);
}
let starterAppVersion = templateApp.version;
let starterAppName = `${templateId}-${starterAppVersion}`;
let filename = `${starterAppName}.tar.gz`;
let starterAppPath = path.join(_starterAppCacheDirectory(), filename);
if (await existsAsync(starterAppPath)) {
return {
starterAppPath,
starterAppName,
};
}
let url = `https://s3.amazonaws.com/exp-starter-apps/${filename}`;
await Api.downloadAsync(
url,
path.join(_starterAppCacheDirectory(), filename),
{},
progressFunction,
retryFunction
);
return {
starterAppPath,
starterAppName,
};
}
export async function downloadTemplateApp(templateId: string, selectedDir: string, opts: any) {
// Validate
let schema = joi.object().keys({
name: joi.string().required(),
});
// Should we validate that name is a valid name here?
try {
await validateAsync({ name: opts.name }, schema);
} catch (e) {
throw new XDLError(ErrorCode.INVALID_OPTIONS, e.toString());
}
let name = opts.name;
let root = path.join(selectedDir, name);
Analytics.logEvent('New Project', {
selectedDir,
name,
});
let fileExists = true;
try {
// If file doesn't exist it will throw an error.
// Don't want to continue unless there is nothing there.
fs.statSync(root);
} catch (e) {
fileExists = false;
}
// This check is required because without it, the retry button would throw an error because the directory already exists,
// even though it is empty.
if (fileExists && fs.readdirSync(root).length !== 0) {
throw new XDLError(
ErrorCode.DIRECTORY_ALREADY_EXISTS,
`That directory already exists. Please choose a different parent directory or project name.`
);
}
// Download files
await mkdirpAsync(root);
Logger.notifications.info({ code: NotificationCode.PROGRESS }, MessageCode.DOWNLOADING);
let { starterAppPath } = await _downloadStarterAppAsync(
templateId,
opts.progressFunction,
opts.retryFunction
);
return { starterAppPath, name, root };
}
export async function extractTemplateApp(starterAppPath: string, name: string, root: string) {
Logger.notifications.info({ code: NotificationCode.PROGRESS }, MessageCode.EXTRACTING);
await Extract.extractAsync(starterAppPath, root);
// Update files
Logger.notifications.info({ code: NotificationCode.PROGRESS }, MessageCode.CUSTOMIZING);
// Update app.json
let appJson = await readFileAsync(path.join(root, 'app.json'), 'utf8');
let customAppJson = appJson
.replace(/\"My New Project\"/, `"${name}"`)
.replace(/\"my-new-project\"/, `"${name}"`);
await writeFileAsync(path.join(root, 'app.json'), customAppJson, 'utf8');
await initGitRepo(root);
Logger.notifications.info({ code: NotificationCode.PROGRESS }, 'Starting project...');
return root;
}
async function initGitRepo(root: string) {
if (process.platform === 'darwin' && !Binaries.isXcodeInstalled()) {
Logger.global.warn(`Unable to initialize git repo. \`git\` not installed.`);
return;
}
// let's see if we're in a git tree
let insideGit = true;
try {
await spawnAsync('git', ['rev-parse', '--is-inside-work-tree'], {
cwd: root,
});
Logger.global.debug('New project is already inside of a git repo, skipping git init.');
} catch (e) {
insideGit = false;
}
if (!insideGit) {
try {
await spawnAsync('git', ['init'], { cwd: root });
} catch (e) {
// no-op -- this is just a convenience and we don't care if it fails
}
}
}
export async function saveRecentExpRootAsync(root: string) {
root = path.resolve(root);
// Write the recent Exps JSON file
let recentExpsJsonFile = UserSettings.recentExpsJsonFile();
let recentExps = await recentExpsJsonFile.readAsync();
// Filter out copies of this so we don't get dupes in this list
recentExps = recentExps.filter(function(x) {
return x !== root;
});
recentExps.unshift(root);
return await recentExpsJsonFile.writeAsync(recentExps.slice(0, 100));
}
function getHomeDir(): string {
return process.env[process.platform === 'win32' ? 'USERPROFILE' : 'HOME'] || '';
}
function makePathReadable(pth) {
let homedir = getHomeDir();
if (pth.substr(0, homedir.length) === homedir) {
return `~${pth.substr(homedir.length)}`;
} else {
return pth;
}
}
export async function expInfoSafeAsync(root: string) {
try {
let { exp: { name, description, icon, iconUrl } } = await ProjectUtils.readConfigJsonAsync(
root
);
let pathOrUrl =
icon || iconUrl || 'https://d3lwq5rlu14cro.cloudfront.net/ExponentEmptyManifest_192.png';
let resolvedPath = path.resolve(root, pathOrUrl);
if (fs.existsSync(resolvedPath)) {
icon = `file://${resolvedPath}`;
} else {
icon = pathOrUrl; // Assume already a URL
}
return {
readableRoot: makePathReadable(root),
root,
name,
description,
icon,
};
} catch (e) {
return null;
}
}
type PublishInfo = {
args: {
username: string,
remoteUsername: string,
remotePackageName: string,
remoteFullPackageName: string,
bundleIdentifierIOS: ?string,
packageNameAndroid: ?string,
},
};
// TODO: remove / change, no longer publishInfo, this is just used for signing
export async function getPublishInfoAsync(root: string): Promise<PublishInfo> {
const user = await UserManager.ensureLoggedInAsync();
if (!user) {
throw new Error('Attempted to login in offline mode. This is a bug.');
}
const { username } = user;
const { exp } = await ProjectUtils.readConfigJsonAsync(root);
const name = exp.slug;
const version = exp.version;
const configName = await ProjectUtils.configFilenameAsync(root);
if (!exp || !exp.sdkVersion) {
throw new Error(`sdkVersion is missing from ${configName}`);
}
if (!name) {
// slug is made programmatically for app.json
throw new Error(`slug field is missing from exp.json.`);
}
if (!version) {
throw new Error(`Can't get version of package.`);
}
let remotePackageName = name;
let remoteUsername = username;
let remoteFullPackageName = `@${remoteUsername}/${remotePackageName}`;
let bundleIdentifierIOS = exp.ios ? exp.ios.bundleIdentifier : null;
let packageNameAndroid = exp.android ? exp.android.package : null;
return {
args: {
username,
remoteUsername,
remotePackageName,
remoteFullPackageName,
bundleIdentifierIOS,
packageNameAndroid, // TODO: this isn't used anywhere
},
};
}
export async function recentValidExpsAsync() {
let recentExpsJsonFile = UserSettings.recentExpsJsonFile();
let recentExps = await recentExpsJsonFile.readAsync();
let results = await Promise.all(recentExps.map(expInfoSafeAsync));
let filteredResults = results.filter(result => result);
return filteredResults;
}
export async function sendAsync(recipient: string, url_: string) {
let result = await Api.callMethodAsync('send', [recipient, url_]);
return result;
}
// TODO: figure out where these functions should live
export async function getProjectRandomnessAsync(projectRoot: string) {
let ps = await ProjectSettings.readAsync(projectRoot);
let randomness = ps.urlRandomness;
if (randomness) {
return randomness;
} else {
return resetProjectRandomnessAsync(projectRoot);
}
}
export async function resetProjectRandomnessAsync(projectRoot: string) {
let randomness = UrlUtils.someRandomness();
ProjectSettings.setAsync(projectRoot, { urlRandomness: randomness });
return randomness;
}
export async function clearXDLCacheAsync() {
let dotExpoHomeDirectory = UserSettings.dotExpoHomeDirectory();
rimraf.sync(path.join(dotExpoHomeDirectory, 'ios-simulator-app-cache'));
rimraf.sync(path.join(dotExpoHomeDirectory, 'android-apk-cache'));
rimraf.sync(path.join(dotExpoHomeDirectory, 'starter-app-cache'));
Logger.notifications.info(`Cleared cache`);
}