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.

575 lines
22 KiB

"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.actionAsync = actionAsync;
function _config() {
const data = require("@expo/config");
_config = function () {
return data;
};
return data;
}
function _configPlugins() {
const data = require("@expo/config-plugins");
_configPlugins = function () {
return data;
};
return data;
}
function _plist() {
const data = _interopRequireDefault(require("@expo/plist"));
_plist = function () {
return data;
};
return data;
}
function _spawnAsync() {
const data = _interopRequireDefault(require("@expo/spawn-async"));
_spawnAsync = function () {
return data;
};
return data;
}
function _chalk() {
const data = _interopRequireDefault(require("chalk"));
_chalk = function () {
return data;
};
return data;
}
function _commander() {
const data = _interopRequireDefault(require("commander"));
_commander = function () {
return data;
};
return data;
}
function _fsExtra() {
const data = _interopRequireDefault(require("fs-extra"));
_fsExtra = function () {
return data;
};
return data;
}
function _npmPackageArg() {
const data = _interopRequireDefault(require("npm-package-arg"));
_npmPackageArg = function () {
return data;
};
return data;
}
function _path() {
const data = _interopRequireDefault(require("path"));
_path = function () {
return data;
};
return data;
}
function _stripAnsi() {
const data = _interopRequireDefault(require("strip-ansi"));
_stripAnsi = function () {
return data;
};
return data;
}
function _terminalLink() {
const data = _interopRequireDefault(require("terminal-link"));
_terminalLink = function () {
return data;
};
return data;
}
function _xdl() {
const data = require("xdl");
_xdl = function () {
return data;
};
return data;
}
function _CommandError() {
const data = _interopRequireWildcard(require("../CommandError"));
_CommandError = function () {
return data;
};
return data;
}
function _log() {
const data = _interopRequireDefault(require("../log"));
_log = function () {
return data;
};
return data;
}
function _migration() {
const data = require("../utils/migration");
_migration = function () {
return data;
};
return data;
}
function _ora() {
const data = require("../utils/ora");
_ora = function () {
return data;
};
return data;
}
function _prompts() {
const data = _interopRequireWildcard(require("../utils/prompts"));
_prompts = function () {
return data;
};
return data;
}
function _clearNativeFolder() {
const data = require("./eject/clearNativeFolder");
_clearNativeFolder = function () {
return data;
};
return data;
}
function CreateApp() {
const data = _interopRequireWildcard(require("./utils/CreateApp"));
CreateApp = function () {
return data;
};
return data;
}
function _ProjectUtils() {
const data = require("./utils/ProjectUtils");
_ProjectUtils = function () {
return data;
};
return data;
}
function _extractTemplateAppAsync() {
const data = require("./utils/extractTemplateAppAsync");
_extractTemplateAppAsync = function () {
return data;
};
return data;
}
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
const FEATURED_TEMPLATES = ['----- Managed workflow -----', {
shortName: 'blank',
name: 'expo-template-blank',
description: 'a minimal app as clean as an empty canvas'
}, {
shortName: 'blank (TypeScript)',
name: 'expo-template-blank-typescript',
description: 'same as blank but with TypeScript configuration'
}, {
shortName: 'tabs (TypeScript)',
name: 'expo-template-tabs',
description: 'several example screens and tabs using react-navigation and TypeScript'
}, '----- Bare workflow -----', {
shortName: 'minimal',
name: 'expo-template-bare-minimum',
description: 'bare and minimal, just the essentials to get you started'
}];
const isMacOS = process.platform === 'darwin';
function assertValidName(folderName) {
const validation = CreateApp().validateName(folderName);
if (typeof validation === 'string') {
throw new (_CommandError().default)(`Cannot create an app named ${_chalk().default.red(`"${folderName}"`)}. ${validation}`);
}
const isFolderNameForbidden = CreateApp().isFolderNameForbidden(folderName);
if (isFolderNameForbidden) {
throw new (_CommandError().default)(`Cannot create an app named ${_chalk().default.red(`"${folderName}"`)} because it would conflict with a dependency of the same name.`);
}
}
function parseOptions(command) {
return {
yes: !!command.yes,
yarn: !!command.yarn,
npm: !!command.npm,
install: !!command.install,
template: command.template
};
}
async function assertFolderEmptyAsync(projectRoot, folderName) {
if (!(await CreateApp().assertFolderEmptyAsync({
projectRoot,
folderName,
overwrite: false
}))) {
const message = 'Try using a new directory name, or moving these files.';
_log().default.newLine();
_log().default.nested(message);
_log().default.newLine();
throw new (_CommandError().SilentError)(message);
}
}
async function resolveProjectRootAsync(input) {
let name = input === null || input === void 0 ? void 0 : input.trim();
if (!name) {
try {
const {
answer
} = await (0, _prompts().default)({
type: 'text',
name: 'answer',
message: 'What would you like to name your app?',
initial: 'my-app',
validate: name => {
const validation = CreateApp().validateName(_path().default.basename(_path().default.resolve(name)));
if (typeof validation === 'string') {
return 'Invalid project name: ' + validation;
}
return true;
}
}, {
nonInteractiveHelp: 'Pass the project name using the first argument `expo init <name>`'
});
if (typeof answer === 'string') {
name = answer.trim();
}
} catch (error) {
// Handle the aborted message in a custom way.
if (error.code !== 'ABORTED') {
throw error;
}
}
}
if (!name) {
const message = ['', 'Please choose your app name:', ` ${_chalk().default.green(`${_commander().default.name()} init`)} ${_chalk().default.cyan('<app-name>')}`, '', `Run ${_chalk().default.green(`${_commander().default.name()} init --help`)} for more info`, ''].join('\n');
_log().default.nested(message);
throw new (_CommandError().SilentError)(message);
}
const projectRoot = _path().default.resolve(name);
const folderName = _path().default.basename(projectRoot);
assertValidName(folderName);
await _fsExtra().default.ensureDir(projectRoot);
await assertFolderEmptyAsync(projectRoot, folderName);
return projectRoot;
}
function padEnd(str, width) {
// Pulled from commander for overriding
const len = Math.max(0, width - (0, _stripAnsi().default)(str).length);
return str + Array(len + 1).join(' ');
}
async function resolveTemplateAsync(resolvedTemplate) {
const {
version: newestSdkVersion,
data: newestSdkReleaseData
} = await _xdl().Versions.newestReleasedSdkVersionAsync();
// If the user is opting into a beta then we need to append the template tag explicitly
// in order to not fall back to the latest tag for templates.
let versionParam = '';
if (newestSdkReleaseData !== null && newestSdkReleaseData !== void 0 && newestSdkReleaseData.beta) {
const majorVersion = parseInt(newestSdkVersion, 10);
versionParam = `@sdk-${majorVersion}`;
// If the --template flag is provided without an explicit version, then opt-in to
// the beta version
if (resolvedTemplate && !resolvedTemplate.includes('@')) {
resolvedTemplate = `${resolvedTemplate}${versionParam}`;
}
}
let templateSpec;
if (resolvedTemplate) {
var _templateSpec$name, _templateSpec$fetchSp;
templateSpec = (0, _npmPackageArg().default)(resolvedTemplate);
// For backwards compatibility, 'blank' and 'tabs' are aliases for
// 'expo-template-blank' and 'expo-template-tabs', respectively.
if (templateSpec.name && templateSpec.registry && ['blank', 'tabs', 'bare-minimum'].includes(templateSpec.name)) {
templateSpec.escapedName = `expo-template-${templateSpec.name}`;
templateSpec.name = templateSpec.escapedName;
templateSpec.raw = templateSpec.escapedName;
}
return `${(_templateSpec$name = templateSpec.name) !== null && _templateSpec$name !== void 0 ? _templateSpec$name : templateSpec.raw}@${(_templateSpec$fetchSp = templateSpec.fetchSpec) !== null && _templateSpec$fetchSp !== void 0 ? _templateSpec$fetchSp : 'latest'}`;
}
const descriptionColumn = Math.max(...FEATURED_TEMPLATES.map(t => typeof t === 'object' ? t.shortName.length : 0)) + 2;
const template = await (0, _prompts().selectAsync)({
message: 'Choose a template:',
optionsPerPage: 20,
choices: FEATURED_TEMPLATES.map(template => {
if (typeof template === 'string') {
return _prompts().default.separator(template);
} else {
return {
value: template.name,
title: _chalk().default.bold(padEnd(template.shortName, descriptionColumn)) + template.description.trim(),
short: template.name
};
}
})
}, {
nonInteractiveHelp: '--template: argument is required in non-interactive mode. Valid choices are: "blank", "tabs", "bare-minimum" or any custom template (name of npm package).'
});
return `${template}${versionParam}`;
}
async function actionAsync(incomingProjectRoot, command) {
var _options$template;
(0, _migration().warnMigration)('npx create-expo-app --template');
const options = parseOptions(command);
const deprecatedNameArgument = typeof command.name === 'string' ? command.name : undefined;
if (deprecatedNameArgument) {
// Commander doesn't support using the `--name` argument so it shouldn't have been implemented in the first place.
// Using `--name` will cause other parts of commander to break since it expects a function and `this.name` would be a string.
_log().default.error((0, _chalk().default)`Deprecated: Use {bold expo init [name]} instead of {bold --name [name]}.`);
throw new (_CommandError().AbortCommandError)();
}
// Resolve the name, and projectRoot
let projectRoot;
if (!incomingProjectRoot && options.yes) {
projectRoot = _path().default.resolve(process.cwd());
const folderName = _path().default.basename(projectRoot);
assertValidName(folderName);
await assertFolderEmptyAsync(projectRoot, folderName);
} else {
projectRoot = await resolveProjectRootAsync(incomingProjectRoot);
}
let resolvedTemplate = (_options$template = options.template) !== null && _options$template !== void 0 ? _options$template : null;
// @ts-ignore: This guards against someone passing --template without a name after it.
if (resolvedTemplate === true) {
throw new (_CommandError().default)('Please specify the template name');
}
// Download and sync templates
// TODO(Bacon): revisit
if (options.yes && !resolvedTemplate) {
resolvedTemplate = 'blank';
}
// Supported templates:
// `-t tabs` (tabs, blank, bare-minimum, expo-template-blank-typescript)
// `-t tabs@40`
// `-t tabs@sdk-40`
// `-t tabs@latest`
// `-t expo-template-tabs@latest`
const npmPackageName = await resolveTemplateAsync(resolvedTemplate);
_log().default.debug(`Using template: ${npmPackageName}`);
const projectName = _path().default.basename(projectRoot);
const initialConfig = {
// In older templates the `.name` property is set when extracting template files. This is because older templates have the `.name` property set to `HelloWorld`.
// Newer templates don't need the `.name` property set, so we don't bother with setting it.
expo: {
name: projectName,
slug: projectName
}
};
const extractTemplateStep = (0, _ora().logNewSection)('Downloading template.');
let projectPath;
try {
projectPath = await (0, _extractTemplateAppAsync().extractAndPrepareTemplateAppAsync)(npmPackageName, projectRoot, initialConfig);
extractTemplateStep.succeed('Downloaded template.');
} catch (e) {
extractTemplateStep.fail('Something went wrong while downloading and extracting the template.');
throw e;
}
// Install dependencies
const packageManager = CreateApp().resolvePackageManager(options);
// TODO(Bacon): not this
const isBare = await (0, _clearNativeFolder().directoryExistsAsync)(_path().default.join(projectRoot, 'ios'));
const workflow = isBare ? 'bare' : 'managed';
let hasPodsInstalled = false;
const needsPodsInstalled = _fsExtra().default.existsSync(_path().default.join(projectRoot, 'ios/Podfile'));
if (options.install) {
await installNodeDependenciesAsync(projectRoot, packageManager);
if (needsPodsInstalled) {
hasPodsInstalled = await CreateApp().installCocoaPodsAsync(projectRoot);
}
}
const cdPath = CreateApp().getChangeDirectoryPath(projectRoot);
// Log info
_log().default.addNewLineIfNone();
await logProjectReadyAsync(projectRoot, {
cdPath,
packageManager,
workflow
});
// Log a warning about needing to install node modules
if (!options.install) {
logNodeInstallWarning(cdPath, packageManager);
}
if (needsPodsInstalled && !hasPodsInstalled) {
logCocoaPodsWarning(cdPath);
}
// Initialize Git at the end to ensure all lock files are committed.
await initGitRepoAsync(projectPath);
}
async function installNodeDependenciesAsync(projectRoot, packageManager) {
const installJsDepsStep = (0, _ora().logNewSection)('Installing JavaScript dependencies.');
try {
await CreateApp().installNodeDependenciesAsync(projectRoot, packageManager);
installJsDepsStep.succeed('Installed JavaScript dependencies.');
} catch {
installJsDepsStep.fail(`Something went wrong installing JavaScript dependencies. Check your ${packageManager} logs. Continuing to initialize the app.`);
}
}
/**
* Check if the project is inside an existing Git repo, if so bail out,
* if not then create a new git repo and commit the initial files.
*
* @returns `true` if git is setup.
*/
async function initGitRepoAsync(root) {
// let's see if we're in a git tree
try {
await (0, _spawnAsync().default)('git', ['rev-parse', '--is-inside-work-tree'], {
cwd: root
});
// Log a light notice if we're in a git tree.
_log().default.log(_chalk().default.gray(`Project is already inside of a git repo, skipping ${_chalk().default.bold`git init`}.`));
// Bail out if inside git repo, this makes monorepos a bit easier to setup.
return true;
} catch (e) {
if (e.errno === 'ENOENT') {
_log().default.warn('Unable to initialize git repo. `git` not in PATH.');
return false;
}
}
// not in git tree, so let's init
try {
await (0, _spawnAsync().default)('git', ['init'], {
cwd: root
});
_log().default.debug('Initialized a git repository.');
await (0, _spawnAsync().default)('git', ['add', '--all'], {
cwd: root,
stdio: 'ignore'
});
await (0, _spawnAsync().default)('git', ['commit', '-m', 'Created a new Expo app'], {
cwd: root,
stdio: 'ignore'
});
return true;
} catch (e) {
_log().default.debug('git error:', e);
// no-op -- this is just a convenience and we don't care if it fails
return false;
}
}
function logNodeInstallWarning(cdPath, packageManager) {
_log().default.newLine();
_log().default.nested(`⚠️ Before running your app, make sure you have node modules installed:`);
_log().default.nested('');
if (cdPath) {
// In the case of --yes the project can be created in place so there would be no need to change directories.
_log().default.nested(` cd ${cdPath}/`);
}
_log().default.nested(` ${packageManager === 'npm' ? 'npm install' : 'yarn'}`);
_log().default.nested('');
}
function logCocoaPodsWarning(cdPath) {
if (process.platform !== 'darwin') {
return;
}
_log().default.newLine();
_log().default.nested(`⚠️ Before running your app on iOS, make sure you have CocoaPods installed and initialize the project:`);
_log().default.nested('');
if (cdPath) {
// In the case of --yes the project can be created in place so there would be no need to change directories.
_log().default.nested(` cd ${cdPath}/`);
}
_log().default.nested(` npx pod-install`);
_log().default.nested('');
}
async function logProjectReadyAsync(projectRoot, {
cdPath,
packageManager,
workflow
}) {
_log().default.nested(_chalk().default.bold(`✅ Your project is ready!`));
_log().default.newLine();
// empty string if project was created in current directory
if (cdPath) {
_log().default.nested(`To run your project, navigate to the directory and run one of the following ${packageManager} commands.`);
_log().default.newLine();
_log().default.nested(`- ${_chalk().default.bold('cd ' + cdPath)}`);
} else {
_log().default.nested(`To run your project, run one of the following ${packageManager} commands.`);
_log().default.newLine();
}
if (workflow === 'managed') {
_log().default.nested(`- ${_chalk().default.bold(`${packageManager} start`)} ${_chalk().default.dim(`# you can open iOS, Android, or web from here, or run them directly with the commands below.`)}`);
}
_log().default.nested(`- ${_chalk().default.bold(packageManager === 'npm' ? 'npm run android' : 'yarn android')}`);
let macOSComment = '';
if (!isMacOS && workflow === 'bare') {
macOSComment = ' # you need to use macOS to build the iOS project - use managed workflow if you need to do iOS development without a Mac';
} else if (!isMacOS && workflow === 'managed') {
macOSComment = ' # requires an iOS device or macOS for access to an iOS simulator';
}
_log().default.nested(`- ${_chalk().default.bold(packageManager === 'npm' ? 'npm run ios' : 'yarn ios')}${macOSComment}`);
_log().default.nested(`- ${_chalk().default.bold(packageManager === 'npm' ? 'npm run web' : 'yarn web')}`);
if (workflow === 'bare') {
_log().default.newLine();
_log().default.nested(`💡 You can also open up the projects in the ${_chalk().default.bold('ios')} and ${_chalk().default.bold('android')} directories with their respective IDEs.`);
await addBareUpdatesWarningsAsync(projectRoot);
// TODO: add equivalent of this or some command to wrap it:
// # ios
// $ open -a Xcode ./ios/{PROJECT_NAME}.xcworkspace
// # android
// $ open -a /Applications/Android\\ Studio.app ./android
}
}
async function addBareUpdatesWarningsAsync(projectRoot) {
if (!(await (0, _ProjectUtils().hasExpoUpdatesInstalledAsync)(projectRoot))) {
return;
}
if (await (0, _ProjectUtils().usesOldExpoUpdatesAsync)(projectRoot)) {
_log().default.nested(`🚀 ${(0, _terminalLink().default)('expo-updates', 'https://github.com/expo/expo/blob/main/packages/expo-updates/README.md')} has been configured in your project. Before you do a release build, make sure you run ${_chalk().default.bold('expo publish')}. ${(0, _terminalLink().default)('Learn more.', 'https://expo.fyi/release-builds-with-expo-updates')}`);
return;
}
let didConfigureUpdatesProjectFiles = false;
const username = await _xdl().UserManager.getCurrentUsernameAsync();
if (username) {
try {
await configureUpdatesProjectFilesAsync(projectRoot, username);
didConfigureUpdatesProjectFiles = true;
} catch {}
}
if (didConfigureUpdatesProjectFiles) {
_log().default.nested(`🚀 ${(0, _terminalLink().default)('expo-updates', 'https://github.com/expo/expo/blob/main/packages/expo-updates/README.md')} has been configured in your project. If you publish this project under a different user account than ${_chalk().default.bold(username)}, you'll need to update the configuration in Expo.plist and AndroidManifest.xml before making a release build.`);
} else {
_log().default.nested(`🚀 ${(0, _terminalLink().default)('expo-updates', 'https://github.com/expo/expo/blob/main/packages/expo-updates/README.md')} has been installed in your project. Before you do a release build, you'll need to configure a few values in Expo.plist and AndroidManifest.xml in order for updates to work.`);
}
}
async function configureUpdatesProjectFilesAsync(projectRoot, username) {
// skipSDKVersionRequirement here so that this will work when you use the
// --no-install flag. the tradeoff is that the SDK version field won't be
// filled in, but we should be getting rid of that for expo-updates ASAP
// anyways.
const {
exp
} = (0, _config().getConfig)(projectRoot, {
skipSDKVersionRequirement: true
});
// apply Android config
const androidManifestPath = await _configPlugins().AndroidConfig.Paths.getAndroidManifestAsync(projectRoot);
const androidManifestJSON = await _configPlugins().AndroidConfig.Manifest.readAndroidManifestAsync(androidManifestPath);
const result = await _configPlugins().AndroidConfig.Updates.setUpdatesConfig(projectRoot, exp, androidManifestJSON, username);
await _configPlugins().AndroidConfig.Manifest.writeAndroidManifestAsync(androidManifestPath, result);
// apply iOS config
const iosSourceRoot = _configPlugins().IOSConfig.Paths.getSourceRoot(projectRoot);
const supportingDirectory = _path().default.join(iosSourceRoot, 'Supporting');
const plistFilePath = _path().default.join(supportingDirectory, 'Expo.plist');
let data = _plist().default.parse(_fsExtra().default.readFileSync(plistFilePath, 'utf8'));
data = _configPlugins().IOSConfig.Updates.setUpdatesConfig(projectRoot, exp, data, username);
await _fsExtra().default.writeFile(plistFilePath, _plist().default.build(data));
}
//# sourceMappingURL=initAsync.js.map