├── .travis.yml ├── src ├── index.js ├── hide-library-schemes.js ├── verify-config.js ├── fix-script.js ├── utilities.js └── fix-libraries.js ├── .github ├── PULL_REQUEST_TEMPLATE.md └── ISSUE_TEMPLATE.md ├── .eslintrc.js ├── .editorconfig ├── .gitignore ├── LICENSE ├── index.js ├── package.json ├── lib └── react-native-xcode.sh └── README.md /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | script: 5 | - npm run lint 6 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | fixLibraries: require('./fix-libraries'), 3 | fixScript: require('./fix-script'), 4 | hideLibrarySchemes: require('./hide-library-schemes'), 5 | }; 6 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | ## Description of changes 8 | 9 | 10 | 11 | ## Related issues (if any) 12 | 13 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'extends': [ 3 | 'keystone-react', 4 | ], 5 | 'rules': { 6 | 'react/jsx-indent': 0, 7 | 'react/jsx-no-bind': 'off', 8 | 'react/jsx-boolean-value': 'off', 9 | } 10 | }; -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | root = true 4 | 5 | [*] 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | indent_style = tab 11 | 12 | [*.js] 13 | indent_size = 4 14 | 15 | [*.json] 16 | indent_style = space 17 | indent_size = 2 18 | 19 | [*.yml] 20 | indent_style = space 21 | indent_size = 2 -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 11 | 12 | ### Steps to reproduce the behavior 13 | 14 | ### Expected behavior 15 | 16 | ### Actual behavior 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Kevin Brown 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const yargs = require('yargs'); 3 | 4 | yargs 5 | .usage('$0 command') 6 | .command('all', 'run all bundled commands', yargs => { 7 | const operations = require('./src'); 8 | 9 | for (const key of Object.keys(operations)) { 10 | operations[key](); 11 | } 12 | }) 13 | .command('fix-libraries', 'add any missing build configurations to all xcode projects in node_modules', yargs => { 14 | require('./src/fix-libraries')(yargs.argv.singleProject); 15 | }) 16 | .command('fix-script', 'replace the react native ios bundler with our scheme aware one', yargs => { 17 | require('./src/fix-script')(); 18 | }) 19 | .command('hide-library-schemes', `hide any schemes that come from your node modules directory so they don't clutter up the menu.`, yargs => { 20 | require('./src/hide-library-schemes')(); 21 | }) 22 | .command('verify-config', `check the configuration and ensure we have both a postinstall script and xcodeSchemes configurations.`, yargs => { 23 | require('./src/verify-config')(); 24 | }) 25 | .option('single-project', { 26 | describe: 'Specify Xcode project name to fix a single project', 27 | type: 'string', 28 | default: null, 29 | }) 30 | .demand(1, 'must provide a valid command') 31 | .help('h') 32 | .alias('h', 'help') 33 | .argv; 34 | -------------------------------------------------------------------------------- /src/hide-library-schemes.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk'); 2 | const path = require('path'); 3 | 4 | const utilities = require('./utilities'); 5 | 6 | function updateFile (file, filePath) { 7 | let changed = false; 8 | 9 | if (file.SchemeUserState) { 10 | for (const key of Object.keys(file.SchemeUserState)) { 11 | if (key.endsWith('.xcscheme')) { 12 | const scheme = file.SchemeUserState[key]; 13 | 14 | if (!scheme.hasOwnProperty('isShown') || scheme.isShown) { 15 | scheme.isShown = false; 16 | changed = true; 17 | 18 | console.log(chalk.gray(` ${chalk.green('✔')} [hide-library-schemes]: ${path.dirname(path.relative(process.cwd(), filePath))} ${chalk.green('hidden')}`)); 19 | } else { 20 | console.log(chalk.gray(` - [hide-library-schemes]: ${path.dirname(path.relative(process.cwd(), filePath))} skipped`)); 21 | } 22 | } 23 | } 24 | } 25 | 26 | return changed; 27 | } 28 | 29 | module.exports = function findAndFix () { 30 | // Find all of the pbxproj files we care about. 31 | const userSpecificPattern = './node_modules/**/*.xcodeproj/xcuserdata/*.xcuserdatad/xcschemes/xcschememanagement.plist'; 32 | 33 | console.log(chalk.gray('Hiding schemes from node_modules xcode projects.')); 34 | 35 | utilities.updatePlistsMatchingGlob(userSpecificPattern, (err, file, filePath) => { 36 | return updateFile(file, filePath); 37 | }); 38 | }; 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-schemes-manager", 3 | "description": "Helps to manage React Native XCode projects that use multiple schemes to manage things like environment variables.", 4 | "version": "2.0.0", 5 | "main": "index.js", 6 | "bin": { 7 | "react-native-schemes-manager": "index.js" 8 | }, 9 | "scripts": { 10 | "postinstall": "node index.js verify-config", 11 | "lint": "eslint .", 12 | "lint-fix": "eslint . --fix" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/thekevinbrown/react-native-schemes-manager.git" 17 | }, 18 | "keywords": [ 19 | "react", 20 | "native", 21 | "xcode", 22 | "scheme", 23 | "build", 24 | "configuration", 25 | "environment", 26 | "variables" 27 | ], 28 | "author": "Kevin Brown", 29 | "license": "MIT", 30 | "bugs": { 31 | "url": "https://github.com/thekevinbrown/react-native-schemes-manager/issues" 32 | }, 33 | "homepage": "https://github.com/thekevinbrown/react-native-schemes-manager#readme", 34 | "dependencies": { 35 | "chalk": "^2.4.2", 36 | "glob": "^7.1.1", 37 | "path": "^0.12.7", 38 | "plist": "^3.0.1", 39 | "xcode": "^2.0.0", 40 | "yargs": "^13.2.1" 41 | }, 42 | "devDependencies": { 43 | "eslint": "^5.14.1", 44 | "eslint-config-keystone": "^3.0.0", 45 | "eslint-config-keystone-react": "^1.0.0", 46 | "eslint-plugin-react": "^7.12.4" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/verify-config.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk'); 2 | const path = require('path'); 3 | 4 | const utilities = require('./utilities'); 5 | 6 | module.exports = function verifyConfig () { 7 | let packageJson = null; 8 | 9 | const project = utilities.getClosestLikelyReactNativeProjectPath(); 10 | if (project) packageJson = require(path.join(project, 'package.json')); 11 | 12 | if (!packageJson) { 13 | console.log(chalk.red('=========================================================================================')); 14 | console.log(chalk.red(' Error: Unable to locate a react-native project directory.')); 15 | console.log(chalk.red(' Are you sure you\'re running in a directory with an xcode project under ios/?\n')); 16 | console.log(chalk.red(' It\'s quite likely that react-native-schemes-manager will fail in this project.')); 17 | console.log(chalk.red('=========================================================================================')); 18 | return; 19 | } 20 | 21 | const postinstall = packageJson.scripts && packageJson.scripts.postinstall; 22 | const postinstallInvalid = !postinstall || typeof postinstall !== 'string' || postinstall.indexOf('react-native-schemes-manager') < 0; 23 | 24 | const xcodeSchemes = packageJson.xcodeSchemes; 25 | const xcodeSchemesInvalid = !xcodeSchemes || !(Array.isArray(xcodeSchemes.Debug) || Array.isArray(xcodeSchemes.Release)); 26 | 27 | if (postinstallInvalid || xcodeSchemesInvalid) { 28 | console.log(chalk.yellow('=========================================================================================')); 29 | 30 | if (postinstallInvalid) { 31 | console.log(chalk.yellow(' It looks like you haven\'t yet configured a postinstall script')); 32 | console.log(chalk.yellow(' for react-native-schemes-manager.\n')); 33 | } 34 | 35 | if (xcodeSchemesInvalid) { 36 | console.log(chalk.yellow(' It looks like you haven\'t yet configured your xcode schemes')); 37 | console.log(chalk.yellow(' for react-native-schemes-manager.\n')); 38 | } 39 | 40 | console.log(chalk.yellow(' Please visit https://github.com/thekevinbrown/react-native-schemes-manager#installation')); 41 | console.log(chalk.yellow(' and complete the installation process.')); 42 | console.log(chalk.yellow('=========================================================================================')); 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /src/fix-script.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk'); 2 | const path = require('path'); 3 | 4 | const utilities = require('./utilities'); 5 | 6 | function updateProject (project) { 7 | console.log(path.dirname(path.relative(process.cwd(), project.filepath))); 8 | 9 | const section = project.hash.project.objects.PBXShellScriptBuildPhase; 10 | 11 | for (const key of Object.keys(section)) { 12 | // Look for the React native script. 13 | const step = section[key]; 14 | 15 | if (step && step.shellScript && step.shellScript.indexOf('react-native-xcode.sh') >= 0) { 16 | // Found it! 17 | // Need to add our actual mappings to the project. 18 | const mappings = utilities.getMappings(); 19 | const configurations = (mappings.Debug || []).join('|'); 20 | const scriptSettings = (mappings.settings && mappings.settings['fix-script']) || {}; 21 | const nodeCommand = scriptSettings.nodeCommand ? scriptSettings.nodeCommand + ' ' : ''; 22 | const env = scriptSettings.env || []; 23 | 24 | const devConfigs = `\\"+(${configurations}${configurations.length ? '|' : ''}Debug)\\"`; 25 | env.DEVELOPMENT_BUILD_CONFIGURATIONS = devConfigs; 26 | env.NODE_BINARY = env.NODE_BINARY || 'node'; 27 | 28 | const exports = Object.keys(env) 29 | .map((key) => [key, env[key]]) 30 | .map(([key, value]) => `export ${key}=${value}`); 31 | 32 | const runCommand = `${nodeCommand}../node_modules/react-native-schemes-manager/lib/react-native-xcode.sh`; 33 | 34 | const commands = [ 35 | ...exports, 36 | runCommand, 37 | ]; 38 | 39 | const newScript = `"${commands.join('\\n')}"`; 40 | 41 | if (step.shellScript === newScript) { 42 | // It's already up to date. 43 | console.log(chalk.gray(` - [fix-script]: ${path.dirname(path.relative(process.cwd(), project.filepath))} skipped`)); 44 | return false; 45 | } else { 46 | step.shellScript = newScript; 47 | 48 | console.log(chalk.gray(` ${chalk.green('✔')} [fix-script]: ${path.dirname(path.relative(process.cwd(), project.filepath))} ${chalk.green('fixed')}`)); 49 | return true; 50 | } 51 | } 52 | } 53 | } 54 | 55 | module.exports = function findAndFix () { 56 | let projectDirectory = utilities.getMappings().projectDirectory; 57 | if (!projectDirectory) { 58 | projectDirectory = 'ios'; 59 | } 60 | 61 | // Find all of the pbxproj files we care about. 62 | const pattern = `./${projectDirectory}/*.xcodeproj/project.pbxproj`; 63 | 64 | utilities.updateProjectsMatchingGlob(pattern, (err, project) => { 65 | if (err) { 66 | return console.error(chalk.red(`⃠ [fix-script]: Error!`, err)); 67 | } 68 | 69 | return updateProject(project); 70 | }); 71 | }; 72 | -------------------------------------------------------------------------------- /src/utilities.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const glob = require('glob'); 3 | const path = require('path'); 4 | const plist = require('plist'); 5 | const xcode = require('xcode'); 6 | 7 | module.exports = { 8 | getClosestLikelyReactNativeProjectPath () { 9 | let currentPath = process.cwd(); 10 | let nextPath; 11 | 12 | // first we need to get to the point where we're not in the node_modules directory 13 | while (currentPath.indexOf('node_modules') > -1) { 14 | currentPath = path.resolve(currentPath, '..'); 15 | } 16 | 17 | // now find the package.json for the project 18 | const pattern = 'package.json'; 19 | let files = glob.sync(path.join(currentPath, pattern)); 20 | while (files.length === 0) { 21 | nextPath = path.resolve(currentPath, '..'); 22 | 23 | if (nextPath === currentPath) { 24 | // We're at the root of the filesystem and didn't find anything. 25 | return null; 26 | } 27 | 28 | currentPath = nextPath; 29 | 30 | files = glob.sync(path.join(currentPath, pattern)); 31 | } 32 | 33 | return currentPath; 34 | }, 35 | getFilesMatchingGlob (pattern, callback) { 36 | // Find all of the files we care about. 37 | const project = this.getClosestLikelyReactNativeProjectPath(); 38 | if (!project) return callback(new Error('Unable to find project path.')); 39 | 40 | glob(path.join(project, pattern), { follow: true }, (err, files) => { 41 | if (err) return callback(err); 42 | 43 | // Go through each project. 44 | for (const filePath of files) { 45 | callback(null, filePath); 46 | } 47 | }); 48 | }, 49 | updateProjectsMatchingGlob (pattern, callback) { 50 | this.getFilesMatchingGlob(pattern, (err, filePath) => { 51 | if (!filePath) return callback(new Error('Unable to find project path.')); 52 | if (filePath.endsWith(path.join('Pods.xcodeproj', 'project.pbxproj'))) { 53 | // The Pods.xcodeproj file isn't currently able to be parsed 54 | // by node-xcode: 55 | // https://github.com/alunny/node-xcode/issues/127 56 | // 57 | // We don't actually need to manipulate it so we'll go ahead and ignore. 58 | return; 59 | } 60 | 61 | const project = xcode.project(filePath); 62 | 63 | try { 64 | project.parseSync(); 65 | } catch (error) { 66 | return callback(error); 67 | } 68 | 69 | // And fix it. 70 | if (callback(null, project)) { 71 | fs.writeFileSync(filePath, project.writeSync()); 72 | } 73 | }); 74 | }, 75 | updatePlistsMatchingGlob (pattern, callback) { 76 | this.getFilesMatchingGlob(pattern, (err, filePath) => { 77 | if (!filePath) return callback(new Error('Unable to find plist path.')); 78 | 79 | const file = plist.parse(fs.readFileSync(filePath, 'utf8')); 80 | 81 | // And fix it. 82 | if (callback(null, file, filePath)) { 83 | fs.writeFileSync(filePath, plist.build(file)); 84 | } 85 | }); 86 | }, 87 | getMappings () { 88 | const project = this.getClosestLikelyReactNativeProjectPath(); 89 | const packageJson = require(path.join(project, 'package.json')); 90 | 91 | if (!packageJson.xcodeSchemes) { 92 | throw new Error( 93 | 'Please configure schemes on your project. For more information, see https://github.com/thekevinbrown/react-native-schemes-manager/blob/master/README.md' 94 | ); 95 | } 96 | 97 | return packageJson.xcodeSchemes; 98 | }, 99 | }; 100 | -------------------------------------------------------------------------------- /src/fix-libraries.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk'); 2 | const path = require('path'); 3 | 4 | const utilities = require('./utilities'); 5 | 6 | function configListForConfig (configLists, configUuid) { 7 | for (const listUuid of Object.keys(configLists)) { 8 | if (listUuid.endsWith('_comment')) continue; 9 | 10 | const configList = configLists[listUuid]; 11 | 12 | if (configList.buildConfigurations.find(config => config.value === configUuid)) { 13 | return configList; 14 | } 15 | } 16 | 17 | return null; 18 | } 19 | 20 | function updateProject (project) { 21 | 22 | const configs = project.pbxXCBuildConfigurationSection(); 23 | const configLists = project.pbxXCConfigurationList(); 24 | let changed = false; 25 | const { Debug, Release } = utilities.getMappings(); 26 | const mappings = { 27 | Debug, 28 | Release, 29 | }; 30 | 31 | // Go through each mapping in our debug map and figure out if we need to clone it. 32 | for (const sourceBuildConfig of Object.keys(mappings)) { 33 | for (const destinationBuildConfig of mappings[sourceBuildConfig]) { 34 | // Do we have the clone already? 35 | const buildConfig = project.getBuildConfigByName(destinationBuildConfig); 36 | 37 | // If we get an empty object back, then it's not able to find the build config. 38 | if (Object.keys(buildConfig).length === 0) { 39 | // Ok, we need to create our clone of the build configs they've asked for and add it to the config lists. 40 | const sourceConfigs = project.getBuildConfigByName(sourceBuildConfig); 41 | 42 | // There are actually multiple of the same configs spread across multiple lists. Clone them all to the destination build configs. 43 | for (const key of Object.keys(sourceConfigs)) { 44 | const sourceConfig = sourceConfigs[key]; 45 | const configList = configListForConfig(configLists, key); 46 | 47 | if (!configList) throw new Error(`Unable to find config list for build configuration: ${sourceConfig.name}`); 48 | 49 | // Copy that bad boy. 50 | const clone = JSON.parse(JSON.stringify(sourceConfig)); 51 | clone.name = `"${destinationBuildConfig}"`; 52 | 53 | const configurationUuid = project.generateUuid(); 54 | const configurationCommentKey = `${configurationUuid}_comment`; 55 | 56 | configs[configurationUuid] = clone; 57 | configs[configurationCommentKey] = destinationBuildConfig; 58 | configList.buildConfigurations.push({ value: configurationUuid, comment: destinationBuildConfig }); 59 | } 60 | 61 | console.log(chalk.gray(` ${chalk.green('✔')} [fix-libraries]: ${chalk.green(sourceBuildConfig + ' -> ' + destinationBuildConfig + ' created')} in ${path.dirname(path.relative(process.cwd(), project.filepath))}`)); 62 | 63 | changed = true; 64 | } else { 65 | console.log(chalk.gray(` - [fix-libraries]: ${sourceBuildConfig} -> ${destinationBuildConfig} skipped in ${path.dirname(path.relative(process.cwd(), project.filepath))}`)); 66 | } 67 | } 68 | } 69 | 70 | return changed; 71 | } 72 | 73 | module.exports = function findAndFix (singleProject = null) { 74 | // Find all of the pbxproj files we care about. 75 | let pattern = './node_modules/**/*.xcodeproj/project.pbxproj'; 76 | // Search only for specified project 77 | if (singleProject) { 78 | pattern = './node_modules/**/' + singleProject + '.xcodeproj/project.pbxproj'; 79 | } 80 | 81 | utilities.updateProjectsMatchingGlob(pattern, (err, project) => { 82 | if (err) { 83 | return console.error(chalk.red(`⃠ [fix-libraries]: Error!`, err)); 84 | } 85 | 86 | return updateProject(project); 87 | }); 88 | }; 89 | -------------------------------------------------------------------------------- /lib/react-native-xcode.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright (c) Facebook, Inc. and its affiliates. 3 | # 4 | # This source code is licensed under the MIT license found in the 5 | # LICENSE file in the root directory of this source tree. 6 | # 7 | # Bundle React Native app's code and image assets. 8 | # This script is supposed to be invoked as part of Xcode build process 9 | # and relies on environment variables (including PWD) set by Xcode 10 | 11 | # Print commands before executing them (useful for troubleshooting) 12 | set -x 13 | DEST=$CONFIGURATION_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH 14 | 15 | # Enables iOS devices to get the IP address of the machine running Metro Bundler 16 | if [[ "$CONFIGURATION" = *Debug* && ! "$PLATFORM_NAME" == *simulator ]]; then 17 | IP=$(ipconfig getifaddr en0) 18 | if [ -z "$IP" ]; then 19 | IP=$(ifconfig | grep 'inet ' | grep -v ' 127.' | cut -d\ -f2 | awk 'NR==1{print $1}') 20 | fi 21 | 22 | echo "$IP" > "$DEST/ip.txt" 23 | fi 24 | 25 | if [[ "$SKIP_BUNDLING" ]]; then 26 | echo "SKIP_BUNDLING enabled; skipping." 27 | exit 0; 28 | fi 29 | 30 | # Enable pattern matching 31 | shopt -s extglob 32 | 33 | # And on to your previously scheduled React Native build script programming. 34 | eval 'case "$CONFIGURATION" in 35 | $DEVELOPMENT_BUILD_CONFIGURATIONS) 36 | echo "Debug build!" 37 | if [[ "$PLATFORM_NAME" == *simulator ]]; then 38 | if [[ "$FORCE_BUNDLING" ]]; then 39 | echo "FORCE_BUNDLING enabled; continuing to bundle." 40 | else 41 | echo "Skipping bundling in Debug for the Simulator (since the packager bundles for you). Use the FORCE_BUNDLING flag to change this behavior." 42 | exit 0; 43 | fi 44 | else 45 | echo "Bundling for physical device. Use the SKIP_BUNDLING flag to change this behavior." 46 | fi 47 | 48 | DEV=true 49 | ;; 50 | "") 51 | echo "$0 must be invoked by Xcode" 52 | exit 1 53 | ;; 54 | *) 55 | echo "Production build!" 56 | DEV=false 57 | ;; 58 | esac' 59 | 60 | # Path to react-native folder inside node_modules 61 | REACT_NATIVE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../react-native" && pwd)" 62 | SCHEMES_MANAGER_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" 63 | # The project should be located next to where react-native is installed 64 | # in node_modules. 65 | PROJECT_ROOT=${PROJECT_ROOT:-"$REACT_NATIVE_DIR/../.."} 66 | 67 | cd "$PROJECT_ROOT" || exit 68 | 69 | # Define NVM_DIR and source the nvm.sh setup script 70 | [ -z "$NVM_DIR" ] && export NVM_DIR="$HOME/.nvm" 71 | 72 | # Define entry file 73 | if [[ "$ENTRY_FILE" ]]; then 74 | # Use ENTRY_FILE defined by user 75 | : 76 | elif [[ -s "index.ios.js" ]]; then 77 | ENTRY_FILE=${1:-index.ios.js} 78 | else 79 | ENTRY_FILE=${1:-index.js} 80 | fi 81 | 82 | if [[ -s "$HOME/.nvm/nvm.sh" ]]; then 83 | . "$HOME/.nvm/nvm.sh" 84 | elif [[ -x "$(command -v brew)" && -s "$(brew --prefix nvm)/nvm.sh" ]]; then 85 | . "$(brew --prefix nvm)/nvm.sh" 86 | fi 87 | 88 | # Set up the nodenv node version manager if present 89 | if [[ -x "$HOME/.nodenv/bin/nodenv" ]]; then 90 | eval "$("$HOME/.nodenv/bin/nodenv" init -)" 91 | elif [[ -x "$(command -v brew)" && -x "$(brew --prefix nodenv)/bin/nodenv" ]]; then 92 | eval "$("$(brew --prefix nodenv)/bin/nodenv" init -)" 93 | fi 94 | 95 | # Set up the ndenv of anyenv if preset 96 | if [[ ! -x node && -d ${HOME}/.anyenv/bin ]]; then 97 | export PATH=${HOME}/.anyenv/bin:${PATH} 98 | if [[ "$(anyenv envs | grep -c ndenv )" -eq 1 ]]; then 99 | eval "$(anyenv init -)" 100 | fi 101 | fi 102 | 103 | # check and assign NODE_BINARY env 104 | # shellcheck source=/dev/null 105 | source "$REACT_NATIVE_DIR/scripts/node-binary.sh" 106 | 107 | [ -z "$NODE_ARGS" ] && export NODE_ARGS="" 108 | 109 | [ -z "$CLI_PATH" ] && export CLI_PATH="$REACT_NATIVE_DIR/cli.js" 110 | 111 | [ -z "$BUNDLE_COMMAND" ] && BUNDLE_COMMAND="bundle" 112 | 113 | if [[ -z "$BUNDLE_CONFIG" ]]; then 114 | CONFIG_ARG="" 115 | else 116 | CONFIG_ARG="--config $BUNDLE_CONFIG" 117 | fi 118 | 119 | BUNDLE_FILE="$DEST/main.jsbundle" 120 | 121 | "$NODE_BINARY" $NODE_ARGS "$CLI_PATH" $BUNDLE_COMMAND \ 122 | $CONFIG_ARG \ 123 | --entry-file "$ENTRY_FILE" \ 124 | --platform ios \ 125 | --dev $DEV \ 126 | --reset-cache \ 127 | --bundle-output "$BUNDLE_FILE" \ 128 | --assets-dest "$DEST" \ 129 | $EXTRA_PACKAGER_ARGS 130 | 131 | # XCode randomly generates user specific workspace files whenever it feels like it. 132 | # We want these hidden at all times, so go ahead and clean up if they're showing now. 133 | cd "$SCHEMES_MANAGER_DIR/../.." 134 | $NODE_BINARY "$SCHEMES_MANAGER_DIR/index.js" hide-library-schemes 135 | 136 | if [[ $DEV != true && ! -f "$BUNDLE_FILE" ]]; then 137 | echo "error: File $BUNDLE_FILE does not exist. This must be a bug with" >&2 138 | echo "React Native, please report it here: https://github.com/facebook/react-native/issues" 139 | exit 2 140 | fi 141 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Native Schemes Manager 2 | 3 | > ⚠️ This package is no longer necessary since `react-native` version `0.60` or above. React Native now supports the schemes correctly without any workaround. 4 | 5 | Now you can use Xcode build configurations / schemes with React Native without pulling your hair out. 6 | 7 | [![Build Status](https://travis-ci.org/thekevinbrown/react-native-schemes-manager.svg?branch=master)](https://travis-ci.org/thekevinbrown/react-native-schemes-manager) 8 | 9 | If your app has multiple environments, you'd probably like to be able to manage these the way native developers have been doing for years, Xcode schemes and build configurations. 10 | 11 | By default, React Native doesn't handle these particularly well, and the ability to launch dev tools or run in debug mode is impacted. This package fixes these problems. 12 | 13 | _Made by [Kevin Brown](https://twitter.com/kevinbrowntech), supported by [Tech Insite](http://techin.site/). Thank you for making this project possible!_ 14 | 15 | ## Installation 16 | 17 | ``` 18 | yarn add --dev react-native-schemes-manager 19 | ``` 20 | 21 | or 22 | 23 | ``` 24 | npm install --save-dev react-native-schemes-manager 25 | ``` 26 | 27 | Once the package is installed in your project, you just need to configure it by adding an `xcodeSchemes` section to your `package.json` and adding a `postinstall` script which will re-run the script whenever you add or remove packages to/from your project: 28 | 29 | ```js 30 | { 31 | "name": "your-awesome-app", 32 | "version": "1.0.0", 33 | "scripts": { 34 | "postinstall": "react-native-schemes-manager all" 35 | }, 36 | "xcodeSchemes": { 37 | "Debug": ["Staging", "Preflight"], 38 | "Release": ["Beta"], 39 | "projectDirectory": "iOS", 40 | "settings": { 41 | "fix-script": { 42 | // Additional environment variables your code might need 43 | // They will be set using an `export` right before the scheme manager script runs. 44 | "env": { 45 | "NODE_BINARY": "/usr/bin/node6", 46 | "LOGGING_LEVEL": "4" 47 | }, 48 | // If you need to use some other command to run the scheme manager script 49 | // (for example, if you use a library like Sentry). 50 | "nodeCommand": "$NODE_BINARY ../node_modules/@sentry/cli/bin/sentry-cli react-native xcode" 51 | } 52 | } 53 | }, 54 | } 55 | ``` 56 | 57 | This configuration will copy the "Debug" build configuration to the "Staging" and "Preflight" build configurations in all your dependent library projects. This configuration will also copy the "Release" build configuration to the "Beta" build configuration for all of the dependent libraries. The "Debug" and "Release" arrays are both optional. 58 | 59 | If your Xcode project is not in the default directory, "ios", you can specify the directory using the optional `projectDirectory` configuration. The above configuration specifies that the project is in an "iOS" directory instead (directories are case sensitive). 60 | 61 | You can also pass in settings for each command. The only one currently supported is `fix-script`. 62 | 63 | ## What Then? 64 | 65 | The package is able to do three things: 66 | 67 | - Swap its own version of react-native-xcode.sh in instead of the stock one that assumes all debug schemes are named 'Debug'. 68 | - Add your build configurations to all library Xcode projects. 69 | - Hide any schemes that come from your library projects so they don't show up in the menu. 70 | 71 | As long as `react-native-schemes-manager all` has run whenever you add react native modules, you should be good to go. 72 | 73 | ## It's not working! 74 | 75 | A good starting point for troubleshooting is: 76 | 77 | - Completely quit Xcode. 78 | - `rm -rf node_modules` 79 | - `yarn` or `npm i` 80 | - Re open Xcode 81 | - Product -> Clean 82 | - Run 83 | 84 | If you're still having trouble, post an issue so we can look into it. 85 | 86 | ## Running Manually 87 | 88 | You can run the three parts of this package on demand by running either: 89 | 90 | - `react-native-schemes-manager fix-libraries`: Adds your build configurations to all library Xcode projects. 91 | - Specify the `--single-project` parameter to fix a single Xcode project. Example: `--single-project=React` 92 | - `react-native-schemes-manager fix-script`: Swaps a schemes aware build script in instead of the stock react native one. 93 | - `react-native-schemes-manager hide-library-schemes`: Hides any build schemes that come from your node_modules folder xcodeproj files as you don't usually want to see them anyway. 94 | 95 | And of course you can run all 3 easily if you'd prefer: 96 | 97 | - `react-native-schemes-manager all`: Runs all the scripts above. 98 | 99 | The best way to give yourself a manual trigger for this is add to your `package.json` scripts section like so: 100 | 101 | ```json 102 | { 103 | "name": "your-awesome-app", 104 | "version": "1.0.0", 105 | "scripts": { 106 | "fix-xcode": "react-native-schemes-manager all", 107 | "postinstall": "react-native-schemes-manager all" 108 | } 109 | } 110 | ``` 111 | 112 | You can then `yarn run fix-xcode` or `npm run fix-xcode` which will run the cleanup scripts on demand. 113 | 114 | ## Uninstalling 115 | 116 | If you decide this isn't working out for you, we'd love to know why and to try to fix it. Please [open an issue](https://github.com/thekevinbrown/react-native-schemes-manager/issues/new). 117 | 118 | But if you still want to get rid of this and revert the changes to your project, you can follow these steps: 119 | 120 | 1. `yarn remove react-native-schemes-manager` or `npm remove react-native-schemes-manager --save-dev` 121 | 1. Open your Xcode project. 122 | 1. Click on the project itself on the left sidebar to ensure it's selected. 123 | 1. Click the "Build Phases" tab. 124 | 1. Expand the "Bundle React Native code and images" phase. 125 | 1. Change the script to: 126 | 127 | ``` 128 | export NODE_BINARY=node 129 | ../node_modules/react-native/scripts/react-native-xcode.sh 130 | ``` 131 | 132 | Then, you can revert the changes to the installed libraries by: 133 | 134 | 1. Quitting Xcode. 135 | 1. `rm -rf node_modules` 136 | 1. `yarn` or `npm i` 137 | 1. Re-open Xcode. 138 | 139 | You're back to where you started! 140 | 141 | ## Further Reading 142 | 143 | These are some great articles about related topics in case you're hungry for more: 144 | 145 | - [📝 Migrating iOS App Through Multiple Environments](http://www.blackdogfoundry.com/blog/migrating-ios-app-through-multiple-environments/): Explains how Xcode is handling this situation in a detailed way. 146 | - [📝 How to set up multiple schemes & configurations in Xcode for your React Native iOS app](https://zeemee.engineering/how-to-set-up-multiple-schemes-configurations-in-xcode-for-your-react-native-ios-app-7da4b5237966#.vsq9mlgv8): The inspiration and approach for this package, unfortunately written in Ruby. I wanted a pure Node build pipeline and I didn't want to have to muck around with per package configuration. 147 | 148 | ## License 149 | 150 | Licensed under the MIT License, Copyright © 2017 Kevin Brown. 151 | 152 | See [LICENSE](./LICENSE) for more information. 153 | 154 | ## Acknowledgements 155 | 156 | This project builds on a lot of earlier work by clever folks all around the world. We'd like to thank Alek Hurst and Craig Edwards who contributed ideas and inspiration, without which this project wouldn't have been possible. 157 | --------------------------------------------------------------------------------