├── .github └── workflows │ ├── android-reusable-workflow.yaml │ ├── ios-reusable-workflow.yaml │ ├── nightly.yaml │ └── pr.yaml ├── .gitignore ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── SECURITY.md ├── android ├── README.md ├── forcedroid.js ├── package.json └── shared ├── hybrid ├── README.md ├── forcehybrid.js ├── package.json └── shared ├── install.js ├── ios ├── README.md ├── forceios.js ├── package.json └── shared ├── pack └── pack.js ├── react ├── README.md ├── forcereact.js ├── package.json └── shared ├── release ├── README.md ├── common.js ├── package.json ├── release.js └── setup_test_branches.js ├── setversion.sh ├── sfdx ├── .gitignore ├── README.md ├── bin │ └── run ├── generate_oclif.js ├── generate_readme.sh ├── package.json └── shared ├── shared ├── commandLineUtils.js ├── configHelper.js ├── constants.js ├── createHelper.js ├── example.userstore.json ├── example.usersyncs.json ├── jsonChecker.js ├── oclifAdapter.js ├── outputColors.js ├── store.schema.json ├── syncs.schema.json ├── templateHelper.js └── utils.js └── test └── test_force.js /.github/workflows/android-reusable-workflow.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_call: 3 | inputs: 4 | is_pr: 5 | type: boolean 6 | default: false 7 | 8 | jobs: 9 | forcedroid: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | if: ${{ inputs.is_pr }} 14 | with: 15 | ref: ${{ github.event.pull_request.head.sha }} 16 | - uses: actions/checkout@v4 17 | if: ${{ ! inputs.is_pr }} 18 | with: 19 | ref: ${{ github.head_ref }} 20 | - name: Install Dependencies 21 | run: node ./install.js 22 | - uses: actions/setup-java@v4 23 | with: 24 | distribution: 'zulu' 25 | java-version: '21' 26 | - name: Setup Android SDK 27 | uses: android-actions/setup-android@v3 28 | - uses: gradle/actions/setup-gradle@v4 29 | with: 30 | gradle-version: "8.7" 31 | add-job-summary: on-failure 32 | add-job-summary-as-pr-comment: on-failure 33 | - name: Build all android native templates 34 | run: ./test/test_force.js --exit-on-failure --cli=forcedroid 35 | 36 | forcedroid-sfdx: 37 | runs-on: ubuntu-latest 38 | steps: 39 | - uses: actions/checkout@v4 40 | if: ${{ inputs.is_pr }} 41 | with: 42 | ref: ${{ github.event.pull_request.head.sha }} 43 | - uses: actions/checkout@v4 44 | if: ${{ ! inputs.is_pr }} 45 | with: 46 | ref: ${{ github.head_ref }} 47 | - name: Install Dependencies 48 | run: node ./install.js 49 | - uses: actions/setup-java@v4 50 | with: 51 | distribution: 'zulu' 52 | java-version: '21' 53 | - name: Setup Android SDK 54 | uses: android-actions/setup-android@v3 55 | - uses: gradle/actions/setup-gradle@v4 56 | with: 57 | gradle-version: "8.7" 58 | add-job-summary: on-failure 59 | add-job-summary-as-pr-comment: on-failure 60 | - name: Install SFDX 61 | run: npm install -g @salesforce/cli 62 | - name: Build all android native templates wth SFDX 63 | run: ./test/test_force.js --exit-on-failure --cli=forcedroid --use-sfdx 64 | 65 | forcehybrid-android: 66 | runs-on: ubuntu-latest 67 | steps: 68 | - uses: actions/checkout@v4 69 | if: ${{ inputs.is_pr }} 70 | with: 71 | ref: ${{ github.event.pull_request.head.sha }} 72 | - uses: actions/checkout@v4 73 | if: ${{ ! inputs.is_pr }} 74 | with: 75 | ref: ${{ github.head_ref }} 76 | - name: Install Dependencies 77 | run: node ./install.js 78 | - uses: actions/setup-java@v4 79 | with: 80 | distribution: 'zulu' 81 | java-version: '21' 82 | - name: Setup Android SDK 83 | uses: android-actions/setup-android@v3 84 | - uses: gradle/actions/setup-gradle@v4 85 | with: 86 | gradle-version: "8.7" 87 | add-job-summary: on-failure 88 | add-job-summary-as-pr-comment: on-failure 89 | - name: Install SFDX 90 | run: npm install -g @salesforce/cli 91 | - name: Install Cordova 92 | run: npm install -g cordova 93 | - name: Build all android hybrid templates 94 | run: ./test/test_force.js --exit-on-failure --cli=forcehybrid --os=android --no-plugin-update 95 | 96 | forcehybrid-android-sfdx: 97 | runs-on: ubuntu-latest 98 | steps: 99 | - uses: actions/checkout@v4 100 | if: ${{ inputs.is_pr }} 101 | with: 102 | ref: ${{ github.event.pull_request.head.sha }} 103 | - uses: actions/checkout@v4 104 | if: ${{ ! inputs.is_pr }} 105 | with: 106 | ref: ${{ github.head_ref }} 107 | - name: Install Dependencies 108 | run: node ./install.js 109 | - uses: actions/setup-java@v4 110 | with: 111 | distribution: 'zulu' 112 | java-version: '21' 113 | - name: Setup Android SDK 114 | uses: android-actions/setup-android@v3 115 | - uses: gradle/actions/setup-gradle@v4 116 | with: 117 | gradle-version: "8.7" 118 | add-job-summary: on-failure 119 | add-job-summary-as-pr-comment: on-failure 120 | - name: Install SFDX 121 | run: npm install -g @salesforce/cli 122 | - name: Install Cordova 123 | run: npm install -g cordova 124 | - name: Build all android hybrid templates with SFDX 125 | run: ./test/test_force.js --exit-on-failure --cli=forcehybrid --os=android --use-sfdx --no-plugin-update 126 | 127 | forcereact-android: 128 | runs-on: ubuntu-latest 129 | steps: 130 | - uses: actions/checkout@v4 131 | if: ${{ inputs.is_pr }} 132 | with: 133 | ref: ${{ github.event.pull_request.head.sha }} 134 | - uses: actions/checkout@v4 135 | if: ${{ ! inputs.is_pr }} 136 | with: 137 | ref: ${{ github.head_ref }} 138 | - name: Install Dependencies 139 | run: node ./install.js 140 | - uses: actions/setup-java@v4 141 | with: 142 | distribution: 'zulu' 143 | java-version: '21' 144 | - name: Setup Android SDK 145 | uses: android-actions/setup-android@v3 146 | - uses: gradle/actions/setup-gradle@v4 147 | with: 148 | gradle-version: "8.7" 149 | add-job-summary: on-failure 150 | add-job-summary-as-pr-comment: on-failure 151 | - name: Install SFDX 152 | run: npm install -g @salesforce/cli 153 | - name: Install Typescript 154 | run: npm install -g typescript 155 | - name: Build all android react native templates with SFDX 156 | run: ./test/test_force.js --exit-on-failure --cli=forcereact --os=android 157 | 158 | forcereact-android-sfdx: 159 | runs-on: ubuntu-latest 160 | steps: 161 | - uses: actions/checkout@v4 162 | if: ${{ inputs.is_pr }} 163 | with: 164 | ref: ${{ github.event.pull_request.head.sha }} 165 | - uses: actions/checkout@v4 166 | if: ${{ ! inputs.is_pr }} 167 | with: 168 | ref: ${{ github.head_ref }} 169 | - name: Install Dependencies 170 | run: node ./install.js 171 | - uses: actions/setup-java@v4 172 | with: 173 | distribution: 'zulu' 174 | java-version: '21' 175 | - name: Setup Android SDK 176 | uses: android-actions/setup-android@v3 177 | - uses: gradle/actions/setup-gradle@v4 178 | with: 179 | gradle-version: "8.7" 180 | add-job-summary: on-failure 181 | add-job-summary-as-pr-comment: on-failure 182 | - name: Install SFDX 183 | run: npm install -g @salesforce/cli 184 | - name: Install Typescript 185 | run: npm install -g typescript 186 | - name: Build all android react native templates with SFDX 187 | run: ./test/test_force.js --exit-on-failure --cli=forcereact --os=android --use-sfdx -------------------------------------------------------------------------------- /.github/workflows/ios-reusable-workflow.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_call: 3 | inputs: 4 | is_pr: 5 | type: boolean 6 | default: false 7 | macos: 8 | default: macos-15 9 | required: false 10 | type: string 11 | 12 | jobs: 13 | forceios: 14 | runs-on: ${{ inputs.macos }} 15 | steps: 16 | - uses: actions/checkout@v4 17 | if: ${{ inputs.is_pr }} 18 | with: 19 | ref: ${{ github.event.pull_request.head.sha }} 20 | - uses: actions/checkout@v4 21 | if: ${{ ! inputs.is_pr }} 22 | with: 23 | ref: ${{ github.head_ref }} 24 | - name: Install Dependencies 25 | run: node ./install.js 26 | - name: Build all ios native templates 27 | run: ./test/test_force.js --exit-on-failure --cli=forceios 28 | 29 | forceios-sfdx: 30 | runs-on: ${{ inputs.macos }} 31 | steps: 32 | - uses: actions/checkout@v4 33 | if: ${{ inputs.is_pr }} 34 | with: 35 | ref: ${{ github.event.pull_request.head.sha }} 36 | - uses: actions/checkout@v4 37 | if: ${{ ! inputs.is_pr }} 38 | with: 39 | ref: ${{ github.head_ref }} 40 | - name: Install Dependencies 41 | run: node ./install.js 42 | - name: Install SFDX 43 | run: npm install -g @salesforce/cli 44 | - name: Build all ios native templates with SFDX 45 | run: ./test/test_force.js --exit-on-failure --cli=forceios --use-sfdx 46 | 47 | forcehybrid-ios: 48 | runs-on: ${{ inputs.macos }} 49 | steps: 50 | - uses: actions/checkout@v4 51 | if: ${{ inputs.is_pr }} 52 | with: 53 | ref: ${{ github.event.pull_request.head.sha }} 54 | - uses: actions/checkout@v4 55 | if: ${{ ! inputs.is_pr }} 56 | with: 57 | ref: ${{ github.head_ref }} 58 | - name: Install Dependencies 59 | run: node ./install.js 60 | - name: Install SFDX 61 | run: npm install -g @salesforce/cli 62 | - name: Install Cordova 63 | run: npm install -g cordova 64 | - name: Build all ios hybrid templates 65 | run: ./test/test_force.js --exit-on-failure --cli=forcehybrid --os=ios --no-plugin-update 66 | 67 | forcehybrid-ios-sfdx: 68 | runs-on: ${{ inputs.macos }} 69 | steps: 70 | - uses: actions/checkout@v4 71 | if: ${{ inputs.is_pr }} 72 | with: 73 | ref: ${{ github.event.pull_request.head.sha }} 74 | - uses: actions/checkout@v4 75 | if: ${{ ! inputs.is_pr }} 76 | with: 77 | ref: ${{ github.head_ref }} 78 | - name: Install Dependencies 79 | run: node ./install.js 80 | - name: Install SFDX 81 | run: npm install -g @salesforce/cli 82 | - name: Install Cordova 83 | run: npm install -g cordova 84 | - name: Build all ios hybrid templates with SFDX 85 | run: ./test/test_force.js --exit-on-failure --cli=forcehybrid --os=ios --use-sfdx --no-plugin-update 86 | 87 | forcereact-ios: 88 | runs-on: ${{ inputs.macos }} 89 | steps: 90 | - uses: actions/checkout@v4 91 | if: ${{ inputs.is_pr }} 92 | with: 93 | ref: ${{ github.event.pull_request.head.sha }} 94 | - uses: actions/checkout@v4 95 | if: ${{ ! inputs.is_pr }} 96 | with: 97 | ref: ${{ github.head_ref }} 98 | - name: Install Dependencies 99 | run: node ./install.js 100 | - name: Install Typescript 101 | run: npm install -g typescript 102 | - name: Build all ios react native templates 103 | run: ./test/test_force.js --exit-on-failure --cli=forcereact --os=ios 104 | 105 | forcereact-ios-sfdx: 106 | runs-on: ${{ inputs.macos }} 107 | steps: 108 | - uses: actions/checkout@v4 109 | if: ${{ inputs.is_pr }} 110 | with: 111 | ref: ${{ github.event.pull_request.head.sha }} 112 | - uses: actions/checkout@v4 113 | if: ${{ ! inputs.is_pr }} 114 | with: 115 | ref: ${{ github.head_ref }} 116 | - name: Install Dependencies 117 | run: node ./install.js 118 | - name: Install SFDX 119 | run: npm install -g @salesforce/cli 120 | - name: Install Typescript 121 | run: npm install -g typescript 122 | - name: Build all ios react native templates with SFDX 123 | run: ./test/test_force.js --exit-on-failure --cli=forcereact --os=ios --use-sfdx -------------------------------------------------------------------------------- /.github/workflows/nightly.yaml: -------------------------------------------------------------------------------- 1 | name: Nightly Tests 2 | 3 | on: 4 | schedule: 5 | - cron: "0 3 * * 3" # cron is UTC, this translates to 8 PM PST Wednesday. 6 | # This lets us trigger the workflow from a browser. 7 | workflow_dispatch: 8 | 9 | jobs: 10 | android-nightly: 11 | strategy: 12 | fail-fast: false 13 | uses: ./.github/workflows/android-reusable-workflow.yaml 14 | 15 | ios-nightly: 16 | strategy: 17 | fail-fast: false 18 | uses: ./.github/workflows/ios-reusable-workflow.yaml -------------------------------------------------------------------------------- /.github/workflows/pr.yaml: -------------------------------------------------------------------------------- 1 | name: Pull Request 2 | 3 | on: 4 | pull_request: 5 | branches: [dev, master] 6 | 7 | jobs: 8 | android-pr: 9 | strategy: 10 | fail-fast: false 11 | uses: ./.github/workflows/android-reusable-workflow.yaml 12 | with: 13 | is_pr: true 14 | 15 | ios-pr: 16 | strategy: 17 | fail-fast: false 18 | uses: ./.github/workflows/ios-reusable-workflow.yaml 19 | with: 20 | is_pr: true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | package-lock.json 3 | sfdx/yarn.lock 4 | tmp*/ 5 | .idea 6 | **/oclif.manifest.json 7 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Comment line immediately above ownership line is reserved for related gus information. Please be careful while editing. 2 | #ECCN:Open Source 3 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Salesforce Open Source Community Code of Conduct 2 | 3 | ## About the Code of Conduct 4 | 5 | Equality is a core value at Salesforce. We believe a diverse and inclusive 6 | community fosters innovation and creativity, and are committed to building a 7 | culture where everyone feels included. 8 | 9 | Salesforce open-source projects are committed to providing a friendly, safe, and 10 | welcoming environment for all, regardless of gender identity and expression, 11 | sexual orientation, disability, physical appearance, body size, ethnicity, nationality, 12 | race, age, religion, level of experience, education, socioeconomic status, or 13 | other similar personal characteristics. 14 | 15 | The goal of this code of conduct is to specify a baseline standard of behavior so 16 | that people with different social values and communication styles can work 17 | together effectively, productively, and respectfully in our open source community. 18 | It also establishes a mechanism for reporting issues and resolving conflicts. 19 | 20 | All questions and reports of abusive, harassing, or otherwise unacceptable behavior 21 | in a Salesforce open-source project may be reported by contacting the Salesforce 22 | Open Source Conduct Committee at ossconduct@salesforce.com. 23 | 24 | ## Our Pledge 25 | 26 | In the interest of fostering an open and welcoming environment, we as 27 | contributors and maintainers pledge to making participation in our project and 28 | our community a harassment-free experience for everyone, regardless of gender 29 | identity and expression, sexual orientation, disability, physical appearance, 30 | body size, ethnicity, nationality, race, age, religion, level of experience, education, 31 | socioeconomic status, or other similar personal characteristics. 32 | 33 | ## Our Standards 34 | 35 | Examples of behavior that contributes to creating a positive environment 36 | include: 37 | 38 | * Using welcoming and inclusive language 39 | * Being respectful of differing viewpoints and experiences 40 | * Gracefully accepting constructive criticism 41 | * Focusing on what is best for the community 42 | * Showing empathy toward other community members 43 | 44 | Examples of unacceptable behavior by participants include: 45 | 46 | * The use of sexualized language or imagery and unwelcome sexual attention or 47 | advances 48 | * Personal attacks, insulting/derogatory comments, or trolling 49 | * Public or private harassment 50 | * Publishing, or threatening to publish, others' private information—such as 51 | a physical or electronic address—without explicit permission 52 | * Other conduct which could reasonably be considered inappropriate in a 53 | professional setting 54 | * Advocating for or encouraging any of the above behaviors 55 | 56 | ## Our Responsibilities 57 | 58 | Project maintainers are responsible for clarifying the standards of acceptable 59 | behavior and are expected to take appropriate and fair corrective action in 60 | response to any instances of unacceptable behavior. 61 | 62 | Project maintainers have the right and responsibility to remove, edit, or 63 | reject comments, commits, code, wiki edits, issues, and other contributions 64 | that are not aligned with this Code of Conduct, or to ban temporarily or 65 | permanently any contributor for other behaviors that they deem inappropriate, 66 | threatening, offensive, or harmful. 67 | 68 | ## Scope 69 | 70 | This Code of Conduct applies both within project spaces and in public spaces 71 | when an individual is representing the project or its community. Examples of 72 | representing a project or community include using an official project email 73 | address, posting via an official social media account, or acting as an appointed 74 | representative at an online or offline event. Representation of a project may be 75 | further defined and clarified by project maintainers. 76 | 77 | ## Enforcement 78 | 79 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 80 | reported by contacting the Salesforce Open Source Conduct Committee 81 | at ossconduct@salesforce.com. All complaints will be reviewed and investigated 82 | and will result in a response that is deemed necessary and appropriate to the 83 | circumstances. The committee is obligated to maintain confidentiality with 84 | regard to the reporter of an incident. Further details of specific enforcement 85 | policies may be posted separately. 86 | 87 | Project maintainers who do not follow or enforce the Code of Conduct in good 88 | faith may face temporary or permanent repercussions as determined by other 89 | members of the project's leadership and the Salesforce Open Source Conduct 90 | Committee. 91 | 92 | ## Attribution 93 | 94 | This Code of Conduct is adapted from the [Contributor Covenant][contributor-covenant-home], 95 | version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html. 96 | It includes adaptions and additions from [Go Community Code of Conduct][golang-coc], 97 | [CNCF Code of Conduct][cncf-coc], and [Microsoft Open Source Code of Conduct][microsoft-coc]. 98 | 99 | This Code of Conduct is licensed under the [Creative Commons Attribution 3.0 License][cc-by-3-us]. 100 | 101 | [contributor-covenant-home]: https://www.contributor-covenant.org (https://www.contributor-covenant.org/) 102 | [golang-coc]: https://golang.org/conduct 103 | [cncf-coc]: https://github.com/cncf/foundation/blob/master/code-of-conduct.md 104 | [microsoft-coc]: https://opensource.microsoft.com/codeofconduct/ 105 | [cc-by-3-us]: https://creativecommons.org/licenses/by/3.0/us/ 106 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2018, Salesforce.com, inc. 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build](https://github.com/forcedotcom/SalesforceMobileSDK-Package/actions/workflows/nightly.yaml/badge.svg)](https://github.com/forcedotcom/SalesforceMobileSDK-Package/actions/workflows/nightly.yaml) 2 | 3 | # SalesforceMobileSDK-Package 4 | Repo for forceios/forcedroid/forcehybrid/forcereact and the Salesforce CLI plugin. 5 | 6 | ## To get started do the following from the root directory 7 | ``` shell 8 | node ./install.js 9 | ``` 10 | 11 | ## To run forceios do 12 | ```shell 13 | ./ios/forceios.js 14 | ``` 15 | 16 | ## To run forcedroid do 17 | ```shell 18 | ./android/forcedroid.js 19 | ``` 20 | 21 | ## To run forcehybrid do 22 | ```shell 23 | ./hybrid/forcehybrid.js 24 | ``` 25 | 26 | ## To run forcereact do 27 | ```shell 28 | ./react/forcereact.js 29 | ``` 30 | 31 | ## To load the sf plugin from source do 32 | ```shell 33 | sf plugins link sfdx 34 | ``` 35 | 36 | ## To run the Salesforce CLI plugin do 37 | ```shell 38 | sf mobilesdk ios --help 39 | sf mobilesdk android --help 40 | sf mobilesdk hybrid --help 41 | sf mobilesdk reactnative --help 42 | ``` 43 | 44 | ## To test forceios, forcedroid, forcehybrid, forcereact or the Salesforce CLI plugin do 45 | ```shell 46 | ./test/test_force.js 47 | ``` 48 | 49 | ## To npm pack forceios, forcedroid, forcehybrid, forcereact or the Salesforce CLI plugin do 50 | ```shell 51 | ./pack/pack.js 52 | ``` 53 | 54 | ## To do a full release 55 | ``` 56 | ./release/release.js 57 | ``` 58 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | ## Security 2 | 3 | Please report any security issue to [security@salesforce.com](mailto:security@salesforce.com) 4 | as soon as it is discovered. This library limits its runtime dependencies in 5 | order to reduce the total cost of ownership as much as can be, but all consumers 6 | should remain vigilant and have their security stakeholders review all third-party 7 | products (3PP) like this one and their dependencies. 8 | -------------------------------------------------------------------------------- /android/README.md: -------------------------------------------------------------------------------- 1 | # Salesforce Mobile SDK for Android Package 2 | 3 | The **forcedroid** npm package allows users to create Android mobile applications to interface with the [Salesforce Platform](http://www.salesforce.com/platform/overview/), leveraging [Salesforce Mobile SDK for Android](https://github.com/forcedotcom/SalesforceMobileSDK-Android). 4 | 5 | ## Getting Started 6 | 7 | If you're new to mobile development or the force.com platform, you may want to start at the [Mobile SDK landing page](http://wiki.developerforce.com/page/Mobile_SDK). This page offers a variety of resources to help you determine the best technology path for creating your app, as well as many guides and blog posts detailing how to work with Mobile SDK. 8 | 9 | But assuming you're all read up, here's how to get started with the **forcedroid** package to create the starting point for your mobile application. 10 | 11 | ## Install the forcedroid Package 12 | 13 | Because forcedroid is a command-line utility, we recommend installing it globally so that it's easily accessible on your path: 14 | 15 | sudo npm install forcedroid -g 16 | 17 | You're of course welcome to install it locally as well: 18 | 19 | npm install forcedroid 20 | 21 | In local installations, you can access the forcedroid app at `[Install Directory]/node_modules/.bin/forcedroid`. 22 | 23 | ## Using forcedroid 24 | 25 | For the rest of this document, we'll assume that `forcedroid` is on your path. 26 | 27 | Typing `forcedroid` with no arguments gives you a breakdown of the usage: 28 | 29 | ``` 30 | -> forcedroid 31 | forcedroid: Tool for building an Android native mobile application using Salesforce Mobile SDK 32 | 33 | Usage: 34 | 35 | # create an Android native mobile application 36 | forcedroid create 37 | [--apptype=application type (native or native_kotlin, leave empty for native_kotlin)] 38 | --appname=application name 39 | --packagename=app package identifier (e.g. com.mycompany.myapp) 40 | --organization=organization name (your company's/organization's name) 41 | [--outputdir=output directory (leave empty for current directory)] 42 | 43 | OR 44 | 45 | # create an Android native mobile application from a template 46 | forcedroid createwithtemplate 47 | --templaterepouri=template repo URI or Mobile SDK template name 48 | --appname=application name 49 | --packagename=app package identifier (e.g. com.mycompany.myapp) 50 | --organization=organization name (your company's/organization's name) 51 | [--outputdir=output directory (leave empty for current directory)] 52 | 53 | OR 54 | 55 | # list available Mobile SDK templates 56 | forcedroid listtemplates 57 | 58 | OR 59 | 60 | # validate store or syncs configuration 61 | forcedroid checkconfig 62 | --configpath=path to store or syncs config to validate 63 | --configtype=type of config to validate (store or syncs) 64 | 65 | OR 66 | 67 | # show version of Mobile SDK 68 | forcedroid version 69 | 70 | OR 71 | 72 | forcedroid 73 | ``` 74 | 75 | **Note:** You can specify any or all of the arguments as command line options as specified in the usage. If you run `forcedroid create` with missing arguments, it prompts you for each missing option interactively. 76 | 77 | Once the creation script completes, you'll have a fully functioning basic application of the type you specified. The new application has an Android Studio workspace that you can peruse, run, and debug. 78 | 79 | ### forcedroid create options 80 | 81 | **App Type:** \( *Optional* \) The type of application you wish to develop: 82 | 83 | - **native\_kotlin** (default) — A fully native Android application written in Kotlin 84 | - **native** — A fully native Android application 85 | 86 | **App Name:** The name of your application 87 | 88 | **App Package Identifier:** The Java package identifier for your app (e.g. `com.acme.mobile_apps`). **Note:** Your package name must be formatted as a [valid Java package name](http://docs.oracle.com/javase/tutorial/java/package/namingpkgs.html), or you will receive an error. 89 | 90 | **Organization:** The name of your company or organization. For example, `Acme Widgets, Inc.` 91 | 92 | **Output Directory:** \( *Optional* \) The folder where you want your app to be created. 93 | 94 | ## More information 95 | 96 | - After your app has been created, you will see some on-screen instructions for next steps, such as building and running your app, importing the project into Android Studio, and changing the default Connected App (sample) configuration values to match your own Connected App. 97 | 98 | - You can find the `forceios` npm package [here](https://npmjs.org/package/forceios) to develop Mobile SDK apps for iOS. 99 | 100 | - You can find the `forcehybrid` npm package [here](https://npmjs.org/package/forcehybrid) to develop Mobile SDK hybrid apps for iOS and Android. 101 | 102 | - You can find the `forcereact` npm package [here](https://npmjs.org/package/forcereact) to develop Mobile SDK react native apps for iOS and Android. 103 | 104 | - The Salesforce Mobile SDK for iOS source repository lives [here](https://github.com/forcedotcom/SalesforceMobileSDK-iOS). 105 | 106 | - The Salesforce Mobile SDK for Android source repository lives [here](https://github.com/forcedotcom/SalesforceMobileSDK-Android). 107 | 108 | - See [our developerforce site](http://wiki.developerforce.com/page/Mobile_SDK) for more information about how you can leverage Salesforce Mobile SDK with the force.com platform. 109 | 110 | - If you would like to make suggestions, have questions, or encounter any issues, we'd love to hear from you. Post any feedback you have on [Salesforce StackExchange](https://salesforce.stackexchange.com/questions/tagged/mobilesdk). 111 | -------------------------------------------------------------------------------- /android/forcedroid.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | * Copyright (c) 2016-present, salesforce.com, inc. 5 | * All rights reserved. 6 | * Redistribution and use of this software in source and binary forms, with or 7 | * without modification, are permitted provided that the following conditions 8 | * are met: 9 | * - Redistributions of source code must retain the above copyright notice, this 10 | * list of conditions and the following disclaimer. 11 | * - Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * - Neither the name of salesforce.com, inc. nor the names of its contributors 15 | * may be used to endorse or promote products derived from this software without 16 | * specific prior written permission of salesforce.com, inc. 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 21 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | * POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | // Dependencies 31 | var SDK = require('./shared/constants'), 32 | createHelper = require('./shared/createHelper'); 33 | 34 | 35 | // Do everything 36 | createHelper.createApp(SDK.forceclis.forcedroid); 37 | -------------------------------------------------------------------------------- /android/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "forcedroid", 3 | "version": "13.1.0", 4 | "description": "Utilities for creating mobile apps based on the Salesforce Mobile SDK for Android", 5 | "keywords": [ "mobilesdk", "android", "salesforce", "mobile", "sdk" ], 6 | "homepage": "https://github.com/forcedotcom/SalesforceMobileSDK-Android", 7 | "bugs": "https://github.com/forcedotcom/SalesforceMobileSDK-Android/issues", 8 | "licenses" : [ 9 | { "type": "Salesforce.com Mobile SDK License", "url": "https://github.com/forcedotcom/SalesforceMobileSDK-Android/blob/master/LICENSE.md" } 10 | ], 11 | "bin" : { "forcedroid" : "forcedroid.js" }, 12 | "engines": { 13 | "node": ">=7.0.0" 14 | }, 15 | "dependencies": { 16 | "shelljs": "0.8.5", 17 | "ajv": "^8.11.0", 18 | "jsonlint": "^1.6.3" 19 | }, 20 | "repository": { 21 | "type" : "git", 22 | "url" : "https://github.com/forcedotcom/SalesforceMobileSDK-Package.git" 23 | }, 24 | "author": { "name": "Kevin Hawkins", "email": "khawkins@salesforce.com" }, 25 | "contributors": [ 26 | { "name": "Wolfgang Mathurin", "email": "wmathurin@salesforce.com" }, 27 | { "name": "Bharath Hariharan", "email": "bhariharan@salesforce.com" } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /android/shared: -------------------------------------------------------------------------------- 1 | ../shared -------------------------------------------------------------------------------- /hybrid/README.md: -------------------------------------------------------------------------------- 1 | # Salesforce Mobile SDK for Hybrid Package 2 | 3 | The **forcehybrid** npm package allows users to create hybrid iOS and Android mobile applications to interface with the [Salesforce Platform](http://www.salesforce.com/platform/overview/), leveraging [Salesforce Mobile SDK for iOS](https://github.com/forcedotcom/SalesforceMobileSDK-iOS) and [Salesforce Mobile SDK for Android](https://github.com/forcedotcom/SalesforceMobileSDK-Android). 4 | 5 | ## Getting Started 6 | 7 | If you're new to mobile development or the force.com platform, you may want to start at the [Mobile SDK landing page](http://wiki.developerforce.com/page/Mobile_SDK). This page offers a variety of resources to help you determine the best technology path for creating your app, as well as many guides and blog posts detailing how to work with Mobile SDK. 8 | 9 | But assuming you're all read up, here's how to get started with the **forcehybrid** package to create the starting point for your mobile application. 10 | 11 | ## Install the forcehybrid Package 12 | 13 | Because forcehybrid is a command-line utility, we recommend installing it globally so that it's easily accessible on your path: 14 | 15 | sudo npm install forcehybrid -g 16 | 17 | You're of course welcome to install it locally as well: 18 | 19 | npm install forcehybrid 20 | 21 | In local installations, you can access the forcehybrid app at `[Install Directory]/node_modules/.bin/forcehybrid`. 22 | 23 | ## Using forcehybrid 24 | 25 | For the rest of this document, we'll assume that `forcehybrid` is on your path. 26 | 27 | Typing `forcehybrid` with no arguments gives you a breakdown of the usage: 28 | 29 | ``` 30 | -> forcehybrid 31 | forcehybrid: Tool for building a hybrid mobile application using Salesforce Mobile SDK 32 | 33 | Usage: 34 | 35 | # create a hybrid mobile application 36 | forcehybrid create 37 | --platform=comma-separated list of platforms (ios, android) 38 | [--apptype=application type (hybrid_local or hybrid_remote, leave empty for hybrid_local)] 39 | --appname=application name 40 | --packagename=app package identifier (e.g. com.mycompany.myapp) 41 | --organization=organization name (your company's/organization's name) 42 | [--startpage=app start page (the start page of your remote app; required for hybrid_remote apps only)] 43 | [--outputdir=output directory (leave empty for current directory)] 44 | 45 | OR 46 | 47 | # create a hybrid mobile application from a template 48 | forcehybrid createwithtemplate 49 | --platform=comma-separated list of platforms (ios, android) 50 | --templaterepouri=template repo URI or Mobile SDK template name 51 | --appname=application name 52 | --packagename=app package identifier (e.g. com.mycompany.myapp) 53 | --organization=organization name (your company's/organization's name) 54 | [--outputdir=output directory (leave empty for current directory)] 55 | 56 | OR 57 | 58 | # list available Mobile SDK templates 59 | forcehybrid listtemplates 60 | 61 | OR 62 | 63 | # validate store or syncs configuration 64 | forcehybrid checkconfig 65 | --configpath=path to store or syncs config to validate 66 | --configtype=type of config to validate (store or syncs) 67 | 68 | OR 69 | 70 | # show version of Mobile SDK 71 | forcehybrid version 72 | 73 | OR 74 | 75 | forcehybrid 76 | ``` 77 | 78 | **Note:** You can specify any or all of the arguments as command line options as specified in the usage. If you run `forcehybrid create` with missing arguments, it prompts you for each missing option interactively. 79 | 80 | Once the creation script completes, you'll have a fully functioning basic application of the type you specified. The new application has an Android Studio and/or a XCode workspace that you can peruse, run, and debug. 81 | 82 | ### forcehybrid create options 83 | 84 | **App Type:** \( *Optional* \) The type of application you wish to develop: 85 | 86 | - **hybrid\_local** (default) — A hybrid application, based on the Cordova framework, that runs in a native container. The app contents are developed locally in the Xcode project and are deployed to the device itself when the app is built 87 | - **hybrid\_remote** — A hybrid application, based on the [Cordova](http://cordova.apache.org/) framework, that runs in a native container. The app contents live in the cloud as a [Visualforce](http://wiki.developerforce.com/page/An_Introduction_to_Visualforce) application 88 | 89 | **App Name:** The name of your application 90 | 91 | **App Package Identifier:** An identifier for your company, similar to a Java package (e.g. `com.acme.MobileApps`). This concatenates with the app name to form the unique identifier for your app in the App Store. 92 | 93 | **Organization:** The name of your company or organization. For example, `Acme Widgets, Inc.` 94 | 95 | **Start Page:** \( *Required for hybrid\_remote apps only* \) The starting page of your application on salesforce.com. This is the entry point of your remote application, though it's only the path, not the server portion of the URL. For instance, `/apex/MyVisualforceStartPage`. 96 | 97 | **Output Directory:** \( *Optional* \) The folder where you want your app to be created. 98 | 99 | ## More information 100 | 101 | - After your app has been created, you will see some on-screen instructions for next steps, such as building and running your app, importing the project into XCode or Android Studio, and changing the default Connected App (sample) configuration values to match your own Connected App. 102 | 103 | - You can find the `forceios` npm package [here](https://npmjs.org/package/forcedroid) to develop Mobile SDK apps for iOS. 104 | 105 | - You can find the `forcedroid` npm package [here](https://npmjs.org/package/forcedroid) to develop Mobile SDK apps for Android. 106 | 107 | - You can find the `forcereact` npm package [here](https://npmjs.org/package/forcereact) to develop Mobile SDK react native apps for iOS and Android. 108 | 109 | - The Salesforce Mobile SDK for iOS source repository lives [here](https://github.com/forcedotcom/SalesforceMobileSDK-iOS). 110 | 111 | - The Salesforce Mobile SDK for Android source repository lives [here](https://github.com/forcedotcom/SalesforceMobileSDK-Android). 112 | 113 | - See [our developerforce site](http://wiki.developerforce.com/page/Mobile_SDK) for more information about how you can leverage Salesforce Mobile SDK with the force.com platform. 114 | 115 | - If you would like to make suggestions, have questions, or encounter any issues, we'd love to hear from you. Post any feedback you have on [Salesforce StackExchange](https://salesforce.stackexchange.com/questions/tagged/mobilesdk). 116 | -------------------------------------------------------------------------------- /hybrid/forcehybrid.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | * Copyright (c) 2017-present, salesforce.com, inc. 5 | * All rights reserved. 6 | * Redistribution and use of this software in source and binary forms, with or 7 | * without modification, are permitted provided that the following conditions 8 | * are met: 9 | * - Redistributions of source code must retain the above copyright notice, this 10 | * list of conditions and the following disclaimer. 11 | * - Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * - Neither the name of salesforce.com, inc. nor the names of its contributors 15 | * may be used to endorse or promote products derived from this software without 16 | * specific prior written permission of salesforce.com, inc. 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 21 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | * POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | // Dependencies 31 | var SDK = require('./shared/constants'), 32 | createHelper = require('./shared/createHelper'); 33 | 34 | 35 | // Do everything 36 | createHelper.createApp(SDK.forceclis.forcehybrid); 37 | 38 | 39 | -------------------------------------------------------------------------------- /hybrid/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "forcehybrid", 3 | "version": "13.1.0", 4 | "description": "Utilities for creating hybrid mobile apps based on the Salesforce Mobile SDK for iOS and Android", 5 | "keywords": [ "mobilesdk", "ios", "android", "hybrid", "salesforce", "mobile", "sdk" ], 6 | "homepage": "https://github.com/forcedotcom/SalesforceMobileSDK-Package", 7 | "bugs": "https://github.com/forcedotcom/SalesforceMobileSDK-Package/issues", 8 | "licenses" : [ 9 | { "type": "Salesforce.com Mobile SDK License", "url": "https://github.com/forcedotcom/SalesforceMobileSDK-iOS/blob/master/LICENSE.md" } 10 | ], 11 | "bin" : { "forcehybrid" : "forcehybrid.js" }, 12 | "dependencies": { 13 | "shelljs": "0.8.5", 14 | "ajv": "^8.11.0", 15 | "jsonlint": "^1.6.3" 16 | }, 17 | "repository": { 18 | "type" : "git", 19 | "url" : "https://github.com/forcedotcom/SalesforceMobileSDK-Package.git" 20 | }, 21 | "author": { "name": "Wolfgang Mathurin", "email": "wmathurin@salesforce.com" }, 22 | "contributors": [ 23 | { "name": "Bharath Hariharan", "email": "bhariharan@salesforce.com" } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /hybrid/shared: -------------------------------------------------------------------------------- 1 | ../shared -------------------------------------------------------------------------------- /install.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const path = require('path') 4 | const execSync = require('child_process').execSync 5 | console.log('Installing npm dependencies') 6 | 7 | let deps = [] 8 | for (let dir of ['sfdx', 'release'].values()) { 9 | const pjson = require(path.join(__dirname, dir, 'package.json')) 10 | if (pjson.dependencies) { 11 | deps = deps.concat(Object.entries(pjson.dependencies).map(entry => `${entry[0]}@${entry[1]}`)) 12 | } 13 | 14 | if (pjson.devDependencies) { 15 | deps = deps.concat(Object.entries(pjson.devDependencies).map(entry => `${entry[0]}@${entry[1]}`)) 16 | } 17 | } 18 | 19 | execSync(`npm install ${deps.join(' ')}`, {stdio:[0,1,2]}) 20 | 21 | -------------------------------------------------------------------------------- /ios/README.md: -------------------------------------------------------------------------------- 1 | # Salesforce Mobile SDK for iOS Package 2 | 3 | The **forceios** npm package allows users to create iOS mobile applications to interface with the [Salesforce Platform](http://www.salesforce.com/platform/overview/), leveraging [Salesforce Mobile SDK for iOS](https://github.com/forcedotcom/SalesforceMobileSDK-iOS). 4 | 5 | ## Getting Started 6 | 7 | If you're new to mobile development or the force.com platform, you may want to start at the [Mobile SDK landing page](http://wiki.developerforce.com/page/Mobile_SDK). This page offers a variety of resources to help you determine the best technology path for creating your app, as well as many guides and blog posts detailing how to work with the Mobile SDK. 8 | 9 | But assuming you're all read up, here's how to get started with the **forceios** package to create the starting point for your mobile application. 10 | 11 | ## Install the forceios Package 12 | 13 | Because forceios is a command-line utility, we recommend installing it globally, so that it's easily accessible on your path: 14 | 15 | sudo npm install forceios -g 16 | 17 | You're of course welcome to install it locally as well: 18 | 19 | npm install forceios 20 | 21 | In local installations, you can access the forceios app at `[Install Directory]/node_modules/.bin/forceios`. 22 | 23 | ## Using forceios 24 | 25 | For the rest of this document, we'll assume that `forceios` is on your path. 26 | 27 | Typing `forceios` with no arguments gives you a breakdown of the usage: 28 | 29 | ``` 30 | -> forceios 31 | forceios: Tool for building an iOS native mobile application using Salesforce Mobile SDK 32 | 33 | Usage: 34 | 35 | # create an iOS native mobile application 36 | forceios create 37 | [--apptype=application type (native_swift or native, leave empty for native_swift)] 38 | --appname=application name 39 | --packagename=app package identifier (e.g. com.mycompany.myapp) 40 | --organization=organization name (your company's/organization's name) 41 | [--outputdir=output directory (leave empty for current directory)] 42 | 43 | OR 44 | 45 | # create an iOS native mobile application from a template 46 | forceios createwithtemplate 47 | --templaterepouri=template repo URI or Mobile SDK template name 48 | --appname=application name 49 | --packagename=app package identifier (e.g. com.mycompany.myapp) 50 | --organization=organization name (your company's/organization's name) 51 | [--outputdir=output directory (leave empty for current directory)] 52 | 53 | OR 54 | 55 | # list available Mobile SDK templates 56 | forceios listtemplates 57 | 58 | OR 59 | 60 | # validate store or syncs configuration 61 | forceios checkconfig 62 | --configpath=path to store or syncs config to validate 63 | --configtype=type of config to validate (store or syncs) 64 | 65 | OR 66 | 67 | # show version of Mobile SDK 68 | forceios version 69 | 70 | OR 71 | 72 | forceios 73 | ``` 74 | 75 | **Note:** You can specify any or all of the arguments as command line options as specified in the usage. If you run `forceios create` with missing arguments, it prompts you for each missing option interactively. 76 | 77 | Once the creation script completes, you'll have a fully functioning basic application of the type you specified. The new application has an Xcode workspace that you can peruse, run, and debug. 78 | 79 | ### forceios create options 80 | 81 | **App Type:** \( *Optional* \) The type of application you wish to develop: 82 | 83 | - **native\_swift** (default) — A fully native iOS application written in Swift 84 | - **native** — A fully native iOS application written in Objective C 85 | 86 | **App Name:** The name of your application 87 | 88 | **App Package Identifier:** An identifier for your company, similar to a Java package (e.g. `com.acme.MobileApps`). This concatenates with the app name to form the unique identifier for your app in the App Store. 89 | 90 | **Organization:** The name of your company or organization. For example, `Acme Widgets, Inc.` 91 | 92 | **Output Directory:** \( *Optional* \) The folder where you want your app to be created. 93 | 94 | ## More information 95 | 96 | - After your app has been created, you will see some on-screen instructions for next steps, such as building and running your app, importing the project into XCode, and changing the default Connected App (sample) configuration values to match your own Connected App. 97 | 98 | - You can find the `forcedroid` npm package [here](https://npmjs.org/package/forcedroid) to develop Mobile SDK apps for Android. 99 | 100 | - You can find the `forcehybrid` npm package [here](https://npmjs.org/package/forcehybrid) to develop Mobile SDK hybrid apps for iOS and Android. 101 | 102 | - You can find the `forcereact` npm package [here](https://npmjs.org/package/forcereact) to develop Mobile SDK react native apps for iOS and Android. 103 | 104 | - The Salesforce Mobile SDK for iOS source repository lives [here](https://github.com/forcedotcom/SalesforceMobileSDK-iOS). 105 | 106 | - The Salesforce Mobile SDK for Android source repository lives [here](https://github.com/forcedotcom/SalesforceMobileSDK-Android). 107 | 108 | - See [our developerforce site](http://wiki.developerforce.com/page/Mobile_SDK) for more information about how you can leverage Salesforce Mobile SDK with the force.com platform. 109 | 110 | - If you would like to make suggestions, have questions, or encounter any issues, we'd love to hear from you. Post any feedback you have on [Salesforce StackExchange](https://salesforce.stackexchange.com/questions/tagged/mobilesdk). 111 | -------------------------------------------------------------------------------- /ios/forceios.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | * Copyright (c) 2016-present, salesforce.com, inc. 5 | * All rights reserved. 6 | * Redistribution and use of this software in source and binary forms, with or 7 | * without modification, are permitted provided that the following conditions 8 | * are met: 9 | * - Redistributions of source code must retain the above copyright notice, this 10 | * list of conditions and the following disclaimer. 11 | * - Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * - Neither the name of salesforce.com, inc. nor the names of its contributors 15 | * may be used to endorse or promote products derived from this software without 16 | * specific prior written permission of salesforce.com, inc. 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 21 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | * POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | // Dependencies 31 | var SDK = require('./shared/constants'), 32 | createHelper = require('./shared/createHelper'); 33 | 34 | 35 | // Do everything 36 | createHelper.createApp(SDK.forceclis.forceios); 37 | -------------------------------------------------------------------------------- /ios/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "forceios", 3 | "version": "13.1.0", 4 | "description": "Utilities for creating mobile apps based on the Salesforce Mobile SDK for iOS", 5 | "keywords": [ "mobilesdk", "ios", "salesforce", "mobile", "sdk" ], 6 | "homepage": "https://github.com/forcedotcom/SalesforceMobileSDK-iOS", 7 | "bugs": "https://github.com/forcedotcom/SalesforceMobileSDK-iOS/issues", 8 | "licenses" : [ 9 | { "type": "Salesforce.com Mobile SDK License", "url": "https://github.com/forcedotcom/SalesforceMobileSDK-iOS/blob/master/LICENSE.md" } 10 | ], 11 | "os" : [ "darwin" ], 12 | "engines": { 13 | "node": ">=7.0.0" 14 | }, 15 | "bin" : { "forceios" : "forceios.js" }, 16 | "dependencies": { 17 | "shelljs": "0.8.5", 18 | "ajv": "^8.11.0", 19 | "jsonlint": "^1.6.3" 20 | }, 21 | "repository": { 22 | "type" : "git", 23 | "url" : "https://github.com/forcedotcom/SalesforceMobileSDK-Package.git" 24 | }, 25 | "author": { "name": "Kevin Hawkins", "email": "khawkins@salesforce.com" }, 26 | "contributors": [ 27 | { "name": "Wolfgang Mathurin", "email": "wmathurin@salesforce.com" }, 28 | { "name": "Bharath Hariharan", "email": "bhariharan@salesforce.com" } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /ios/shared: -------------------------------------------------------------------------------- 1 | ../shared -------------------------------------------------------------------------------- /pack/pack.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // Dependencies 4 | var path = require('path'), 5 | shelljs = require('shelljs'), 6 | utils = require('../shared/utils.js'), 7 | commandLineUtils = require('../shared/commandLineUtils'), 8 | COLOR = require('../shared/outputColors'), 9 | SDK = require('../shared/constants') 10 | ; 11 | 12 | // Calling main 13 | main(process.argv); 14 | 15 | // 16 | // Main function 17 | // 18 | function main(args) { 19 | var commandLineArgs = process.argv.slice(2, args.length); 20 | var parsedArgs = commandLineUtils.parseArgs(commandLineArgs); 21 | 22 | // Args extraction 23 | var usageRequested = parsedArgs.hasOwnProperty('usage'); 24 | var sfdxPluginRequested = parsedArgs.hasOwnProperty('sfdx-plugin'); 25 | var chosenClis = cleanSplit(parsedArgs.cli, ','); 26 | var cliPackingRequested = chosenClis.some(cli=>Object.keys(SDK.forceclis).indexOf(cli) >= 0); 27 | 28 | // Usage 29 | if (usageRequested || (!sfdxPluginRequested && !cliPackingRequested)) { 30 | usage(); 31 | process.exit(1); 32 | } 33 | // Sfdx plugin packing 34 | else if (sfdxPluginRequested) { 35 | pack('sfdx-mobilesdk-plugin', 'sfdx', function(dir) { 36 | utils.runProcessThrowError('npm install @oclif/dev-cli', dir); 37 | utils.runProcessThrowError('node generate_oclif.js', dir); 38 | }); 39 | } 40 | // CLI packing 41 | else { 42 | for (var cliName in SDK.forceclis) { 43 | var cli = SDK.forceclis[cliName]; 44 | if (chosenClis.indexOf(cli.name) >= 0) { 45 | pack(cli.name, cli.dir); 46 | } 47 | } 48 | } 49 | } 50 | 51 | // 52 | // Create package with name from dir 53 | // 54 | function pack(name, relativeDir, prePack) { 55 | var packageName = name + '-' + SDK.version + '.tgz'; 56 | 57 | utils.logInfo('Creating ' + packageName, COLOR.green); 58 | 59 | // Packing 60 | var packageRepoDir = path.join(__dirname, '..'); 61 | var dir = path.join(packageRepoDir, relativeDir); 62 | var sharedDir = path.join(dir, 'shared'); 63 | 64 | // npm pack doesn't following links 65 | utils.removeFile(sharedDir); 66 | shelljs.cp('-R', path.join(packageRepoDir, 'shared'), dir); 67 | if (typeof prePack === 'function') prePack(dir); 68 | utils.runProcessThrowError('npm pack', dir); 69 | utils.removeFile(sharedDir); 70 | shelljs.ln('-s', path.join('..', 'shared'), sharedDir); 71 | 72 | // Moving package to current directory 73 | utils.moveFile(path.join(dir, packageName), packageName); 74 | } 75 | 76 | // 77 | // Like split, but splitting null returns [] instead of throwing an error 78 | // splitting '' returns [] instead of [''] 79 | // 80 | function cleanSplit(str, delimiter) { 81 | if (str == null || str === '') { 82 | return []; 83 | } 84 | else { 85 | return str.split(delimiter); 86 | } 87 | } 88 | 89 | // 90 | // Usage 91 | // 92 | function usage() { 93 | utils.logInfo('Usage:', COLOR.cyan); 94 | utils.logInfo(' pack.js --usage', COLOR.magenta); 95 | utils.logInfo('OR', COLOR.magenta); 96 | utils.logInfo(' pack.js', COLOR.magenta); 97 | utils.logInfo(' --cli=cli1,cli2', COLOR.magenta); 98 | utils.logInfo(' where cliN is one of: ' + Object.keys(SDK.forceclis).join(', '), COLOR.magenta); 99 | utils.logInfo(' OR', COLOR.magenta); 100 | utils.logInfo(' --sfdx-plugin', COLOR.magenta); 101 | } 102 | -------------------------------------------------------------------------------- /react/README.md: -------------------------------------------------------------------------------- 1 | # Salesforce Mobile SDK for React Native Package 2 | 3 | The **forcereact** npm package allows users to create React Native iOS and Android mobile applications to interface with the [Salesforce Platform](http://www.salesforce.com/platform/overview/), leveraging [Salesforce Mobile SDK for iOS](https://github.com/forcedotcom/SalesforceMobileSDK-iOS) and [Salesforce Mobile SDK for Android](https://github.com/forcedotcom/SalesforceMobileSDK-Android). 4 | 5 | ## Getting Started 6 | 7 | If you're new to mobile development or the force.com platform, you may want to start at the [Mobile SDK landing page](http://wiki.developerforce.com/page/Mobile_SDK). This page offers a variety of resources to help you determine the best technology path for creating your app, as well as many guides and blog posts detailing how to work with Mobile SDK. 8 | 9 | But assuming you're all read up, here's how to get started with the **forcereact** package to create the starting point for your mobile application. 10 | 11 | ## Install the forcereact Package 12 | 13 | Because forcereact is a command-line utility, we recommend installing it globally so that it's easily accessible on your path: 14 | 15 | sudo npm install forcereact -g 16 | 17 | You're of course welcome to install it locally as well: 18 | 19 | npm install forcereact 20 | 21 | In local installations, you can access the forcereact app at `[Install Directory]/node_modules/.bin/forcereact`. 22 | 23 | ## Using forcereact 24 | 25 | For the rest of this document, we'll assume that `forcereact` is on your path. 26 | 27 | Typing `forcereact` with no arguments gives you a breakdown of the usage: 28 | 29 | ``` 30 | -> forcereact 31 | forcereact: Tool for building a React Native mobile application using Salesforce Mobile SDK 32 | 33 | Usage: 34 | 35 | # create a React Native mobile application 36 | forcereact create 37 | --platform=comma-separated list of platforms (ios, android) 38 | --appname=application name 39 | --packagename=app package identifier (e.g. com.mycompany.myapp) 40 | --organization=organization name (your company's/organization's name) 41 | [--outputdir=output directory (leave empty for current directory)] 42 | 43 | OR 44 | 45 | # create a React Native mobile application from a template 46 | forcereact createwithtemplate 47 | --platform=comma-separated list of platforms (ios, android) 48 | --templaterepouri=template repo URI or Mobile SDK template name 49 | --appname=application name 50 | --packagename=app package identifier (e.g. com.mycompany.myapp) 51 | --organization=organization name (your company's/organization's name) 52 | [--outputdir=output directory (leave empty for current directory)] 53 | 54 | OR 55 | 56 | # list available Mobile SDK templates 57 | forcereact listtemplates 58 | 59 | OR 60 | 61 | # validate store or syncs configuration 62 | forcereact checkconfig 63 | --configpath=path to store or syncs config to validate 64 | --configtype=type of config to validate (store or syncs) 65 | 66 | OR 67 | 68 | # show version of Mobile SDK 69 | forcereact version 70 | 71 | OR 72 | 73 | forcereact 74 | ``` 75 | 76 | **Note:** You can specify any or all of the arguments as command line options as specified in the usage. If you run `forcereact create` with missing arguments, it prompts you for each missing option interactively. 77 | 78 | Once the creation script completes, you'll have a fully functioning basic application of the type you specified. The new application has an Android Studio and/or an XCode workspace that you can peruse, run, and debug. 79 | 80 | ### forcedreact create options 81 | 82 | **Platform:** Comma-separated list of the mobile platforms that you want to support: iOS, Android, or both 83 | 84 | **App Name:** The name of your application 85 | 86 | **App Package Identifier:** The Java package identifier for your app (e.g. `com.acme.mobile_apps`). **Note:** Your package name must be formatted as a [valid Java package name](http://docs.oracle.com/javase/tutorial/java/package/namingpkgs.html), or you will receive an error. 87 | 88 | **Organization:** The name of your company or organization. For example, `Acme Widgets, Inc.` 89 | 90 | **Output Directory:** \( *Optional* \) The folder where you want your app to be created. 91 | 92 | ### forcedreact createWithTemplate options 93 | 94 | **Platform:** Comma-separated list of the mobile platforms that you want to support: iOS, Android, or both 95 | 96 | **Template Repository:** The URI of the GitHub repo containing your template 97 | 98 | **App Name:** The name of your application 99 | 100 | **App Package Identifier:** The Java package identifier for your app (e.g. `com.acme.mobile_apps`). **Note:** Your package name must be formatted as a [valid Java package name](http://docs.oracle.com/javase/tutorial/java/package/namingpkgs.html), or you will receive an error. 101 | 102 | **Organization:** The name of your company or organization. For example, `Acme Widgets, Inc.` 103 | 104 | **Output Directory:** \( *Optional* \) The folder where you want your app to be created. 105 | 106 | ## More information 107 | 108 | - After your app has been created, you will see some on-screen instructions for next steps, such as building and running your app, importing the project into XCode or Android Studio, and changing the default Connected App (sample) configuration values to match your own Connected App. 109 | 110 | - You can find the `forceios` npm package [here](https://npmjs.org/package/forcedroid) to develop Mobile SDK apps for iOS. 111 | 112 | - You can find the `forcedroid` npm package [here](https://npmjs.org/package/forcedroid) to develop Mobile SDK apps for Android. 113 | 114 | - You can find the `forcehybrid` npm package [here](https://npmjs.org/package/forcehybrid) to develop Mobile SDK react native apps for iOS and Android. 115 | 116 | - The Salesforce Mobile SDK for iOS source repository lives [here](https://github.com/forcedotcom/SalesforceMobileSDK-iOS). 117 | 118 | - The Salesforce Mobile SDK for Android source repository lives [here](https://github.com/forcedotcom/SalesforceMobileSDK-Android). 119 | 120 | - See [our developerforce site](http://wiki.developerforce.com/page/Mobile_SDK) for more information about how you can leverage Salesforce Mobile SDK with the force.com platform. 121 | 122 | - If you would like to make suggestions, have questions, or encounter any issues, we'd love to hear from you. Post any feedback you have on [Salesforce StackExchange](https://salesforce.stackexchange.com/questions/tagged/mobilesdk). 123 | -------------------------------------------------------------------------------- /react/forcereact.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | * Copyright (c) 2017-present, salesforce.com, inc. 5 | * All rights reserved. 6 | * Redistribution and use of this software in source and binary forms, with or 7 | * without modification, are permitted provided that the following conditions 8 | * are met: 9 | * - Redistributions of source code must retain the above copyright notice, this 10 | * list of conditions and the following disclaimer. 11 | * - Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * - Neither the name of salesforce.com, inc. nor the names of its contributors 15 | * may be used to endorse or promote products derived from this software without 16 | * specific prior written permission of salesforce.com, inc. 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 21 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | * POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | // Dependencies 31 | var SDK = require('./shared/constants'), 32 | createHelper = require('./shared/createHelper'); 33 | 34 | 35 | // Do everything 36 | createHelper.createApp(SDK.forceclis.forcereact); 37 | 38 | 39 | -------------------------------------------------------------------------------- /react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "forcereact", 3 | "version": "13.1.0", 4 | "description": "Utilities for creating react native mobile apps based on the Salesforce Mobile SDK for iOS and Android", 5 | "keywords": [ "mobilesdk", "ios", "android", "react", "salesforce", "mobile", "sdk" ], 6 | "homepage": "https://github.com/forcedotcom/SalesforceMobileSDK-Package", 7 | "bugs": "https://github.com/forcedotcom/SalesforceMobileSDK-Package/issues", 8 | "licenses" : [ 9 | { "type": "Salesforce.com Mobile SDK License", "url": "https://github.com/forcedotcom/SalesforceMobileSDK-iOS/blob/master/LICENSE.md" } 10 | ], 11 | "bin" : { "forcereact" : "forcereact.js" }, 12 | "dependencies": { 13 | "shelljs": "0.8.5", 14 | "ajv": "^8.11.0", 15 | "jsonlint": "^1.6.3" 16 | }, 17 | "repository": { 18 | "type" : "git", 19 | "url" : "https://github.com/forcedotcom/SalesforceMobileSDK-Package.git" 20 | }, 21 | "author": { "name": "Wolfgang Mathurin", "email": "wmathurin@salesforce.com" }, 22 | "contributors": [ 23 | { "name": "Bharath Hariharan", "email": "bhariharan@salesforce.com" } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /react/shared: -------------------------------------------------------------------------------- 1 | ../shared -------------------------------------------------------------------------------- /release/README.md: -------------------------------------------------------------------------------- 1 | ## Setup 2 | 3 | First run `./install.js` at the root of the repo 4 | 5 | ## Trial run 6 | 7 | Run `./setup_test_branches.js` to create test branches in all repos 8 | Then run `./release.js` 9 | 10 | ## Actual run 11 | Run `./release.js` and follow instructions 12 | -------------------------------------------------------------------------------- /release/common.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019-present, salesforce.com, inc. 3 | * All rights reserved. 4 | * Redistribution and use of this software in source and binary forms, with or 5 | * without modification, are permitted provided that the following conditions 6 | * are met: 7 | * - Redistributions of source code must retain the above copyright notice, this 8 | * list of conditions and the following disclaimer. 9 | * - Redistributions in binary form must reproduce the above copyright notice, 10 | * this list of conditions and the following disclaimer in the documentation 11 | * and/or other materials provided with the distribution. 12 | * - Neither the name of salesforce.com, inc. nor the names of its contributors 13 | * may be used to endorse or promote products derived from this software without 14 | * specific prior written permission of salesforce.com, inc. 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | // Dependencies 29 | const path = require('path'), 30 | prompts = require('prompts'), 31 | utils = require('../shared/utils'), 32 | COLOR = require('../shared/outputColors') 33 | 34 | // Constants 35 | const REPO = { 36 | shared: 'SalesforceMobileSDK-Shared', 37 | android: 'SalesforceMobileSDK-Android', 38 | ios: 'SalesforceMobileSDK-iOS', 39 | ioshybrid: 'SalesforceMobileSDK-iOS-Hybrid', 40 | iosspecs: 'SalesforceMobileSDK-iOS-Specs', 41 | iosspm: 'SalesforceMobileSDK-iOS-SPM', 42 | cordovaplugin: 'SalesforceMobileSDK-CordovaPlugin', 43 | reactnative: 'SalesforceMobileSDK-ReactNative', 44 | templates: 'SalesforceMobileSDK-Templates', 45 | pkg: 'SalesforceMobileSDK-Package' 46 | } 47 | 48 | const DEPTH_PREFIX = { 49 | 1: '=', 50 | 2: '-', 51 | 3: '-', 52 | 4: '-' 53 | } 54 | 55 | const DEPTH_COLOR = { 56 | 1: COLOR.blue, 57 | 2: COLOR.yellow, 58 | 3: COLOR.cyan 59 | } 60 | 61 | var autoYesForPrompts = false 62 | 63 | // Run a bunch of commands 64 | async function runCmds(dir, cmds, depth) { 65 | if (!depth) { 66 | utils.logInfo(`\n=== ${cmds.msg} ===`, COLOR.magenta) 67 | if (!await proceedPrompt('Answer y(es) to proceed and n(o) to skip:')) { 68 | return 69 | } 70 | } 71 | 72 | depth = depth || 1 73 | cmds.cmds = cmds.cmds.filter(x => !!x) 74 | const count = cmds.cmds.length 75 | for (var i=0; i ${cmd}`, index, count, depth) 113 | try { 114 | utils.runProcessThrowError(cmd, dir) 115 | } catch (e) { 116 | if (cmdIfError) { 117 | await runCmds(dir, cmdIfError, depth+1) 118 | } else if (!ignoreError && !await proceedPrompt('An error occurred. Continue?')) { 119 | process.exit(1); 120 | } 121 | } 122 | } 123 | 124 | function print(msg, index, count, depth) { 125 | const prefix = new Array(2*(depth+1)).join(DEPTH_PREFIX[depth] || ' ') 126 | utils.logInfo(`\n${prefix} ${index}/${count} ${msg}`, DEPTH_COLOR[depth] || COLOR.green) 127 | } 128 | 129 | function urlForRepo(org, repo) { 130 | return `git@github.com:${org}/${repo}`; 131 | } 132 | 133 | function setAutoYesForPrompts(b) { 134 | autoYesForPrompts = b; 135 | } 136 | 137 | function cloneOrClean(org, repo, dir) { 138 | return { 139 | msg: `Preparing ${repo}`, 140 | cmds: [ 141 | {cmd: `git clone ${urlForRepo(org, repo)}`, dir: dir, ignoreError: true}, 142 | {cmd: `git checkout -- .`, dir: path.join(dir, repo)} 143 | ] 144 | } 145 | } 146 | 147 | 148 | module.exports = { 149 | REPO, 150 | runCmds, 151 | proceedPrompt, 152 | urlForRepo, 153 | setAutoYesForPrompts, 154 | cloneOrClean 155 | } 156 | -------------------------------------------------------------------------------- /release/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "release", 3 | "engines": { 4 | "node": ">=8.10.0" 5 | }, 6 | "dependencies": { 7 | "@oclif/command": "^1.8.0", 8 | "@oclif/config": "^1.17.0", 9 | "@oclif/dev-cli": "^1.26.0", 10 | "@oclif/errors": "^1.3.5", 11 | "@oclif/plugin-help": "^2.2.3", 12 | "@salesforce/core": "^1.3.3", 13 | "ajv": "^6.12.6", 14 | "globby": "^9.2.0", 15 | "jsonlint": "^1.6.3", 16 | "prompts": "^2.4.1", 17 | "shelljs": "^0.8.5" 18 | }, 19 | "author": { 20 | "name": "Wolfgang Mathurin", 21 | "email": "wmathurin@salesforce.com" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /release/release.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* 3 | * Copyright (c) 2019-present, salesforce.com, inc. 4 | * All rights reserved. 5 | * Redistribution and use of this software in source and binary forms, with or 6 | * without modification, are permitted provided that the following conditions 7 | * are met: 8 | * - Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * - Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * - Neither the name of salesforce.com, inc. nor the names of its contributors 14 | * may be used to endorse or promote products derived from this software without 15 | * specific prior written permission of salesforce.com, inc. 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 20 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 | * POSSIBILITY OF SUCH DAMAGE. 27 | */ 28 | 29 | // Dependencies 30 | const path = require('path'), 31 | prompts = require('prompts'), 32 | utils = require('../shared/utils'), 33 | COLOR = require('../shared/outputColors'), 34 | proceedPrompt = require('./common.js').proceedPrompt, 35 | runCmds = require('./common.js').runCmds, 36 | cloneOrClean = require('./common.js').cloneOrClean, 37 | urlForRepo = require('./common.js').urlForRepo, 38 | setAutoYesForPrompts = require('./common.js').setAutoYesForPrompts, 39 | REPO = require('./common.js').REPO, 40 | VERSION = require('../shared/constants.js').version, 41 | os = require("os") 42 | 43 | // Default values for prompt 44 | const tmpDirDefault = "generate-new-dir" 45 | const orgDefault = os.userInfo().username 46 | const masterBranchDefault = "master2" 47 | const devBranchDefault = "dev2" 48 | const docBranchDefault = "gh-pages2" 49 | const versionReleasedDefault = VERSION 50 | const nextVersionDefault = "13.1.0" 51 | const versionCodeReleasedDefault = 88 52 | 53 | // Questions 54 | const QUESTIONS = [ 55 | { 56 | type: 'text', 57 | name: 'tmpDir', 58 | message: 'Work directory ?', 59 | initial: tmpDirDefault 60 | }, 61 | { 62 | type: 'text', 63 | name: 'org', 64 | message: 'Organization ?', 65 | initial: orgDefault 66 | }, 67 | { 68 | type:'confirm', 69 | name: 'isPatch', 70 | message: `Is patch release? (no merge from dev, changes already in master)`, 71 | initial: false 72 | }, 73 | { 74 | type: 'text', 75 | name: 'masterBranch', 76 | message: 'Release branch ?', 77 | initial: masterBranchDefault 78 | }, 79 | { 80 | type: 'text', 81 | name: 'devBranch', 82 | message: 'Development branch ?', 83 | initial: devBranchDefault 84 | }, 85 | { 86 | type: 'text', 87 | name: 'docBranch', 88 | message: 'Doc branch (e.g. gh-pages) ?', 89 | initial: docBranchDefault 90 | }, 91 | { 92 | type: 'text', 93 | name: 'versionReleased', 94 | message: `Version being released ?`, 95 | initial: versionReleasedDefault 96 | }, 97 | { 98 | type: 'number', 99 | name: 'versionCodeReleased', 100 | message: 'Version code for Android being released ?', 101 | initial: versionCodeReleasedDefault 102 | }, 103 | { 104 | type: 'text', 105 | name: 'nextVersion', 106 | message: 'Next version ?', 107 | initial: nextVersionDefault 108 | }, 109 | { 110 | type:'confirm', 111 | name: 'autoYesForPrompts', 112 | message: `Automatically answer yes to all prompts?`, 113 | initial: false 114 | } 115 | ] 116 | 117 | // Calling start 118 | utils.setLogLevel(utils.LOG_LEVELS.DEBUG) 119 | var config = {} 120 | start() 121 | 122 | // 123 | // Main function 124 | // 125 | async function start() { 126 | config = await prompts(QUESTIONS) 127 | 128 | validateConfig() 129 | setAutoYesForPrompts(config.autoYesForPrompts) 130 | 131 | config.nextVersionCode = config.versionCodeReleased + 1 132 | 133 | // Final confirmation 134 | utils.logParagraph([ 135 | ` RELEASING version ${config.versionReleased} (code ${config.versionCodeReleased} on Android) `, 136 | ``, 137 | config.isPatch 138 | ? `Will cut release from ${config.masterBranch} on ${config.org} - will NOT merge ${config.devBranch} into it` 139 | : `Will merge ${config.devBranch} to ${config.masterBranch} on ${config.org}`, 140 | `Will apply tag v${config.versionReleased}`, 141 | `New doc will be published to ${config.docBranch}`, 142 | `Afterwards ${config.devBranch} will be for version ${config.nextVersion} (code ${config.nextVersionCode} on Android)`, 143 | ``, 144 | ` !! MAKE SURE TO USE JDK 8 TO SUCCESSFULLY GENERATE JAVADOC !!` 145 | ], COLOR.magenta) 146 | if (!await proceedPrompt()) { 147 | process.exit(0) 148 | } 149 | 150 | // Release!! 151 | if (config.tmpDir == tmpDirDefault) { 152 | config.tmpDir = utils.mkTmpDir() 153 | } else { 154 | utils.mkDirIfNeeded(config.tmpDir) 155 | } 156 | 157 | await releaseShared() 158 | await releaseAndroid() 159 | await releaseIOS() 160 | await releaseIOSHybrid() 161 | await releaseIOSSpecs() 162 | await releaseIOSSpm() 163 | await releaseCordovaPlugin() 164 | await releaseReactNative() 165 | await releaseTemplates() 166 | await releasePackage() 167 | 168 | // We are testing before publishing to npmjs.org 169 | // So we need to use the github plugin repo uri (not the npmjs package name that's in constants.js) 170 | pluginRepoUri = `${urlForRepo(config.org, REPO.cordovaplugin)}#v${config.versionReleased}` 171 | 172 | utils.logParagraph([ 173 | ` NEXT STEPS: TEST then PUBLISH`, 174 | ``, 175 | `To test the NPM packages, do the following:`, 176 | ` cd ${config.tmpDir}/${REPO.pkg}`, 177 | ` ./test/test_force.js --cli=forceios,forcedroid,forcereact,forcehybrid --pluginrepouri=${pluginRepoUri}`, 178 | ` ./test/test_force.js --cli=forceios,forcedroid,forcereact,forcehybrid --use-sfdx --pluginrepouri=${pluginRepoUri}`, 179 | `You should also open and run the generated apps in XCode / Android Studio.`, 180 | ``, 181 | `To publish to NPM, do the following:`, 182 | ` cd ${config.tmpDir}`, 183 | ` npm publish forceios-${config.versionReleased}.tgz`, 184 | ` npm publish forcedroid-${config.versionReleased}.tgz`, 185 | ` npm publish forcehybrid-${config.versionReleased}.tgz`, 186 | ` npm publish forcereact-${config.versionReleased}.tgz`, 187 | ` npm publish sfdx-mobilesdk-plugin-${config.versionReleased}.tgz`, 188 | ` npm publish salesforce-mobilesdk-cordova-plugin-${config.versionReleased}.tgz`, 189 | ``, 190 | `To publish to Maven Central, do the following:`, 191 | ` cd ${config.tmpDir}/${REPO.android}`, 192 | ` ./publish/publish.sh`, 193 | `` 194 | ], COLOR.magenta) 195 | 196 | } 197 | 198 | // 199 | // Config validation 200 | // 201 | function validateConfig() { 202 | if (Object.keys(config).length < QUESTIONS.length) { 203 | process.exit(1) 204 | } 205 | } 206 | 207 | // 208 | // Release function for shared repo 209 | // 210 | async function releaseShared() { 211 | await releaseRepo(REPO.shared) 212 | } 213 | 214 | // 215 | // Release function for android repo (missing: javadoc generation) 216 | // 217 | async function releaseAndroid() { 218 | await releaseRepo(REPO.android, { 219 | submodulePaths: ['external/shared'], 220 | postReleaseGenerateCmd: generateDocAndroid() 221 | }) 222 | } 223 | 224 | // 225 | // Release function for iOS repo (missing: apple doc generation) 226 | // 227 | async function releaseIOS() { 228 | await releaseRepo(REPO.ios, { 229 | postReleaseGenerateCmd: generateDocIOS() 230 | }) 231 | } 232 | 233 | // 234 | // Release function for iOS-Hybrid repo 235 | // 236 | async function releaseIOSHybrid() { 237 | await releaseRepo(REPO.ioshybrid, { 238 | submodulePaths: ['external/shared', 'external/SalesforceMobileSDK-iOS'] 239 | }) 240 | } 241 | 242 | // 243 | // Release function for iOS-Specs repo 244 | // 245 | async function releaseIOSSpecs() { 246 | const repo = REPO.iosspecs 247 | const cmds = { 248 | msg: `PROCESSING ${repo}`, 249 | cmds: [ 250 | cloneOrClean(config.org, repo, config.tmpDir), 251 | `git checkout ${config.masterBranch}`, 252 | `./update.sh -b ${config.masterBranch} -v ${config.versionReleased}`, 253 | commitAndPushMaster() 254 | ] 255 | } 256 | await runCmds(path.join(config.tmpDir, repo), cmds) 257 | } 258 | 259 | // 260 | // Release function for iOS-Spm repo 261 | // 262 | async function releaseIOSSpm() { 263 | const repo = REPO.iosspm 264 | const cmds = { 265 | msg: `PROCESSING ${repo}`, 266 | cmds: [ 267 | cloneOrClean(config.org, repo, config.tmpDir), 268 | `git checkout ${config.masterBranch}`, 269 | `./build_xcframeworks.sh -r ${config.org} -b ${config.masterBranch}`, 270 | commitAndPushMaster(), 271 | tagMaster(true) // SPM needs versions of the form X.Y.Z (where X, Y, Z are integers) 272 | ] 273 | } 274 | await runCmds(path.join(config.tmpDir, repo), cmds) 275 | } 276 | 277 | // 278 | // Release function for CordovaPlugin repo 279 | // 280 | async function releaseCordovaPlugin() { 281 | await releaseRepo(REPO.cordovaplugin, { 282 | masterPostMergeCmd:`./tools/update.sh -b ${config.masterBranch}`, 283 | postReleaseGenerateCmd: generateNpmPackageForCordovaPlugin(), 284 | devPostMergeCmd:`./tools/update.sh -b ${config.devBranch}` 285 | }) 286 | } 287 | 288 | // 289 | // Release function for ReactNative repo 290 | // 291 | async function releaseReactNative() { 292 | await releaseRepo(REPO.reactnative) 293 | } 294 | 295 | // 296 | // Release function for Templates repo 297 | // 298 | async function releaseTemplates() { 299 | await releaseRepo(REPO.templates) 300 | } 301 | 302 | // 303 | // Release function for Package repo 304 | // 305 | async function releasePackage() { 306 | await releaseRepo(REPO.pkg, { 307 | postReleaseGenerateCmd: generateNpmPackages() 308 | }) 309 | } 310 | 311 | // 312 | // Helper functions 313 | // 314 | async function releaseRepo(repo, params) { 315 | params = params || {} 316 | const cmds = { 317 | msg: `PROCESSING ${repo}`, 318 | cmds: [ 319 | cloneOrClean(config.org, repo, config.tmpDir), 320 | // master 321 | { 322 | msg: `Working on ${config.masterBranch}`, 323 | cmds: [ 324 | checkoutBranch(config.masterBranch), 325 | config.isPatch ? null : mergeBranch(config.devBranch, config.masterBranch, params.submodulePaths), 326 | params.masterPostMergeCmd, 327 | setVersion(config.versionReleased, false, config.versionCodeReleased), 328 | updateSubmodules(config.masterBranch, params.submodulePaths), 329 | commitAndPushMaster(), 330 | tagMaster(), 331 | params.postReleaseGenerateCmd 332 | ] 333 | }, 334 | // dev 335 | { 336 | msg: `Working on ${config.devBranch}`, 337 | cmds: [ 338 | checkoutBranch(config.devBranch), 339 | mergeBranch(config.masterBranch, config.devBranch, params.submodulePaths), 340 | params.devPostMergeCmd, 341 | setVersion(config.nextVersion, true, config.nextVersionCode), 342 | updateSubmodules(config.devBranch, params.submodulePaths), 343 | commitAndPushDev() 344 | ] 345 | }, 346 | // In master branch 347 | `git checkout ${config.masterBranch}` 348 | ] 349 | } 350 | await runCmds(path.join(config.tmpDir, repo), cmds) 351 | } 352 | 353 | function setVersion(version, isDev, code) { 354 | return { 355 | msg: `Running setVersion ${version}`, 356 | cmds: [ 357 | `./setVersion.sh -v ${version}` + (isDev != undefined ? ` -d ${isDev ? "yes" : "no"}`:'') + (code != undefined ? ` -c ${code}`:'') 358 | ] 359 | } 360 | } 361 | 362 | function updateSubmodules(branch, submodulePaths) { 363 | const cmds = !submodulePaths ? null : { 364 | msg: `Updating submodules to ${branch}`, 365 | cmds: [ 366 | `git submodule sync`, 367 | `git submodule update --init`, 368 | ... submodulePaths.map(path => { return {cmd:`git pull origin ${branch}`, reldir:path} }) 369 | ] 370 | } 371 | return cmds 372 | } 373 | 374 | function commitAndPushMaster() { 375 | return { 376 | msg: `Pushing to ${config.masterBranch}`, 377 | cmds: [ 378 | `git add *`, 379 | {cmd: `git commit -m "Mobile SDK ${config.versionReleased}"`, ignoreError: true}, 380 | `git push origin ${config.masterBranch}`, 381 | ] 382 | } 383 | } 384 | 385 | function tagMaster(noTagPrefix) { 386 | const tagPrefix = noTagPrefix ? '' : 'v' 387 | return { 388 | msg: `Tagging ${config.masterBranch} with ${tagPrefix}${config.versionReleased}`, 389 | cmds: [ 390 | `git tag ${tagPrefix}${config.versionReleased}`, 391 | `git push origin ${config.masterBranch} --tag`, 392 | ] 393 | } 394 | } 395 | 396 | function commitAndPushDev() { 397 | return { 398 | msg: `Pushing to ${config.devBranch}`, 399 | cmds: [ 400 | `git add *`, 401 | `git commit -m "Updating version numbers to ${config.nextVersion}"`, 402 | `git push origin ${config.devBranch}` 403 | ] 404 | } 405 | } 406 | 407 | function checkoutBranch(branchToCheckout) { 408 | return { 409 | msg: `Checking out ${branchToCheckout}`, 410 | cmds: [ 411 | `git checkout ${branchToCheckout}`, 412 | `git clean -fdxf`, // NB: need double -f to remove deleted submodule directory - see https://stackoverflow.com/a/10761699 413 | `git submodule sync`, 414 | `git submodule update`, 415 | ] 416 | } 417 | } 418 | 419 | function mergeBranch(branchToMergeFrom, branchToMergeInto, submodulePaths) { 420 | const msg = `Merging ${branchToMergeFrom} into ${branchToMergeInto}` 421 | return { 422 | msg: msg, 423 | cmd: `git merge -Xours --no-ff -m "${msg}" origin/${branchToMergeFrom}`, 424 | cmdIfError: !submodulePaths 425 | ? null 426 | : { 427 | // git does not do any merge operations on submodule version 428 | // if there is a submodule conflict keep ours 429 | msg: `Attempting to address merge error`, 430 | cmds: [ 431 | ... submodulePaths.map(path => { return `git add ${path}`}), 432 | {cmd: `git commit -m "${msg}"`, ignoreError: true} 433 | ] 434 | } 435 | } 436 | } 437 | 438 | function generateDocIOS() { 439 | return { 440 | msg: `Generating docs for iOS`, 441 | cmds: [ 442 | `git checkout ${config.masterBranch}`, 443 | `./docs/generate_docs.sh`, 444 | `mv ./build/artifacts/doc ../docIOS`, 445 | `git checkout ${config.docBranch}`, 446 | `rm -rf Documentation/*`, 447 | `mv ../docIOS/* ./Documentation/`, 448 | `git add Documentation`, 449 | `git commit -m "Apple doc for Mobile SDK ${config.versionReleased}"`, 450 | `git push origin ${config.docBranch}` 451 | ] 452 | } 453 | } 454 | 455 | function generateDocAndroid() { 456 | return { 457 | msg: `Generating docs for Android`, 458 | cmds: [ 459 | `git checkout ${config.masterBranch}`, 460 | `./tools/generate_doc.sh`, 461 | `mv ./doc ../docAndroid`, 462 | `git checkout ${config.docBranch}`, 463 | `rm -rf *`, 464 | `mv ../docAndroid/* .`, 465 | `git add *`, 466 | `git commit -m "Java doc for Mobile SDK ${config.versionReleased}"`, 467 | `git push origin ${config.docBranch}` 468 | ] 469 | } 470 | } 471 | 472 | function generateNpmPackageForCordovaPlugin() { 473 | return { 474 | msg: `Generating npm package`, 475 | cmds: [ 476 | `git checkout ${config.masterBranch}`, 477 | `npm pack`, 478 | `mv ./salesforce-mobilesdk-cordova-plugin*.tgz ../`, 479 | `git checkout -- .` 480 | ] 481 | } 482 | } 483 | 484 | function generateNpmPackages() { 485 | return { 486 | msg: `Generating npm packages`, 487 | cmds: [ 488 | `git checkout ${config.masterBranch}`, 489 | `node ./install.js`, 490 | `node ./pack/pack.js --cli=forceios,forcedroid,forcehybrid,forcereact`, 491 | `node ./pack/pack.js --sfdx-plugin`, 492 | `mv ./force*.tgz ../`, 493 | `mv ./sfdx-*.tgz ../`, 494 | `git checkout -- .` 495 | ] 496 | } 497 | } 498 | -------------------------------------------------------------------------------- /release/setup_test_branches.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* 3 | * Copyright (c) 2019-present, salesforce.com, inc. 4 | * All rights reserved. 5 | * Redistribution and use of this software in source and binary forms, with or 6 | * without modification, are permitted provided that the following conditions 7 | * are met: 8 | * - Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * - Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * - Neither the name of salesforce.com, inc. nor the names of its contributors 14 | * may be used to endorse or promote products derived from this software without 15 | * specific prior written permission of salesforce.com, inc. 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 20 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 | * POSSIBILITY OF SUCH DAMAGE. 27 | */ 28 | 29 | // Dependencies 30 | const path = require('path'), 31 | prompts = require('prompts'), 32 | utils = require('../shared/utils'), 33 | COLOR = require('../shared/outputColors'), 34 | proceedPrompt = require('./common.js').proceedPrompt, 35 | runCmds = require('./common.js').runCmds, 36 | cloneOrClean = require('./common.js').cloneOrClean, 37 | setAutoYesForPrompts = require('./common.js').setAutoYesForPrompts, 38 | REPO = require('./common.js').REPO, 39 | VERSION = require('../shared/constants.js').version 40 | 41 | // Default values for prompt 42 | const tmpDirDefault = "generate-new-dir" 43 | const testOrgDefault = "wmathurin" 44 | const testMasterBranchDefault = "master2" 45 | const testDevBranchDefault = "dev2" 46 | const testDocBranchDefault = "gh-pages2" 47 | const testVersionDefault = VERSION 48 | 49 | const templatesPackageJsons = [ 50 | './AndroidIDPTemplate/package.json', 51 | './AndroidNativeKotlinTemplate/package.json', 52 | './AndroidNativeLoginTemplate/package.json', 53 | './AndroidNativeTemplate/package.json', 54 | './HybridLocalTemplate/package.json', 55 | './HybridRemoteTemplate/package.json', 56 | './MobileSyncExplorerKotlinTemplate/package.json', 57 | './MobileSyncExplorerReactNative/package.json', 58 | './MobileSyncExplorerSwift/package.json', 59 | './ReactNativeDeferredTemplate/package.json', 60 | './ReactNativeTemplate/package.json', 61 | './ReactNativeTypeScriptTemplate/package.json', 62 | './iOSIDPTemplate/package.json', 63 | './iOSNativeLoginTemplate/package.json', 64 | './iOSNativeSwiftEncryptedNotificationTemplate/package.json', 65 | './iOSNativeSwiftPackageManagerTemplate/package.json', 66 | './iOSNativeSwiftTemplate/package.json', 67 | './iOSNativeTemplate/package.json' 68 | ] 69 | 70 | // Questions 71 | const QUESTIONS = [ 72 | { 73 | type: 'text', 74 | name: 'tmpDir', 75 | message: 'Work directory ?', 76 | initial: tmpDirDefault 77 | }, 78 | { 79 | type: 'text', 80 | name: 'testOrg', 81 | message: 'Organization ?', 82 | initial: testOrgDefault 83 | }, 84 | { 85 | type: 'text', 86 | name: 'testMasterBranch', 87 | message: 'Name of test master branch ?', 88 | initial: testMasterBranchDefault 89 | }, 90 | { 91 | type: 'text', 92 | name: 'testDevBranch', 93 | message: 'Name of test dev branch ?', 94 | initial: testDevBranchDefault 95 | }, 96 | { 97 | type: 'text', 98 | name: 'testDocBranch', 99 | message: 'Name of test doc branch ?', 100 | initial: testDocBranchDefault 101 | }, 102 | { 103 | type: 'text', 104 | name: 'testVersion', 105 | message: `Name of test version ?`, 106 | initial: testVersionDefault 107 | }, 108 | { 109 | type:'confirm', 110 | name: 'cleanupOnly', 111 | message: `Cleanup only?`, 112 | initial: false 113 | }, 114 | { 115 | type:'confirm', 116 | name: 'autoYesForPrompts', 117 | message: `Automatically answer yes to all prompts?`, 118 | initial: false 119 | } 120 | ] 121 | 122 | // Calling start 123 | utils.setLogLevel(utils.LOG_LEVELS.DEBUG) 124 | var config = {} 125 | start() 126 | 127 | // 128 | // Main function 129 | // 130 | async function start() { 131 | config = await prompts(QUESTIONS) 132 | 133 | validateConfig() 134 | setAutoYesForPrompts(config.autoYesForPrompts) 135 | 136 | // Final confirmation 137 | utils.logParagraph([ 138 | ` SETTING UP TEST BRANCHES FOR RELEASE TESTING `, 139 | ``, 140 | `Will drop ${config.testMasterBranch} ` + (config.cleanupOnly ? "" : `and recreate it off of master on all repos in ${config.testOrg}`), 141 | `Will drop ${config.testDevBranch} ` + (config.cleanupOnly ? "" : `and recreate it off of dev on all applicable repos`), 142 | `Will drop tag v${config.testVersion}` 143 | ], COLOR.magenta) 144 | 145 | if (!await proceedPrompt()) { 146 | process.exit(0) 147 | } 148 | 149 | if (config.tmpDir == tmpDirDefault) { 150 | config.tmpDir = utils.mkTmpDir() 151 | } else { 152 | utils.mkDirIfNeeded(config.tmpDir) 153 | } 154 | 155 | await prepareRepo(REPO.shared) 156 | await prepareRepo(REPO.android, {hasDoc:true, filesWithOrg: ['.gitmodules', './libs/SalesforceReact/package.json'], submodulePaths:['./external/shared']}) 157 | await prepareRepo(REPO.ios, {hasDoc:true}) 158 | await prepareRepo(REPO.ioshybrid, {filesWithOrg: ['.gitmodules'], submodulePaths:['./external/shared', './external/SalesforceMobileSDK-iOS']}) 159 | await prepareRepo(REPO.iosspecs, {noTag: true, noDev: true, filesWithOrg:['update.sh']}) 160 | await prepareRepo(REPO.iosspm, {noTagPrefix: true, noDev: true}) 161 | await prepareRepo(REPO.cordovaplugin, {filesWithOrg:['./plugin.xml','./tools/update.sh']}) 162 | await prepareRepo(REPO.reactnative) 163 | await prepareRepo(REPO.templates, {filesWithOrg:templatesPackageJsons}) 164 | await prepareRepo(REPO.pkg, {filesWithOrg:['./shared/constants.js']}) 165 | } 166 | 167 | async function prepareRepo(repo, params) { 168 | params = params || {} 169 | const cmds = { 170 | msg: `PROCESSING ${repo}`, 171 | cmds: [ 172 | cloneOrClean(config.testOrg, repo, config.tmpDir), 173 | { 174 | msg: `Cleaning up test branches/tag in ${repo}`, 175 | cmds: [ 176 | deleteBranch(config.testMasterBranch), 177 | !params.noDev ? deleteBranch(config.testDevBranch) : null, 178 | params.hasDoc ? deleteBranch(config.testDocBranch) : null, 179 | !params.noTag ? deleteTag(config.testVersion, params.noTagPrefix) : null 180 | ] 181 | }, 182 | config.cleanupOnly ? null : { 183 | msg: `Setting up test branches in ${repo}`, 184 | cmds: [ 185 | { 186 | msg: `Setting up ${config.testMasterBranch}`, 187 | cmds: [ 188 | createBranch(config.testMasterBranch, 'master'), 189 | params.filesWithOrg ? pointToFork(config.testMasterBranch, params) : null, 190 | params.submodulePaths ? updateSubmodules(config.testMasterBranch, params) : null 191 | ] 192 | }, 193 | params.hasDoc ? createBranch(config.testDocBranch, 'gh-pages') : null, 194 | params.noDev ? null : { 195 | msg: `Setting up ${config.testDevBranch}`, 196 | cmds: [ 197 | createBranch(config.testDevBranch, 'dev'), 198 | mergeMasterToDev(), 199 | params.filesWithOrg ? pointToFork(config.testDevBranch, params) : null, 200 | params.submodulePaths ? updateSubmodules(config.testDevBranch, params) : null 201 | ] 202 | } 203 | ] 204 | } 205 | ] 206 | } 207 | 208 | await runCmds(path.join(config.tmpDir, repo), cmds) 209 | } 210 | 211 | // 212 | // Helper functions 213 | // 214 | function deleteBranch(branch) { 215 | return { 216 | msg: `Deleting ${branch} branch`, 217 | cmds: [ 218 | `git checkout master`, 219 | {cmd: `git branch -D ${branch}`, ignoreError: true}, 220 | {cmd: `git push origin :${branch}`, ignoreError: true} 221 | ] 222 | } 223 | } 224 | 225 | function deleteTag(tag, noTagPrefix) { 226 | const fullTag = `${noTagPrefix ? '' : 'v'}${tag}` 227 | return { 228 | msg: `Deleting ${fullTag} tag`, 229 | cmds: [ 230 | {cmd: `git tag -d ${fullTag}`, ignoreError: true}, 231 | {cmd: `git push --delete origin ${fullTag}`, ignoreError: true} 232 | ] 233 | } 234 | } 235 | 236 | function createBranch(branch, rootBranch) { 237 | return { 238 | msg: `Creating ${branch} branch off of ${rootBranch}`, 239 | cmds: [ 240 | `git checkout ${rootBranch}`, 241 | `git checkout -b ${branch}`, 242 | `git push origin ${branch}` 243 | ] 244 | } 245 | } 246 | 247 | function mergeMasterToDev() { 248 | return { 249 | msg: `Merging ${config.testMasterBranch} to ${config.testDevBranch}`, 250 | cmds: [ 251 | `git checkout ${config.testDevBranch}`, 252 | `git submodule sync`, 253 | `git submodule update --init`, 254 | `git merge -Xours -m "Merge from ${config.testMasterBranch}" ${config.testMasterBranch}`, 255 | `git push origin ${config.testDevBranch}` 256 | ] 257 | } 258 | } 259 | 260 | function pointToFork(branch, params) { 261 | return { 262 | msg: `Pointing to fork ${config.testOrg} in ${branch} branch`, 263 | cmds: [ 264 | `git checkout ${branch}`, 265 | ... params.filesWithOrg.map(path => { 266 | return { 267 | msg: `Editing file ${path}`, 268 | cmds: [ 269 | `gsed -i "s/forcedotcom/${config.testOrg}/g" ${path}`, 270 | `git add ${path}` 271 | ] 272 | } 273 | }), 274 | `git commit -m "Pointing to fork"`, 275 | `git push origin ${branch}`, 276 | ] 277 | } 278 | } 279 | 280 | function updateSubmodules(branch, params) { 281 | return { 282 | msg: `Updating submodules in ${branch} branch`, 283 | cmds: [ 284 | `git checkout ${branch}`, 285 | `git submodule sync`, 286 | `git submodule update --init`, 287 | ... params.submodulePaths.map(path => { 288 | return { 289 | msg: `Fixing submodule ${path}`, 290 | cmds: [ 291 | {cmd: `git pull origin ${branch}`, reldir:path }, 292 | `git add ${path}` 293 | ] 294 | } 295 | }), 296 | `git commit -m "Updating submodules"`, 297 | `git push origin ${branch}`, 298 | ] 299 | } 300 | } 301 | 302 | 303 | // 304 | // Config validation 305 | // 306 | function validateConfig() { 307 | if (Object.keys(config).length < QUESTIONS.length) { 308 | process.exit(1) 309 | } 310 | 311 | if (config.testOrg === 'forcedotcom') { 312 | utils.logError(`You can't use ${config.testOrg} for testing`) 313 | process.exit(1) 314 | } 315 | 316 | if (config.testMasterBranch === 'master') { 317 | utils.logError(`You can't use ${config.testMasterBranch} for testing`) 318 | process.exit(1) 319 | } 320 | 321 | if (config.testDevBranch === 'dev') { 322 | utils.logError(`You can't use ${config.testDevBranch} for testing`) 323 | process.exit(1) 324 | } 325 | 326 | if (config.testDocBranch === 'gh-pages') { 327 | utils.logError(`You can't use ${config.testDocBranch} for testing`) 328 | process.exit(1) 329 | } 330 | 331 | if (config.testVersion < VERSION) { 332 | utils.logError(`You can't use ${config.testVersion} for testing`) 333 | process.exit(1) 334 | } 335 | 336 | } 337 | -------------------------------------------------------------------------------- /setversion.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #set -x 4 | 5 | OPT_VERSION="" 6 | OPT_IS_DEV="no" 7 | RED='\033[0;31m' 8 | YELLOW='\033[0;33m' 9 | NC='\033[0m' # No Color 10 | 11 | usage () 12 | { 13 | echo "Use this script to set Mobile SDK version number in source files" 14 | echo "Usage: $0 -v [-d ]" 15 | echo " where: version is the version e.g. 7.1.0" 16 | echo " isDev is yes or no (default) to indicate whether it is a dev build" 17 | } 18 | 19 | parse_opts () 20 | { 21 | while getopts v:d: command_line_opt 22 | do 23 | case ${command_line_opt} in 24 | v) OPT_VERSION=${OPTARG};; 25 | d) OPT_IS_DEV=${OPTARG};; 26 | esac 27 | done 28 | 29 | if [ "${OPT_VERSION}" == "" ] 30 | then 31 | echo -e "${RED}You must specify a value for the version.${NC}" 32 | usage 33 | exit 1 34 | fi 35 | } 36 | 37 | # Helper functions 38 | update_package_json () 39 | { 40 | local file=$1 41 | local version=$2 42 | gsed -i "s/\"version\":.*\"[^\"]*\"/\"version\": \"${version}\"/g" ${file} 43 | } 44 | 45 | update_constants_js () 46 | { 47 | local file=$1 48 | local version=$2 49 | local isDev=$3 50 | local newPodSpecVersion="tag=\"v${version}\"" 51 | gsed -i "s/var VERSION.*=.*'[^\"]*'/var VERSION= '${version}'/g" ${file} 52 | 53 | gsed -i "s/^\([ ]*\)[/][/]\(.*RepoUri\)/\1\2/g" ${file} # uncomment uri's 54 | if [ $isDev == "yes" ] 55 | then 56 | gsed -i "s/^\(.*RepoUri.*[@#]v\)/\/\/\1/g" ${file} # comment uri's pointing to tag 57 | else 58 | gsed -i "s/^\(.*RepoUri.*#dev\)/\/\/\1/g" ${file} # comment uri's pointing to dev 59 | fi 60 | } 61 | 62 | 63 | parse_opts "$@" 64 | 65 | echo -e "${YELLOW}*** SETTING VERSION TO ${OPT_VERSION}, IS DEV = ${OPT_IS_DEV} ***${NC}" 66 | 67 | echo "*** Updating package.json files ***" 68 | update_package_json "./ios/package.json" "${OPT_VERSION}" 69 | update_package_json "./android/package.json" "${OPT_VERSION}" 70 | update_package_json "./sfdx/package.json" "${OPT_VERSION}" 71 | update_package_json "./react/package.json" "${OPT_VERSION}" 72 | update_package_json "./hybrid/package.json" "${OPT_VERSION}" 73 | 74 | 75 | echo "*** Updating constants.js ***" 76 | update_constants_js "./shared/constants.js" "${OPT_VERSION}" "${OPT_IS_DEV}" 77 | 78 | -------------------------------------------------------------------------------- /sfdx/.gitignore: -------------------------------------------------------------------------------- 1 | oclif.manifest.json 2 | oclif/ -------------------------------------------------------------------------------- /sfdx/bin/run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // This is needed to support debugging code that spawns node processes. This code will start a node process and it will 4 | // inherit the parent process' debug settings resulting in a port in use error. Un-comment to work around. 5 | // process.env.NODE_OPTIONS = '--inspect=0'; 6 | 7 | require('@oclif/command').run() 8 | .then(require('@oclif/command/flush')) 9 | .catch(require('@oclif/errors/handle')); 10 | -------------------------------------------------------------------------------- /sfdx/generate_oclif.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // Dependencies 4 | var fs = require('fs'), 5 | path = require('path'), 6 | SDK = require('../shared/constants'), 7 | configHelper = require('../shared/configHelper'); 8 | 9 | function generateCommandClasses() { 10 | 11 | for (var cliName in SDK.forceclis) { 12 | var cli = SDK.forceclis[cliName]; 13 | var dirPath = path.resolve(__dirname, 'oclif', 'mobilesdk', cli.topic); 14 | fs.mkdirSync(dirPath, {recursive: true}); 15 | cli.commands.map(commandName => { 16 | generateCommmandClass(cli, commandName); 17 | }) 18 | } 19 | } 20 | 21 | function generateCommmandClass(cli, commandName) { 22 | var className = capitalize(cli.topic) + capitalize(commandName) + 'Command'; 23 | var classPath = path.resolve(__dirname, 'oclif', 'mobilesdk', cli.topic, commandName + '.js'); 24 | var classContent = [ 25 | `/*`, 26 | ` * Copyright (c) 2019-present, salesforce.com, inc.`, 27 | ` * All rights reserved.`, 28 | ` * Redistribution and use of this software in source and binary forms, with or`, 29 | ` * without modification, are permitted provided that the following conditions`, 30 | ` * are met:`, 31 | ` * - Redistributions of source code must retain the above copyright notice, this`, 32 | ` * list of conditions and the following disclaimer.`, 33 | ` * - Redistributions in binary form must reproduce the above copyright notice,`, 34 | ` * this list of conditions and the following disclaimer in the documentation`, 35 | ` * and/or other materials provided with the distribution.`, 36 | ` * - Neither the name of salesforce.com, inc. nor the names of its contributors`, 37 | ` * may be used to endorse or promote products derived from this software without`, 38 | ` * specific prior written permission of salesforce.com, inc.`, 39 | ` * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"`, 40 | ` * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE`, 41 | ` * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE`, 42 | ` * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE`, 43 | ` * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR`, 44 | ` * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF`, 45 | ` * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS`, 46 | ` * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN`, 47 | ` * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)`, 48 | ` * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE`, 49 | ` * POSSIBILITY OF SUCH DAMAGE.`, 50 | ` */`, 51 | `const path = require('path');`, 52 | ``, 53 | `const OclifAdapter = require('../../../shared/oclifAdapter');`, 54 | `const SDK = require('../../../shared/constants');`, 55 | , 56 | `class ${className} extends OclifAdapter {`, 57 | ` static get command() {`, 58 | ` return OclifAdapter.getCommand.call(this, SDK.forceclis.${cli.name}, path.parse(__filename).name);`, 59 | ` }`, 60 | ``, 61 | ` async run() {`, 62 | ` this.execute(SDK.forceclis.${cli.name}, ${className});`, 63 | ` }`, 64 | `}`, 65 | ``, 66 | `${className}.description = OclifAdapter.formatDescription(${className}.command.description,`, 67 | ` ${className}.command.help);`, 68 | ``, 69 | `${className}.longDescription = ${className}.command.longDescription;`, 70 | `${className}.hidden = ${className}.command.hidden;`, 71 | `${className}.flags = OclifAdapter.toFlags(${className}.command.args);`, 72 | ``, 73 | `exports.${className} = ${className};` 74 | ].join('\n'); 75 | fs.writeFileSync(classPath, classContent); 76 | } 77 | 78 | function capitalize(s) { 79 | return s.charAt(0).toUpperCase() + s.slice(1); 80 | } 81 | 82 | // Main 83 | function main() { 84 | generateCommandClasses(); 85 | } 86 | 87 | main() 88 | 89 | 90 | -------------------------------------------------------------------------------- /sfdx/generate_readme.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | print() { 4 | echo "$1" >> README.md 5 | } 6 | 7 | run() { 8 | echo "\`\`\`" >> README.md 9 | echo "-> $1" >> README.md 10 | # removing colors, make sure to install gnu-sed (brew install gnu-sed) 11 | $1 | gsed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[mGK]//g" >> README.md 12 | echo "\`\`\`" >> README.md 13 | echo "" >> README.md 14 | } 15 | 16 | cat < README.md 17 | # sfdx-mobilesdk-plugin 18 | 19 | A plugin for the Salesforce CLI to create mobile applications to interface with the [Salesforce Platform](http://www.salesforce.com/platform/overview/), leveraging the [Salesforce Mobile SDK for iOS](https://github.com/forcedotcom/SalesforceMobileSDK-iOS) and the [Salesforce Mobile SDK for Android](https://github.com/forcedotcom/SalesforceMobileSDK-Android) repos. 20 | 21 | ## Setup 22 | 23 | ### Install from source 24 | 25 | 1. Install the Salesforce CLI (https://developer.salesforce.com/tools/salesforcecli). 26 | 27 | 2. Clone the repository: \`git clone git@github.com:forcedotcom/SalesforceMobileSDK-Package\` 28 | 29 | 3. Install npm modules: \`npm install\` 30 | 31 | 4. Generate oclif command classes \`./sfdx/generate_oclif.js\` 32 | 33 | 5. Link the plugin: \`sf plugins link sfdx\` 34 | 35 | ### Install as plugin 36 | 37 | 1. Install plugin: \`sf plugins install sfdx-mobilesdk-plugin\` 38 | 39 | EOT 40 | 41 | print "## Help" 42 | run 'sf mobilesdk --help' 43 | print "## Create a native iOS application " 44 | print "### Help for iOS" 45 | run 'sf mobilesdk ios --help' 46 | print "### Create Objective-C (native) or Swift (native_swift) application" 47 | run 'sf mobilesdk ios create --help' 48 | print "### List available native iOS templates" 49 | run 'sf mobilesdk ios listtemplates --help' 50 | print "### Create iOS application from template" 51 | run 'sf mobilesdk ios createwithtemplate --help' 52 | print "### Check store or syncs config" 53 | run 'sf mobilesdk ios checkconfig --help' 54 | 55 | print "## Create a native Android application " 56 | print "### Help for Android" 57 | run 'sf mobilesdk android --help' 58 | print "### Create Java (native) or Kotlin (native_kotlin) application" 59 | run 'sf mobilesdk android create --help' 60 | print "### List available native Android templates" 61 | run 'sf mobilesdk android listtemplates --help' 62 | print "### Create Android application from template" 63 | run 'sf mobilesdk android createwithtemplate --help' 64 | print "### Check store or syncs config" 65 | run 'sf mobilesdk android checkconfig --help' 66 | 67 | print "## Create an hybrid application " 68 | print "### Help for hybrid" 69 | run 'sf mobilesdk hybrid --help' 70 | print "### Create hybrid application" 71 | run 'sf mobilesdk hybrid create --help' 72 | print "### List available hybrid templates" 73 | run 'sf mobilesdk hybrid listtemplates --help' 74 | print "### Create hybrid application from template" 75 | run 'sf mobilesdk hybrid createwithtemplate --help' 76 | print "### Check store or syncs config" 77 | run 'sf mobilesdk hybrid checkconfig --help' 78 | 79 | print "## Create a React Native application" 80 | print "### Help for React Native" 81 | run 'sf mobilesdk reactnative --help' 82 | print "### Create React Native application" 83 | run 'sf mobilesdk reactnative create --help' 84 | print "### List available React Native templates" 85 | run 'sf mobilesdk reactnative listtemplates --help' 86 | print "### Create React Native application from template" 87 | run 'sf mobilesdk reactnative createwithtemplate --help' 88 | print "### Check store or syncs config" 89 | run 'sf mobilesdk reactnative checkconfig --help' 90 | 91 | -------------------------------------------------------------------------------- /sfdx/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sfdx-mobilesdk-plugin", 3 | "version": "13.1.0", 4 | "description": "Salesforce CLI plugin for creating mobile apps based on the Salesforce Mobile SDK", 5 | "keywords": [ 6 | "mobilesdk", 7 | "salesforce", 8 | "mobile", 9 | "sdk", 10 | "salesforcedx", 11 | "salesforce-dx", 12 | "oclif-plugin", 13 | "sfdx-plugin" 14 | ], 15 | "homepage": "https://github.com/forcedotcom/SalesforceMobileSDK-Package", 16 | "bugs": "https://github.com/forcedotcom/SalesforceMobileSDK-Package/issues", 17 | "licenses": [ 18 | { 19 | "type": "Salesforce.com Mobile SDK License", 20 | "url": "https://github.com/forcedotcom/SalesforceMobileSDK-iOS/blob/master/LICENSE.md" 21 | } 22 | ], 23 | "engines": { 24 | "node": ">=8.10.0" 25 | }, 26 | "dependencies": { 27 | "@oclif/command": "1", 28 | "@oclif/config": "1", 29 | "@oclif/errors": "1", 30 | "@oclif/plugin-help": "2", 31 | "@salesforce/core": "^1.1.2", 32 | "ajv": "^8.11.0", 33 | "globby": "^9.0.0", 34 | "jsonlint": "^1.6.3", 35 | "shelljs": "0.8.5" 36 | }, 37 | "devDependencies": { 38 | "@oclif/dev-cli": "^1.26.10" 39 | }, 40 | "repository": { 41 | "type": "git", 42 | "url": "https://github.com/forcedotcom/SalesforceMobileSDK-Package.git" 43 | }, 44 | "author": { 45 | "name": "Wolfgang Mathurin", 46 | "email": "wmathurin@salesforce.com" 47 | }, 48 | "contributors": [ 49 | { 50 | "name": "Bharath Hariharan", 51 | "email": "bhariharan@salesforce.com" 52 | } 53 | ], 54 | "files": [ 55 | "/shared/**/*.js", 56 | "/oclif/**/*.js", 57 | "/bin" 58 | ], 59 | "oclif": { 60 | "commands": "./oclif", 61 | "topics": { 62 | "mobilesdk": { 63 | "description": "create mobile apps based on the Salesforce Mobile SDK" 64 | }, 65 | "mobilesdk:ios": { 66 | "description": "create an iOS native mobile application" 67 | }, 68 | "mobilesdk:android": { 69 | "description": "create an Android native mobile application" 70 | }, 71 | "mobilesdk:hybrid": { 72 | "description": "create a hybrid mobile application" 73 | }, 74 | "mobilesdk:reactnative": { 75 | "description": "create a React Native mobile application" 76 | } 77 | } 78 | }, 79 | "scripts": { 80 | "prepack": "node_modules/.bin/oclif-dev manifest" 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /sfdx/shared: -------------------------------------------------------------------------------- 1 | ../shared -------------------------------------------------------------------------------- /shared/commandLineUtils.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-present, salesforce.com, inc. 3 | * All rights reserved. 4 | * Redistribution and use of this software in source and binary forms, with or 5 | * without modification, are permitted provided that the following conditions 6 | * are met: 7 | * - Redistributions of source code must retain the above copyright notice, this 8 | * list of conditions and the following disclaimer. 9 | * - Redistributions in binary form must reproduce the above copyright notice, 10 | * this list of conditions and the following disclaimer in the documentation 11 | * and/or other materials provided with the distribution. 12 | * - Neither the name of salesforce.com, inc. nor the names of its contributors 13 | * may be used to endorse or promote products derived from this software without 14 | * specific prior written permission of salesforce.com, inc. 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | // Helper module for parsing the command line arguments to our package front-end. 29 | 30 | var readline = require('readline'); 31 | var outputColors = require('./outputColors'); 32 | 33 | /** 34 | * Parses an array of command line arguments, each of the form '--argName=argValue', or 35 | * optionally, '--argName'. Treats '--argName=' as '--argName'. 36 | * 37 | * @param {Array} argsArray The array of String arguments of the specified format. 38 | * @return A map in the form of { 'argName': 'argValue' [, ...] } 39 | */ 40 | var parseArgs = function(argsArray) { 41 | var argMap = {}; 42 | for (var i = 0; i < argsArray.length; i++) { 43 | var fullArg = argsArray[i]; 44 | var argSplitRegExp = /^--([^=]+)(=(.*))?$/; 45 | if (!argSplitRegExp.test(fullArg)) { 46 | throw new Error('Illegal argument: ' + fullArg); 47 | } 48 | var argName = fullArg.replace(argSplitRegExp, "$1"); 49 | argName = argName.toLocaleLowerCase(); 50 | var argVal = fullArg.replace(argSplitRegExp, "$3"); 51 | argMap[argName] = argVal; 52 | } 53 | 54 | return argMap; 55 | }; 56 | 57 | /** 58 | * A method to process command line arguments interactively. Any arguments that were not specified 59 | * on the command line will be prompted for on stdin, for the user to configure. 60 | * 61 | * @param {Array} argsArray The array of command line arguments (if any), of the form --argName=argValue, or --argName. 62 | * @param {ArgProcessorList} argProcessorList A list of arguments and their various processing characteristics. 63 | * @param {Function} callback The callback method to invoke once all arguments have been processed. 64 | */ 65 | var processArgsInteractive = function(argsArray, argProcessorList, callback) { 66 | // Get any initial arguments from the command line. 67 | var argsMap = parseArgs(argsArray); 68 | 69 | processArgsInteractiveHelper(argsMap, argProcessorList, callback, 0); 70 | }; 71 | 72 | /** 73 | * (Recursive) helper function for processArgsInteractive. 74 | * @param argsMap A map of arguments in the form of { 'argName': 'argValue' [, ...] }. 75 | * @param {ArgProcessorList} argProcessorList A list of arguments and their various processing characteristics. 76 | * @param {Function} callback The callback method to invoke once all arguments have been processed. 77 | * @param {Number} currentIndex The index of the current argument being processed. 78 | */ 79 | var processArgsInteractiveHelper = function(argsMap, argProcessorList, callback, currentIndex) { 80 | if (currentIndex === argProcessorList.processorList.length) 81 | return callback(argsMap); 82 | 83 | var argProcessor = argProcessorList.processorList[currentIndex]; 84 | var initialArgValue = argsMap[argProcessor.argName]; 85 | 86 | // Arg preprocessors determine whether an arg should even be presented as an option. 87 | if (typeof argProcessor.preprocessorFunction === 'undefined') { 88 | // By default, process each argument. 89 | processArgument(initialArgValue, argsMap, argProcessor, function(resultArgValue) { 90 | argsMap[argProcessor.argName] = resultArgValue; 91 | processArgsInteractiveHelper(argsMap, argProcessorList, callback, currentIndex + 1); 92 | }); 93 | } else { 94 | var shouldProcessArgument = argProcessor.preprocessorFunction(argsMap); 95 | if (shouldProcessArgument) { 96 | processArgument(initialArgValue, argsMap, argProcessor, function(resultArgValue) { 97 | argsMap[argProcessor.argName] = resultArgValue; 98 | processArgsInteractiveHelper(argsMap, argProcessorList, callback, currentIndex + 1); 99 | }); 100 | } else { 101 | // If the user specified a value already, warn them that it won't be used. 102 | if (typeof initialArgValue !== 'undefined') { 103 | console.log(outputColors.yellow + 'WARNING: ' + outputColors.reset 104 | + '\'' + argProcessor.argName + '\' is not a valid argument in this scenario, and its value will be ignored.'); 105 | argsMap[argProcessor.argName] = undefined; 106 | } 107 | processArgsInteractiveHelper(argsMap, argProcessorList, callback, currentIndex + 1); 108 | } 109 | } 110 | }; 111 | 112 | /** 113 | * Evaluates an argument value against its argument processor 114 | * @param {String} argValue The specified value of the argument. 115 | * @param argsMap A map of arguments in the form of { 'argName': 'argValue' [, ...] }. 116 | * @param {ArgProcessor} argProcessor The argument processor, used to evaluate the arg value. 117 | * @param {Function} postProcessingCallback The callback to invoke once a valid arg value has been determined. 118 | */ 119 | var processArgument = function(argValue, argsMap, argProcessor, postProcessingCallback) { 120 | // NB: If argValue is undefined (i.e. no initial command line input), even for optional arguments the user must 121 | // be prompted at least once. 122 | var processingResult = null; 123 | if (typeof argValue !== 'undefined') { 124 | processingResult = argProcessor.processorFunction(argValue, argsMap); 125 | if (!(processingResult instanceof ArgProcessorOutput)) { 126 | throw new Error ('Expecting processing result of type ArgProcessorOutput. Got \'' + (typeof processingResult) + '\'.'); 127 | } 128 | if (processingResult.isValid) { 129 | if (typeof processingResult.replacementValue !== 'undefined') { 130 | return postProcessingCallback(processingResult.replacementValue); 131 | } else { 132 | return postProcessingCallback(argValue); 133 | } 134 | } 135 | } 136 | 137 | // Otherwise, arg value was either not present, or not valid. If the user actually entered an invalid value, show the error prompt. 138 | if (processingResult && !processingResult.isValid) { 139 | if (typeof processingResult.message !== 'undefined') { 140 | console.log(outputColors.red + processingResult.message + outputColors.reset); 141 | } else { 142 | console.log(outputColors.red + 'Invalid value for \'' + argProcessor.argName + '\'.' + outputColors.reset); 143 | } 144 | } 145 | // If we have an inputPrompt, prompt the user 146 | if (argProcessor.inputPrompt) { 147 | var rl = readline.createInterface({ 148 | input: process.stdin, 149 | output: process.stdout 150 | }); 151 | rl.question(argProcessor.inputPrompt + ' ', function(answer) { 152 | rl.close(); 153 | processArgument(answer, argsMap, argProcessor, postProcessingCallback); 154 | }); 155 | } 156 | else { 157 | processArgument('', argsMap, argProcessor, postProcessingCallback); 158 | } 159 | }; 160 | 161 | /** 162 | * Creates an instance of an ArgProcessorList. 163 | * 164 | * @constructor 165 | */ 166 | var ArgProcessorList = function() { 167 | this.processorList = []; 168 | }; 169 | 170 | /** 171 | * Adds an ArgProcessor to the list of processors. 172 | * 173 | * @param {String} argName The name of the argument. 174 | * @param {String} inputPrompt The prompt to show the user, when interactively configuring the arg value. If null, the user will not be prompted interactively. 175 | * @param {Function} processorFunction The function used to validate the arg value. 176 | * @param {Function} preprocessorFunction An optional function that can be used to determine whether the argument should be configured. 177 | * @return {ArgProcessorList} The updated ArgProcessorList, for chaining calls. 178 | */ 179 | ArgProcessorList.prototype.addArgProcessor = function(argName, inputPrompt, processorFunction, preprocessorFunction) { 180 | var argProcessor = new ArgProcessor(argName, inputPrompt, processorFunction, preprocessorFunction); 181 | this.processorList.push(argProcessor); 182 | return this; 183 | }; 184 | 185 | /** 186 | * Creates an instance of the ArgProcessor object. 187 | * 188 | * @constructor 189 | * @param {String} argName The name of the argument. 190 | * @param {String} inputPrompt The prompt to show the user, when interactively configuring the arg value. If null, the user will not be prompted interactively. 191 | * @param {Function} processorFunction The function used to validate the arg value. 192 | * @param {Function} preprocessorFunction An optional function that can be used to determine whether the argument should be configured. 193 | */ 194 | var ArgProcessor = function(argName, inputPrompt, processorFunction, preprocessorFunction) { 195 | if ((typeof argName !== 'string') || argName.trim() === '') { 196 | throw new Error('Invalid value for argName: \'' + argName + '\'.'); 197 | } 198 | if (typeof processorFunction !== 'function') { 199 | throw new Error('processorFunction should be a function.'); 200 | } 201 | if ((typeof preprocessorFunction !== 'undefined') && (typeof preprocessorFunction !== 'function')) { 202 | throw new Error('If defined, preprocessorFunction should be a function.'); 203 | } 204 | 205 | this.argName = argName; 206 | this.inputPrompt = inputPrompt; 207 | this.processorFunction = processorFunction; 208 | this.preprocessorFunction = preprocessorFunction; 209 | }; 210 | 211 | /** 212 | * Creates an instance of the ArgProcessorOutput object, used to return the result of processing an arg value. 213 | * 214 | * @constructor 215 | * @param {Boolean} isValid Whether or not the arg value was a valid value. 216 | * @param {String} messageOrReplacementValue Optional value. If arg is not valid, a message explaining the failure. If valid, an optional replacment value for the argument. 217 | */ 218 | var ArgProcessorOutput = function(isValid, messageOrReplacementValue) { 219 | 220 | // If an argument is valid (isValid == true), there won't need to be a message 221 | // assocated with it. And if it's not valid, you wouldn't provide a replacement 222 | // value for it (at least in the workflow as defined—replacement values imply a 223 | // "valid" alternative argument value). Hence the single second argument serving 224 | // two masters. But we'll normalize the value for consumption. NB: In either 225 | // use case, this argument can be optional. 226 | 227 | if (typeof isValid !== 'boolean') { 228 | throw new Error('isValid should be a boolean value.'); 229 | } 230 | this.isValid = isValid; 231 | 232 | if (typeof messageOrReplacementValue !== 'undefined') { 233 | if (this.isValid) 234 | this.replacementValue = messageOrReplacementValue; 235 | else 236 | this.message = messageOrReplacementValue; 237 | } 238 | }; 239 | 240 | // Exports 241 | 242 | module.exports.parseArgs = parseArgs; 243 | module.exports.ArgProcessorList = ArgProcessorList; 244 | module.exports.processArgsInteractive = processArgsInteractive; 245 | module.exports.ArgProcessorOutput = ArgProcessorOutput; 246 | -------------------------------------------------------------------------------- /shared/configHelper.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016-present, salesforce.com, inc. 3 | * All rights reserved. 4 | * Redistribution and use of this software in source and binary forms, with or 5 | * without modification, are permitted provided that the following conditions 6 | * are met: 7 | * - Redistributions of source code must retain the above copyright notice, this 8 | * list of conditions and the following disclaimer. 9 | * - Redistributions in binary form must reproduce the above copyright notice, 10 | * this list of conditions and the following disclaimer in the documentation 11 | * and/or other materials provided with the distribution. 12 | * - Neither the name of salesforce.com, inc. nor the names of its contributors 13 | * may be used to endorse or promote products derived from this software without 14 | * specific prior written permission of salesforce.com, inc. 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | // Dependencies 29 | var path = require('path'), 30 | shelljs = require('shelljs'), 31 | SDK = require('./constants'), 32 | COLOR = require('./outputColors'), 33 | commandLineUtils = require('./commandLineUtils'), 34 | logInfo = require('./utils').logInfo, 35 | getTemplates = require('./templateHelper').getTemplates, 36 | validateJson = require('./jsonChecker').validateJson; 37 | 38 | function applyCli(f, cli) { 39 | return typeof f === 'function' ? f(cli): f; 40 | } 41 | 42 | function getArgsExpanded(cli, commandName) { 43 | var argNames = applyCli(SDK.commands[commandName].args, cli); 44 | return argNames 45 | .map(argName => SDK.args[argName]) 46 | .map(arg => 47 | ({ 48 | name: arg.name, 49 | 'char': arg.char, 50 | description: applyCli(arg.description, cli), 51 | longDescription: applyCli(arg.longDescription, cli), 52 | prompt: applyCli(arg.prompt, cli), 53 | error: applyCli(arg.error, cli), 54 | validate: applyCli(arg.validate, cli), 55 | promptIf: arg.promptIf, 56 | required: arg.required === undefined ? true : arg.required, 57 | hasValue: arg.hasValue === undefined ? true : arg.hasValue, 58 | hidden: applyCli(arg.hidden, cli), 59 | type: arg.type 60 | }) 61 | ); 62 | 63 | } 64 | 65 | function getCommandExpanded(cli, commandName) { 66 | var command = SDK.commands[commandName]; 67 | return { 68 | name: command.name, 69 | args: getArgsExpanded(cli, commandName), 70 | description: applyCli(command.description, cli), 71 | longDescription: applyCli(command.longDescription, cli), 72 | help: applyCli(command.help, cli) 73 | }; 74 | } 75 | 76 | function readConfig(args, cli, handler) { 77 | var commandLineArgs = args.slice(2, args.length); 78 | var commandName = commandLineArgs.shift(); 79 | commandName = commandName ? commandName.toLowerCase() : commandName; 80 | 81 | var processorList = null; 82 | 83 | switch (commandName || '') { 84 | case SDK.commands.version.name: 85 | printVersion(cli); 86 | process.exit(0); 87 | break; 88 | case SDK.commands.create.name: 89 | case SDK.commands.createwithtemplate.name: 90 | processorList = buildArgsProcessorList(cli, commandName); 91 | commandLineUtils.processArgsInteractive(commandLineArgs, processorList, handler); 92 | break; 93 | case SDK.commands.checkconfig.name: 94 | processorList = buildArgsProcessorList(cli, commandName); 95 | commandLineUtils.processArgsInteractive(commandLineArgs, processorList, function (config) { 96 | validateJson(config.configpath, config.configtype); 97 | }); 98 | break; 99 | case SDK.commands.listtemplates.name: 100 | listTemplates(cli); 101 | process.exit(0); 102 | break; 103 | default: 104 | usage(cli); 105 | process.exit(1); 106 | }; 107 | 108 | 109 | } 110 | 111 | function printVersion(cli) { 112 | logInfo(cli.name + ' version ' + SDK.version); 113 | } 114 | 115 | function printArgs(cli, commandName) { 116 | getArgsExpanded(cli, commandName) 117 | .filter(arg => !arg.hidden) 118 | .forEach(arg => logInfo(' ' + (!arg.required ? '[' : '') + '--' + arg.name + '=' + arg.description + (!arg.required ? ']' : ''), COLOR.magenta)); 119 | } 120 | 121 | function listTemplates(cli) { 122 | var cliName = cli.name; 123 | var applicableTemplates = getTemplates(cli); 124 | 125 | logInfo('\nAvailable templates:\n', COLOR.cyan); 126 | for (var i=0; i0) { 144 | logInfo('\n OR \n', COLOR.cyan); 145 | } 146 | var commandName = cli.commands[i]; 147 | var command = getCommandExpanded(cli, commandName); 148 | logInfo('# ' + command.description, COLOR.magenta); 149 | logInfo(cliName + ' ' + commandName, COLOR.magenta); 150 | printArgs(cli, commandName); 151 | } 152 | logInfo('\n OR \n', COLOR.cyan); 153 | logInfo(cliName, COLOR.magenta); 154 | logInfo('\nWe also offer:', COLOR.cyan); 155 | for (var otherCliName in SDK.forceclis) { 156 | var otherCli = SDK.forceclis[otherCliName]; 157 | if (otherCli.name != cli.name) { 158 | logInfo('- ' + otherCli.name + ': Tool for building ' + otherCli.purpose + ' using Salesforce Mobile SDK', COLOR.cyan); 159 | } 160 | } 161 | logInfo('\n'); 162 | } 163 | 164 | // 165 | // Processor list 166 | // 167 | function buildArgsProcessorList(cli, commandName) { 168 | var argProcessorList = new commandLineUtils.ArgProcessorList(); 169 | 170 | for (var arg of getArgsExpanded(cli, commandName)) { 171 | addProcessorFor(argProcessorList, arg.name, arg.prompt, arg.error, arg.validate, arg.promptIf); 172 | } 173 | 174 | return argProcessorList; 175 | } 176 | 177 | // 178 | // Helper function to add arg processor 179 | // * argProcessorList: ArgProcessorList 180 | // * argName: string, name of argument 181 | // * prompt: string for prompt 182 | // * error: function 183 | // * validation: function or null (no validation) 184 | // * preprocessor: function or null 185 | // 186 | function addProcessorFor(argProcessorList, argName, prompt, error, validation, preprocessor) { 187 | argProcessorList.addArgProcessor(argName, prompt, function(val) { 188 | val = val.trim(); 189 | 190 | // validation is either a function or null 191 | if (validation == null || validation(val)) { 192 | return new commandLineUtils.ArgProcessorOutput(true, val); 193 | } 194 | else { 195 | return new commandLineUtils.ArgProcessorOutput(false, error(val)); 196 | } 197 | 198 | }, preprocessor); 199 | } 200 | 201 | module.exports = { 202 | readConfig: readConfig, 203 | printVersion: printVersion, 204 | getArgsExpanded: getArgsExpanded, 205 | getCommandExpanded: getCommandExpanded 206 | }; 207 | -------------------------------------------------------------------------------- /shared/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016-present, salesforce.com, inc. 3 | * All rights reserved. 4 | * Redistribution and use of this software in source and binary forms, with or 5 | * without modification, are permitted provided that the following conditions 6 | * are met: 7 | * - Redistributions of source code must retain the above copyright notice, this 8 | * list of conditions and the following disclaimer. 9 | * - Redistributions in binary form must reproduce the above copyright notice, 10 | * this list of conditions and the following disclaimer in the documentation 11 | * and/or other materials provided with the distribution. 12 | * - Neither the name of salesforce.com, inc. nor the names of its contributors 13 | * may be used to endorse or promote products derived from this software without 14 | * specific prior written permission of salesforce.com, inc. 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | var path = require('path'), 29 | shelljs = require('shelljs'); 30 | 31 | var VERSION= '13.1.0'; 32 | 33 | module.exports = { 34 | version: VERSION, 35 | 36 | tools: { 37 | git: { 38 | checkCmd: 'git --version', 39 | minVersion: '2.13' 40 | }, 41 | node: { 42 | checkCmd: 'node --version', 43 | minVersion: '12.0' 44 | }, 45 | npm: { 46 | checkCmd: 'npm -v', 47 | minVersion: '3.10' 48 | }, 49 | yarn: { 50 | checkCmd: 'yarn -v', 51 | minVersion: '1.22' 52 | }, 53 | tsc: { 54 | checkCmd: 'tsc -v', 55 | minVersion: '4.1.2' 56 | }, 57 | pod: { 58 | checkCmd: 'pod --version', 59 | minVersion: '1.8.0' 60 | }, 61 | cordova: { 62 | checkCmd: 'cordova -v', 63 | pluginRepoUri: 'https://github.com/forcedotcom/SalesforceMobileSDK-CordovaPlugin#dev', // dev 64 | minVersion: '12.0.0', 65 | // pluginRepoUri: 'salesforce-mobilesdk-cordova-plugin@v' + VERSION, // GA 66 | platformVersion: { 67 | ios: '7.1.1', 68 | android: '13.0.0' 69 | } 70 | }, 71 | sf: { 72 | checkCmd: 'sf -v', 73 | minVersion: '2.0.0' 74 | } 75 | }, 76 | 77 | ides: { 78 | ios: 'XCode', 79 | android: 'Android Studio' 80 | }, 81 | 82 | templatesRepoUri: 'https://github.com/forcedotcom/SalesforceMobileSDK-Templates#dev', // dev 83 | // templatesRepoUri: 'https://github.com/forcedotcom/SalesforceMobileSDK-Templates#v' + VERSION, // GA 84 | 85 | forceclis: { 86 | forceios: { 87 | name: 'forceios', 88 | topic: 'ios', 89 | purpose: 'an iOS native mobile application', 90 | dir: 'ios', 91 | platforms: ['ios'], 92 | toolNames: ['git', 'node', 'npm', 'pod'], 93 | appTypes: ['native_swift', 'native'], 94 | appTypesToPath: { 95 | 'native': 'iOSNativeTemplate', 96 | 'native_swift': 'iOSNativeSwiftTemplate' 97 | }, 98 | commands: ['create', 'createwithtemplate', 'version', 'listtemplates', 'checkconfig'] 99 | }, 100 | forcedroid: { 101 | name: 'forcedroid', 102 | topic: 'android', 103 | purpose: 'an Android native mobile application', 104 | dir: 'android', 105 | platforms: ['android'], 106 | toolNames: ['git', 'node', 'npm'], 107 | appTypes: ['native_kotlin', 'native'], 108 | appTypesToPath: { 109 | 'native': 'AndroidNativeTemplate', 110 | 'native_kotlin': 'AndroidNativeKotlinTemplate' 111 | }, 112 | commands: ['create', 'createwithtemplate', 'version', 'listtemplates', 'checkconfig'] 113 | }, 114 | forcehybrid: { 115 | name: 'forcehybrid', 116 | topic: 'hybrid', 117 | purpose: 'a hybrid mobile application', 118 | dir: 'hybrid', 119 | platforms: ['ios', 'android'], 120 | toolNames: ['git', 'node', 'npm', 'cordova', 'sf'], 121 | appTypes: ['hybrid_local', 'hybrid_remote'], 122 | appTypesToPath: { 123 | 'hybrid_local': 'HybridLocalTemplate', 124 | 'hybrid_remote': 'HybridRemoteTemplate' 125 | }, 126 | commands: ['create', 'createwithtemplate', 'version', 'listtemplates', 'checkconfig'] 127 | }, 128 | forcereact: { 129 | name: 'forcereact', 130 | topic: 'reactnative', 131 | purpose: 'a React Native mobile application', 132 | dir: 'react', 133 | platforms: ['ios', 'android'], 134 | toolNames: ['git', 'node', 'yarn', 'tsc', 'pod'], 135 | appTypes: ['react_native_typescript', 'react_native'], 136 | appTypesToPath: { 137 | 'react_native': 'ReactNativeTemplate', 138 | 'react_native_typescript': 'ReactNativeTypeScriptTemplate' 139 | }, 140 | commands: ['create', 'createwithtemplate', 'version', 'listtemplates', 'checkconfig'] 141 | } 142 | }, 143 | 144 | args: { 145 | platform: { 146 | name: 'platform', 147 | 'char': 'p', 148 | description: cli => 'comma-separated list of platforms (' + cli.platforms.join(', ') + ')', 149 | longDescription: cli => 'A comma-separated list of one or more platforms you support. The script creates a project for each platform you select. Available options are ' + cli.platforms.join(', ') + '.', 150 | prompt: cli => 'Enter the target platform(s) separated by commas (' + cli.platforms.join(', ') + '):', 151 | error: cli => val => 'Platform(s) must be in ' + cli.platforms.join(', '), 152 | validate: cli => val => !val.split(",").some(p=>cli.platforms.indexOf(p) == -1), 153 | type: 'string' 154 | }, 155 | appType: { 156 | name:'apptype', 157 | 'char':'t', 158 | description: cli => 'application type (' + cli.appTypes.join(' or ') + ', leave empty for ' + cli.appTypes[0] + ')', 159 | longDescription: cli => 'You can choose one of the following types of applications: ' + cli.appTypes.join(', ') + '.', 160 | prompt: cli => 'Enter your application type (' + cli.appTypes.join(' or ') + ', leave empty for ' + cli.appTypes[0] + '):', 161 | error: cli => val => 'App type must be ' + cli.appTypes.join(' or ') + '.', 162 | validate: cli => val => val === undefined || val === '' || cli.appTypes.indexOf(val) >=0, 163 | required: false, 164 | type: 'string' 165 | }, 166 | templateRepoUri: { 167 | name:'templaterepouri', 168 | 'char': 'r', 169 | description:'template repo URI or Mobile SDK template name', 170 | longDescription: 'The URI of a repository that contains the template application to be used as the basis of your new app or simply the name of a Mobile SDK template.', 171 | prompt: 'Enter URI of repo containing template application or a Mobile SDK template name:', 172 | error: cli => val => 'Invalid value for template repo uri: \'' + val + '\'.', 173 | validate: cli => val => /^\S+$/.test(val), 174 | type: 'string' 175 | }, 176 | appName: { 177 | name: 'appname', 178 | 'char': 'n', 179 | description: 'application name', 180 | longDescription: 'A name for the app that conforms to the naming requirements for the platform.', 181 | prompt: 'Enter your application name:', 182 | error: cli => val => 'Invalid value for application name: \'' + val + '\'.', 183 | validate: cli => val => (cli.platforms.indexOf('ios') != -1 ? /^[^\s-]+$/ : /^\S+$/).test(val), 184 | type: 'string' 185 | }, 186 | packageName: { 187 | name: 'packagename', 188 | 'char': 'k', 189 | description: 'app package identifier (e.g. com.mycompany.myapp)', 190 | longDescription: 'A string in reverse internet domain format that identifies your app\'s package or bundle. For example, "com.mycompany.myapp".', 191 | prompt: 'Enter your package name:', 192 | error: cli => val => '\'' + val + '\' is not a valid package name.', 193 | validate: cli => val => /^[a-z]+[a-z0-9_]*(\.[a-z]+[a-z0-9_-]*)*$/.test(val), 194 | type: 'string' 195 | }, 196 | organization: { 197 | name: 'organization', 198 | 'char': 'o', 199 | description: 'organization name (your company\'s/organization\'s name)', 200 | longDescription: 'The name of your company or organization. This string is user-defined and may contain spaces and punctuation.', 201 | prompt: 'Enter your organization name (Acme, Inc.):', 202 | error: cli => val => 'Invalid value for organization: \'' + val + '\'.', 203 | validate: cli => val => /\S+/.test(val), 204 | type: 'string' 205 | }, 206 | outputDir: { 207 | name:'outputdir', 208 | 'char':'d', 209 | description:'output directory (leave empty for current directory)', 210 | longDescription: 'The local path for your new project. If this path points to an existing directory, that directory must be empty. If you don\'t specify a value, the script creates the app in the current directory.', 211 | prompt: 'Enter output directory for your app (leave empty for the current directory):', 212 | error: cli => val => 'Invalid value for output directory (directory must not already exist): \'' + val + '\'.', 213 | validate: cli => val => val === undefined || val === '' || !shelljs.test('-e', path.resolve(val)), 214 | required:false, 215 | type: 'string' 216 | }, 217 | startPage: { 218 | name:'startpage', 219 | 'char':'s', 220 | description:'app start page (the start page of your remote app; required for hybrid_remote apps only)', 221 | longDescription: 'For hybrid remote apps only, specify the relative server path to your Visualforce start page. This relative path always discards the Salesforce instance and domain name and starts with "apex/".', 222 | prompt: 'Enter the start page for your app:', 223 | error: cli => val => 'Invalid value for start page: \'' + val + '\'.', 224 | validate: cli => val => /\S+/.test(val), 225 | required: false, 226 | promptIf: otherArgs => otherArgs.apptype === 'hybrid_remote', 227 | type: 'string' 228 | }, 229 | configPath: { 230 | name:'configpath', 231 | 'char': 'p', 232 | description:'path to store or syncs config to validate', 233 | longDescription:'Path to the store or syncs config file to validate.', 234 | error: cli => val => 'Config file not found: \'' + val + '\'.', 235 | prompt: 'Enter the path of the store or syncs config to validate:', 236 | validate: cli => val => shelljs.test('-e', path.resolve(val)), 237 | required: true, 238 | type: 'string' 239 | }, 240 | configType: { 241 | name:'configtype', 242 | 'char': 't', 243 | description:'type of config to validate (store or syncs)', 244 | longDescription:'Type of config to validate (store or syncs).', 245 | error: cli => val => 'Invalid config type: \'' + val + '\'.', 246 | prompt: 'Enter the type of the config to validate (store or syncs):', 247 | validate: cli => val => val === 'store' || val === 'syncs', 248 | required: true, 249 | type: 'string' 250 | }, 251 | // Private args 252 | verbose: { 253 | name: 'verbose', 254 | 'char': 'v', 255 | description: 'increase information output', 256 | hasValue: false, 257 | required: false, 258 | type: 'boolean', 259 | hidden: true 260 | }, 261 | pluginRepoUri: { 262 | name: 'pluginrepouri', 263 | description: 'supply a plugin repository uri', 264 | 'char': 'v', 265 | error: cli => val => 'Invalid value for plugin repo uri: \'' + val + '\'.', 266 | validate: cli => val => /.*/.test(val), 267 | required: false, 268 | type: 'string', 269 | hidden: true 270 | }, 271 | sdkDependencies: { 272 | name: 'sdkdependencies', 273 | description: 'override sdk dependencies', 274 | 'char': 'd', 275 | error: cli => val => 'Invalid value for sdk dependencies: \'' + val + '\'.', 276 | validate: cli => val => /.*/.test(val), 277 | required: false, 278 | type: 'string', 279 | hidden: true 280 | }, 281 | }, 282 | 283 | commands: { 284 | create: { 285 | name: 'create', 286 | args: cli => [cli.platforms.length > 1 ? 'platform' : null, 287 | cli.appTypes.length > 1 ? 'appType' : null, 288 | 'appName', 289 | 'packageName', 290 | 'organization', 291 | cli.appTypes.indexOf('hybrid_remote') >=0 ? 'startPage' : null, 292 | 'outputDir', 293 | 'verbose', 294 | cli.name === 'forcehybrid' ? 'pluginRepoUri' : null, 295 | 'sdkDependencies' 296 | ].filter(x=>x!=null), 297 | description: cli => 'create ' + cli.purpose, 298 | longDescription: cli => 'Create ' + cli.purpose + '.', 299 | help: 'This command initiates creation of a new app based on the standard Mobile SDK template.' 300 | }, 301 | createwithtemplate: { 302 | name: 'createwithtemplate', 303 | args: cli => [cli.platforms.length > 1 ? 'platform' : null, 304 | 'templateRepoUri', 305 | 'appName', 306 | 'packageName', 307 | 'organization', 308 | cli.appTypes.indexOf('hybrid_remote') >=0 ? 'startPage' : null, 309 | 'outputDir', 310 | 'verbose', 311 | cli.name === 'forcehybrid' ? 'pluginRepoUri' : null, 312 | 'sdkDependencies' 313 | ].filter(x=>x!=null), 314 | description: cli => 'create ' + cli.purpose + ' from a template', 315 | longDescription: cli => 'Create ' + cli.purpose + ' from a template.', 316 | help: 'This command initiates creation of a new app based on the Mobile SDK template that you specify. The template can be a specialized app for your app type that Mobile SDK provides, or your own custom app that you\'ve configured to use as a template. See https://developer.salesforce.com/docs/atlas.en-us.mobile_sdk.meta/mobile_sdk/ios_new_project_template.htm for information on custom templates.' 317 | }, 318 | version: { 319 | name: 'version', 320 | args: [], 321 | description: 'show version of Mobile SDK', 322 | longDescription: 'Show version of Mobile SDK.', 323 | help: 'This command displays to the console the version of Mobile SDK that the script uses to create apps.' 324 | }, 325 | listtemplates: { 326 | name: 'listtemplates', 327 | args: [], 328 | description: cli => 'list available Mobile SDK templates to create ' + cli.purpose, 329 | longDescription: cli => 'List available Mobile SDK templates to create ' + cli.purpose + '.', 330 | help: 'This command displays the list of available Mobile SDK templates. You can copy repo paths from the output for use with the createwithtemplate command.' 331 | }, 332 | checkconfig: { 333 | name: 'checkconfig', 334 | args: ['configPath', 'configType'], 335 | description: 'validate store or syncs configuration', 336 | longDescription: 'Validate store or syncs configuration against their JSON schema.', 337 | help: 'This command checks whether the given store or syncs configuration is valid according to its JSON schema.' 338 | } 339 | } 340 | }; 341 | -------------------------------------------------------------------------------- /shared/createHelper.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016-present, salesforce.com, inc. 3 | * All rights reserved. 4 | * Redistribution and use of this software in source and binary forms, with or 5 | * without modification, are permitted provided that the following conditions 6 | * are met: 7 | * - Redistributions of source code must retain the above copyright notice, this 8 | * list of conditions and the following disclaimer. 9 | * - Redistributions in binary form must reproduce the above copyright notice, 10 | * this list of conditions and the following disclaimer in the documentation 11 | * and/or other materials provided with the distribution. 12 | * - Neither the name of salesforce.com, inc. nor the names of its contributors 13 | * may be used to endorse or promote products derived from this software without 14 | * specific prior written permission of salesforce.com, inc. 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | // Dependencies 29 | var path = require('path'), 30 | SDK = require('./constants'), 31 | utils = require('./utils'), 32 | configHelper = require('./configHelper'), 33 | prepareTemplate = require('./templateHelper').prepareTemplate, 34 | getSDKTemplateURI = require('./templateHelper').getSDKTemplateURI, 35 | fs = require('fs'); 36 | 37 | // Constant 38 | var SERVER_PROJECT_DIR = 'server'; 39 | 40 | // 41 | // Helper for native application creation 42 | // 43 | function createNativeApp(config) { 44 | 45 | // Copying template to projectDir 46 | utils.copyFile(config.templateLocalPath, config.projectDir); 47 | 48 | // Run prepare function of template 49 | var prepareResult = prepareTemplate(config, config.projectDir); 50 | 51 | // Cleanup 52 | utils.removeFile(path.join(config.projectDir, 'template.js')); 53 | 54 | // Done 55 | return prepareResult; 56 | } 57 | 58 | // 59 | // Helper for hybrid application creation 60 | // 61 | function createHybridApp(config) { 62 | 63 | // Create app with cordova 64 | utils.runProcessThrowError('cordova create "' + config.projectDir + '" ' + config.packagename + ' ' + config.appname); 65 | utils.runProcessThrowError('npm install shelljs@0.8.5', config.projectDir); 66 | 67 | for (var platform of config.platform.split(',')) { 68 | utils.runProcessThrowError('cordova platform add ' + platform + '@' + SDK.tools.cordova.platformVersion[platform], config.projectDir); 69 | } 70 | utils.runProcessThrowError('cordova plugin add ' + config.cordovaPluginRepoUri + ' --force', config.projectDir); 71 | 72 | // Web directory - the home for the template 73 | var webDir = path.join(config.projectDir, 'www'); 74 | 75 | // Remove the default Cordova app. 76 | utils.removeFile(webDir); 77 | 78 | // Copying template to www 79 | utils.copyFile(config.templateLocalPath, webDir); 80 | 81 | // Run prepare function of template 82 | var prepareResult = prepareTemplate(config, webDir); 83 | 84 | // Cleanup 85 | utils.removeFile(path.join(webDir, 'template.js')); 86 | 87 | // If template includes server side files 88 | // Create a fresh sfdx project 89 | // Add cordova js and plugins at static resources 90 | // Merge files from template into it 91 | if (utils.dirExists(path.join(webDir, SERVER_PROJECT_DIR))) { 92 | config.serverDir = path.join(config.projectDir, SERVER_PROJECT_DIR) 93 | utils.runProcessThrowError('sf force project create -n ' + SERVER_PROJECT_DIR, config.projectDir); 94 | 95 | // Copy cordova js to static resources 96 | for (var platform of config.platform.split(',')) { 97 | var cordovaStaticResourcesDir = path.join(config.serverDir, 'force-app', 'main', 'default', 'staticresources', 'cordova' + platform); 98 | utils.mkDirIfNeeded(cordovaStaticResourcesDir); 99 | utils.copyFile(path.join(config.projectDir, 'platforms', platform, 'platform_www', '*'), cordovaStaticResourcesDir); 100 | } 101 | 102 | // Merge server files from templates 103 | utils.mergeFile(path.join(webDir, SERVER_PROJECT_DIR), config.serverDir); 104 | 105 | // Remove server files from www 106 | utils.removeFile(path.join(webDir, SERVER_PROJECT_DIR)); 107 | } 108 | 109 | // Run cordova prepare 110 | utils.runProcessThrowError('cordova prepare', config.projectDir); 111 | 112 | // Done 113 | return prepareResult; 114 | } 115 | 116 | // 117 | // Print details 118 | // 119 | function printDetails(config) { 120 | // Printing out details 121 | var details = ['Creating ' + config.platform.replace(',', ' and ') + ' ' + config.apptype + ' application using Salesforce Mobile SDK', 122 | ' with app name: ' + config.appname, 123 | ' package name: ' + config.packagename, 124 | ' organization: ' + config.organization, 125 | '', 126 | ' in: ' + config.projectPath, 127 | '', 128 | ' from template repo: ' + config.templaterepouri 129 | ]; 130 | 131 | if (config.templatepath) { 132 | details = details.concat([' template path: ' + config.templatepath]); 133 | } 134 | 135 | 136 | if (config.sdkdependencies) { 137 | details = details.concat([' sdk dependencies: ' + config.sdkdependencies]); 138 | } 139 | 140 | // Hybrid extra details 141 | if (config.apptype.indexOf('hybrid') >= 0) { 142 | if (config.apptype === 'hybrid_remote') { 143 | details = details.concat([' start page: ' + config.startpage]); 144 | } 145 | 146 | details = details.concat([' plugin repo: ' + config.cordovaPluginRepoUri]); 147 | } 148 | 149 | utils.logParagraph(details); 150 | } 151 | 152 | // 153 | // Print next steps 154 | // 155 | function printNextSteps(ide, projectPath, result) { 156 | var workspacePath = path.join(projectPath, result.workspacePath); 157 | var bootconfigFile = path.join(projectPath, result.bootconfigFile); 158 | 159 | // Printing out next steps 160 | utils.logParagraph(['Next steps' + (result.platform ? ' for ' + result.platform : '') + ':', 161 | '', 162 | 'Your application project is ready in ' + projectPath + '.', 163 | 'To use your new application in ' + ide + ', do the following:', 164 | ' - open ' + workspacePath + ' in ' + ide, 165 | ' - build and run', 166 | 'Before you ship, make sure to plug your OAuth Client ID and Callback URI,', 167 | 'and OAuth Scopes into ' + bootconfigFile, 168 | ]); 169 | }; 170 | 171 | // 172 | // Print next steps for Native Login 173 | // 174 | function printNextStepsForNativeLogin(ide, projectPath, result) { 175 | var workspacePath = path.join(projectPath, result.workspacePath); 176 | var bootconfigFile = path.join(projectPath, result.bootconfigFile); 177 | var entryFile = (ide === 'XCode') ? 'SceneDelegate' : 'MainApplication'; 178 | 179 | // Printing out next steps 180 | utils.logParagraph(['Next steps' + (result.platform ? ' for ' + result.platform : '') + ':', 181 | '', 182 | 'Your application project is ready in ' + projectPath + '.', 183 | 'To use your new application in ' + ide + ', do the following:', 184 | ' - open ' + workspacePath + ' in ' + ide, 185 | ' - Update the OAuth Client ID, Callback URI, and Community URL in ' + entryFile + ' class.', 186 | ' - build and run', 187 | 'Before you ship, make sure to plug your OAuth Client ID and Callback URI,', 188 | 'and OAuth Scopes into ' + bootconfigFile + ', since it is still used for', 189 | 'authentication if we fallback on the webview.' 190 | ]); 191 | } 192 | 193 | // 194 | // Print next steps for server project if present 195 | // 196 | function printNextStepsForServerProjectIfNeeded(projectPath) { 197 | var serverProjectPath = path.join(projectPath, SERVER_PROJECT_DIR); 198 | var hasServerProject = utils.dirExists(serverProjectPath); 199 | // Extra steps if there is a server project 200 | if (hasServerProject) { 201 | utils.logParagraph(['Your application also has a server project in ' + serverProjectPath + '.', 202 | 'Make sure to deploy it to your org before running your application.', 203 | '', 204 | 'From ' + projectPath + ' do the following to setup a scratch org, push the server code:', 205 | ' - sf force org create -f server/config/project-scratch-def.json -a MyOrg', 206 | ' - cd server', 207 | ' - sf force source push -u MyOrg', 208 | 'You also need a password to login to the scratch org from the mobile app:', 209 | ' - sf force user password generate -u MyOrg' 210 | ]); 211 | } 212 | } 213 | 214 | // 215 | // Check tools 216 | // 217 | function checkTools(toolNames) { 218 | try { 219 | utils.log("Checking tools"); 220 | for (var toolName of toolNames) { 221 | utils.checkToolVersion(SDK.tools[toolName].checkCmd, SDK.tools[toolName].minVersion, SDK.tools[toolName].maxVersion, toolName); 222 | } 223 | } 224 | catch (error) { 225 | utils.logError('Missing tools\n', error); 226 | process.exit(1); 227 | } 228 | } 229 | 230 | // 231 | // Create app - check tools, read config then actually create app 232 | // 233 | function createApp(forcecli, config) { 234 | 235 | // Can't target ios or run pod if not on a mac 236 | if (process.platform != 'darwin') { 237 | forcecli.platforms = forcecli.platforms.filter(p=>p!='ios'); 238 | forcecli.toolNames = forcecli.toolNames.filter(t=>t!='pod'); 239 | 240 | if (forcecli.platforms.length == 0) { 241 | utils.logError('You can only run ' + forcecli.name + ' on a Mac'); 242 | process.exit(1); 243 | } 244 | } 245 | 246 | // Check tools 247 | checkTools(forcecli.toolNames); 248 | 249 | if (config === undefined) { 250 | // Read parameters from command line 251 | configHelper.readConfig(process.argv, forcecli, function(config) { actuallyCreateApp(forcecli, config); }); 252 | } 253 | else { 254 | // Use parameters passed through 255 | actuallyCreateApp(forcecli, config); 256 | } 257 | } 258 | 259 | // 260 | // Override sdk dependencies in package.json 261 | // 262 | function overrideSdkDependencies(packageJsonPath, sdkDependenciesString) { 263 | try { 264 | console.log("packageJsonPath =>" + packageJsonPath); 265 | 266 | // Parse sdkDependencies 267 | let sdkDependencies = JSON.parse(sdkDependenciesString) 268 | 269 | // Read the package.json file 270 | let originalContent = fs.readFileSync(packageJsonPath, 'utf8'); 271 | console.log("original content =>" + originalContent); 272 | let packageJson = JSON.parse(originalContent) 273 | 274 | // Ensure "sdkDependencies" exists in the package.json 275 | if (!packageJson.sdkDependencies) { 276 | packageJson.sdkDependencies = {}; 277 | } 278 | 279 | // Merge the sdkDependencies argument into the packageJson.sdkDependencies 280 | packageJson.sdkDependencies = { 281 | ...packageJson.sdkDependencies, 282 | ...sdkDependencies 283 | }; 284 | 285 | // Write the updated package.json back to file 286 | let updatedContent = JSON.stringify(packageJson, null, 2); 287 | console.log("updated content =>" + updatedContent); 288 | fs.writeFileSync(packageJsonPath, updatedContent, 'utf8'); 289 | 290 | } catch (err) { 291 | console.error(`Failed to override sdk dependencies in package.json: ${err}`); 292 | } 293 | } 294 | 295 | 296 | // 297 | // Actually create app 298 | // 299 | function actuallyCreateApp(forcecli, config) { 300 | try { 301 | // Adding platform 302 | if (forcecli.platforms.length == 1) { 303 | config.platform = forcecli.platforms[0]; 304 | } 305 | 306 | // Adding app type 307 | if (forcecli.appTypes.length == 1 || config.apptype === undefined || config.apptype === '') { 308 | config.apptype = forcecli.appTypes[0]; 309 | } 310 | 311 | // Setting log level 312 | if (config.verbose) { 313 | utils.setLogLevel(utils.LOG_LEVELS.DEBUG); 314 | } 315 | else { 316 | utils.setLogLevel(utils.LOG_LEVELS.INFO); 317 | } 318 | 319 | // Computing projectDir 320 | config.projectDir = config.outputdir ? path.resolve(config.outputdir) : path.join(process.cwd(),config.appname) 321 | config.projectPath = path.relative(process.cwd(), config.projectDir); 322 | 323 | // Adding version 324 | config.version = SDK.version; 325 | 326 | // Figuring out template repo uri and path 327 | if (config.templaterepouri) { 328 | if (!config.templaterepouri.startsWith("https://")) { 329 | // Given a Mobile SDK template name 330 | config.templatepath = config.templaterepouri; 331 | config.templaterepouri = SDK.templatesRepoUri; 332 | } else { 333 | // Given a full URI 334 | var templateUriParsed = utils.separateRepoUrlPathBranch(config.templaterepouri); 335 | config.templaterepouri = templateUriParsed.repo + '#' + templateUriParsed.branch; 336 | config.templatepath = templateUriParsed.path; 337 | } 338 | } 339 | else { 340 | config.templaterepouri = SDK.templatesRepoUri; 341 | config.templatepath = forcecli.appTypesToPath[config.apptype]; 342 | } 343 | 344 | // Creating tmp dir for template clone 345 | var tmpDir = utils.mkTmpDir(); 346 | 347 | // Cloning template repo 348 | var repoDir = utils.cloneRepo(tmpDir, config.templaterepouri); 349 | config.templateLocalPath = path.join(repoDir, config.templatepath); 350 | 351 | // Override sdk dependencies in package.json if any were provided 352 | if (config.sdkdependencies) { 353 | overrideSdkDependencies(path.join(config.templateLocalPath, 'package.json'), config.sdkdependencies); 354 | } 355 | 356 | // Getting apptype from template 357 | config.apptype = require(path.join(config.templateLocalPath, 'template.js')).appType; 358 | 359 | var isNative = config.apptype.indexOf('native') >= 0; 360 | 361 | // Adding hybrid only config 362 | if (!isNative) { 363 | config.cordovaPluginRepoUri = config.pluginrepouri || SDK.tools.cordova.pluginRepoUri; 364 | } 365 | 366 | // Print details 367 | printDetails(config); 368 | 369 | // Creating application 370 | var results = isNative ? createNativeApp(config) : createHybridApp(config); 371 | 372 | // Cleanup 373 | utils.removeFile(tmpDir); 374 | 375 | // Printing next steps 376 | if (!(results instanceof Array)) { results = [results] }; 377 | for (var result of results) { 378 | var ide = SDK.ides[result.platform || config.platform.split(',')[0]]; 379 | 380 | if (config.templatepath != undefined && config.templatepath.includes('NativeLogin')) { 381 | printNextStepsForNativeLogin(ide, config.projectPath, result); 382 | } else { 383 | printNextSteps(ide, config.projectPath, result); 384 | } 385 | } 386 | printNextStepsForServerProjectIfNeeded(config.projectPath); 387 | 388 | } 389 | catch (error) { 390 | utils.logError(forcecli.name + ' failed\n', error); 391 | process.exit(1); 392 | } 393 | } 394 | 395 | module.exports = { 396 | createApp 397 | }; 398 | -------------------------------------------------------------------------------- /shared/example.userstore.json: -------------------------------------------------------------------------------- 1 | { 2 | "soups": [ 3 | { 4 | "soupName": "userSoup1", 5 | "indexes": [ 6 | { "path": "stringField1", "type": "string"}, 7 | { "path": "integerField1", "type": "integer"}, 8 | { "path": "floatingField1", "type": "floating"}, 9 | { "path": "json1Field1", "type": "json1"}, 10 | { "path": "ftsField1", "type": "full_text"} 11 | ] 12 | }, 13 | { 14 | "soupName": "userSoup2", 15 | "indexes": [ 16 | { "path": "stringField2", "type": "string"}, 17 | { "path": "integerField2", "type": "integer"}, 18 | { "path": "floatingField2", "type": "floating"}, 19 | { "path": "json1Field2", "type": "json1"}, 20 | { "path": "ftsField2", "type": "full_text"} 21 | ] 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /shared/example.usersyncs.json: -------------------------------------------------------------------------------- 1 | { 2 | "syncs": [ 3 | { 4 | "syncName": "soqlSyncDown", 5 | "syncType": "syncDown", 6 | "soupName": "accounts", 7 | "target": { 8 | "type":"soql", 9 | "query":"SELECT Id, Name, LastModifiedDate FROM Account" 10 | }, 11 | "options": { 12 | "mergeMode":"OVERWRITE" 13 | } 14 | }, 15 | { 16 | "syncName": "soqlSyncDownWithBatchSize", 17 | "syncType": "syncDown", 18 | "soupName": "accounts", 19 | "target": { 20 | "type":"soql", 21 | "query":"SELECT Id, Name, LastModifiedDate FROM Account", 22 | "maxBatchSize": 200 23 | }, 24 | "options": { 25 | "mergeMode":"OVERWRITE" 26 | } 27 | }, 28 | { 29 | "syncName": "soslSyncDown", 30 | "syncType": "syncDown", 31 | "soupName": "accounts", 32 | "target": { 33 | "type":"sosl", 34 | "query":"FIND {Joe} IN NAME FIELDS RETURNING Account" 35 | }, 36 | "options": { 37 | "mergeMode":"LEAVE_IF_CHANGED" 38 | } 39 | }, 40 | { 41 | "syncName": "mruSyncDown", 42 | "syncType": "syncDown", 43 | "soupName": "accounts", 44 | "target": { 45 | "type":"mru", 46 | "sobjectType": "Account", 47 | "fieldlist": ["Name", "Description"] 48 | }, 49 | "options": { 50 | "mergeMode":"OVERWRITE" 51 | } 52 | }, 53 | { 54 | "syncName": "refreshSyncDown", 55 | "syncType": "syncDown", 56 | "soupName": "accounts", 57 | "target": { 58 | "type":"refresh", 59 | "sobjectType": "Account", 60 | "fieldlist": ["Name", "Description"], 61 | "soupName": "accounts" 62 | }, 63 | "options": { 64 | "mergeMode":"LEAVE_IF_CHANGED" 65 | } 66 | }, 67 | { 68 | "syncName": "layoutSyncDown", 69 | "syncType": "syncDown", 70 | "soupName": "accounts", 71 | "target": { 72 | "type":"layout", 73 | "sobjectType": "Account", 74 | "formFactor": "Medium", 75 | "layoutType": "Compact", 76 | "mode": "Edit" 77 | }, 78 | "options": { 79 | "mergeMode":"OVERWRITE" 80 | } 81 | }, 82 | { 83 | "syncName": "metadataSyncDown", 84 | "syncType": "syncDown", 85 | "soupName": "accounts", 86 | "target": { 87 | "type":"metadata", 88 | "sobjectType": "Account" 89 | }, 90 | "options": { 91 | "mergeMode":"LEAVE_IF_CHANGED" 92 | } 93 | }, 94 | { 95 | "syncName": "parentChildrenSyncDown", 96 | "syncType": "syncDown", 97 | "soupName": "accounts", 98 | "target": { 99 | "parent" : { 100 | "idFieldName" : "IdX", 101 | "sobjectType" : "Account", 102 | "modificationDateFieldName" : "LastModifiedDateX", 103 | "soupName" : "accounts" 104 | }, 105 | "parentFieldlist" : ["IdX", "Name", "Description"], 106 | "children" : { 107 | "parentIdFieldName" : "AccountId", 108 | "idFieldName" : "IdY", 109 | "sobjectType" : "Contact", 110 | "modificationDateFieldName" : "LastModifiedDateY", 111 | "soupName" : "contacts", 112 | "sobjectTypePlural" : "Contacts" 113 | }, 114 | "childrenFieldlist" : [ 115 | "LastName", 116 | "AccountId" 117 | ], 118 | "relationshipType" : "MASTER_DETAIL", 119 | "parentSoqlFilter" : "NameX like 'James%'", 120 | "type" : "parent_children" 121 | }, 122 | "options": { 123 | "mergeMode": "OVERWRITE" 124 | } 125 | }, 126 | { 127 | "syncName": "briefcaseSyncDown", 128 | "syncType": "syncDown", 129 | "soupName": "does-not-matter", 130 | "target": { 131 | "infos": [ 132 | { 133 | "sobjectType": "Account", 134 | "fieldlist": ["Name", "Description"], 135 | "soupName": "accounts" 136 | }, 137 | { 138 | "sobjectType": "Contact", 139 | "fieldlist": ["FirstName"], 140 | "idFieldName" : "IdX", 141 | "modificationDateFieldName": "LastModifiedDateX", 142 | "soupName": "contacts" 143 | } 144 | ], 145 | "type" : "briefcase" 146 | }, 147 | "options": { 148 | "mergeMode": "OVERWRITE" 149 | } 150 | }, 151 | { 152 | "syncName": "singleRecordSyncUp", 153 | "syncType": "syncUp", 154 | "soupName": "accounts", 155 | "target": { 156 | "iOSImpl": "SFSyncUpTarget", 157 | "androidImpl": "com.salesforce.androidsdk.mobilesync.target.SyncUpTarget", 158 | "createFieldlist": ["Name"], 159 | "updateFieldlist": ["Description"] 160 | }, 161 | "options": { 162 | "fieldlist": [], 163 | "mergeMode":"LEAVE_IF_CHANGED" 164 | } 165 | }, 166 | { 167 | "syncName": "batchSyncUp", 168 | "syncType": "syncUp", 169 | "soupName": "accounts", 170 | "target": { 171 | "iOSImpl": "SFBatchSyncUpTarget", 172 | "androidImpl": "com.salesforce.androidsdk.mobilesync.target.BatchSyncUpTarget", 173 | "idFieldName": "IdX", 174 | "modificationDateFieldName": "LastModifiedDateX", 175 | "externalIdFieldName": "ExternalIdX" 176 | }, 177 | "options": { 178 | "fieldlist": ["Name", "Description"], 179 | "mergeMode":"OVERWRITE" 180 | } 181 | }, 182 | { 183 | "syncName": "collectionSyncUp", 184 | "syncType": "syncUp", 185 | "soupName": "accounts", 186 | "target": { 187 | "idFieldName": "IdX", 188 | "modificationDateFieldName": "LastModifiedDateX", 189 | "externalIdFieldName": "ExternalIdX" 190 | }, 191 | "options": { 192 | "fieldlist": ["Name", "Description"], 193 | "mergeMode":"OVERWRITE" 194 | } 195 | }, 196 | { 197 | "syncName": "parentChildrenSyncUp", 198 | "syncType": "syncUp", 199 | "soupName": "accounts", 200 | "target": { 201 | "iOSImpl" : "SFParentChildrenSyncUpTarget", 202 | "androidImpl": "com.salesforce.androidsdk.mobilesync.target.ParentChildrenSyncUpTarget", 203 | "parent" : { 204 | "idFieldName" : "IdX", 205 | "externalIdFieldName": "ExternalIdX", 206 | "sobjectType" : "Account", 207 | "modificationDateFieldName" : "LastModifiedDateX", 208 | "soupName" : "accounts" 209 | }, 210 | "createFieldlist" : ["IdX", "Name", "Description"], 211 | "updateFieldlist" : ["Name", "Description"], 212 | "children" : { 213 | "parentIdFieldName" : "AccountId", 214 | "idFieldName" : "IdY", 215 | "externalIdFieldName": "ExternalIdY", 216 | "sobjectType" : "Contact", 217 | "modificationDateFieldName" : "LastModifiedDateY", 218 | "soupName" : "contacts", 219 | "sobjectTypePlural" : "Contacts" 220 | }, 221 | "childrenCreateFieldlist" : ["LastName", "AccountId"], 222 | "childrenUpdateFieldlist" : ["FirstName", "AccountId"], 223 | "relationshipType" : "MASTER_DETAIL" 224 | }, 225 | "options": { 226 | "fieldlist":[], 227 | "mergeMode":"LEAVE_IF_CHANGED" 228 | } 229 | } 230 | ] 231 | } 232 | -------------------------------------------------------------------------------- /shared/jsonChecker.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019-present, salesforce.com, inc. 3 | * All rights reserved. 4 | * Redistribution and use of this software in source and binary forms, with or 5 | * without modification, are permitted provided that the following conditions 6 | * are met: 7 | * - Redistributions of source code must retain the above copyright notice, this 8 | * list of conditions and the following disclaimer. 9 | * - Redistributions in binary form must reproduce the above copyright notice, 10 | * this list of conditions and the following disclaimer in the documentation 11 | * and/or other materials provided with the distribution. 12 | * - Neither the name of salesforce.com, inc. nor the names of its contributors 13 | * may be used to endorse or promote products derived from this software without 14 | * specific prior written permission of salesforce.com, inc. 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | // Dependencies 29 | var fs = require('fs'), 30 | path = require('path'), 31 | COLOR = require('./outputColors'), 32 | utils = require('./utils'), 33 | Ajv = require('ajv'), 34 | jsonlint = require('jsonlint') 35 | ; 36 | 37 | // Config type to schema map 38 | var SCHEMA = { 39 | store: path.resolve(__dirname, 'store.schema.json'), 40 | syncs: path.resolve(__dirname, 'syncs.schema.json') 41 | }; 42 | 43 | 44 | // 45 | // Validate config against schema 46 | // 47 | function validateJson(configPath, configType) { 48 | var config = readJsonFile(configPath) 49 | var schema = readJsonFile(SCHEMA[configType]) 50 | var ajv = new Ajv({allErrors: true}); 51 | var valid = ajv.validate(schema, config); 52 | if (!valid) { 53 | utils.logError(JSON.stringify(ajv.errors, null, " ")) 54 | } else { 55 | utils.logInfo(`${configPath} conforms to ${configType} schema\n`, COLOR.green) 56 | } 57 | } 58 | 59 | // 60 | // Read json from file and validates that is valid json 61 | // 62 | function readJsonFile(filePath) { 63 | try { 64 | var content = fs.readFileSync(filePath, "UTF-8"); 65 | var json = jsonlint.parse(content); 66 | return json; 67 | } catch (error) { 68 | utils.logError(`Error parsing ${filePath}: ${error}\n`); 69 | process.exit(1); 70 | } 71 | } 72 | 73 | module.exports = { 74 | validateJson 75 | }; 76 | -------------------------------------------------------------------------------- /shared/oclifAdapter.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019-present, salesforce.com, inc. 3 | * All rights reserved. 4 | * Redistribution and use of this software in source and binary forms, with or 5 | * without modification, are permitted provided that the following conditions 6 | * are met: 7 | * - Redistributions of source code must retain the above copyright notice, this 8 | * list of conditions and the following disclaimer. 9 | * - Redistributions in binary form must reproduce the above copyright notice, 10 | * this list of conditions and the following disclaimer in the documentation 11 | * and/or other materials provided with the distribution. 12 | * - Neither the name of salesforce.com, inc. nor the names of its contributors 13 | * may be used to endorse or promote products derived from this software without 14 | * specific prior written permission of salesforce.com, inc. 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | const COLOR = require('./outputColors'); 28 | 29 | const SDK = require('./constants'); 30 | const configHelper = require('./configHelper'); 31 | const createHelper = require('./createHelper'); 32 | const templateHelper = require('./templateHelper'); 33 | const jsonChecker = require('./jsonChecker'); 34 | const logInfo = require('./utils').logInfo; 35 | const logError = require('./utils').logError; 36 | const os = require('os'); 37 | 38 | const { SfdxError } = require('@salesforce/core'); 39 | const { Command, flags } = require('@oclif/command'); 40 | 41 | const namespace = 'mobilesdk'; 42 | 43 | class OclifAdapter extends Command { 44 | 45 | static formatDescription(description, help) { 46 | return `${description}${os.EOL}${os.EOL}${help}`; 47 | } 48 | 49 | static listTemplates(cli) { 50 | const applicableTemplates = templateHelper.getTemplates(cli); 51 | 52 | logInfo('\nAvailable templates:\n', COLOR.cyan); 53 | for (let i=0; i { 105 | const { name, char, hidden, required, longDescription, type, values, array } = flag; 106 | const description = flag.description || ''; 107 | const config = { 108 | description, 109 | longDescription, 110 | hidden, 111 | required, 112 | default: flag.default 113 | }; 114 | if (char) { 115 | // oclif types char as a single alpha char, but Flag specifies it as `string`, 116 | // so we use `any` here to get tsc to accept the assignment 117 | config.char = char; 118 | } 119 | if (values) { 120 | config.options = values; 121 | } 122 | delete flagContent.hasValue; 123 | if (type === 'boolean') { 124 | flagsConfig[name] = flags.boolean(config); 125 | } 126 | else if (array) { 127 | flagsConfig[name] = flags.array(config); 128 | } 129 | else if (type === 'string') { 130 | flagsConfig[name] = flags.string(config); 131 | } 132 | else { 133 | // TODO 134 | throw new Error('oh noes! ' + JSON.stringify(flag)); 135 | } 136 | }); 137 | } 138 | return flagsConfig; 139 | } 140 | 141 | execute(cli, klass) { 142 | const { flags } = this.parse(klass); 143 | if (OclifAdapter.validateCommand(cli, klass.command.name, flags)) { 144 | return OclifAdapter.runCommand(cli, klass.command.name, flags); 145 | } 146 | } 147 | 148 | resolveHerokuContext() { 149 | this.stringifyFlags(); 150 | return { 151 | flags: this.flags 152 | } 153 | } 154 | 155 | /** 156 | * Call to stringify parsed flags for backward compatibility. 157 | */ 158 | stringifyFlags() { 159 | Object.keys(this.flags).forEach(name => { 160 | const flag = this.flags[name]; 161 | if (flag == null) { 162 | return; 163 | } 164 | const typeOfFlag = typeof this.flags[name]; 165 | switch (typeOfFlag) { 166 | case 'string': 167 | case 'number': 168 | this.flags[name] = flag + ''; 169 | break; 170 | case 'boolean': 171 | break; 172 | case 'object': 173 | if (Array.isArray(flag)) { 174 | this.flags[name] = flag.join(','); 175 | break; 176 | } else if (flag instanceof Date) { 177 | this.flags[name] = flag.toISOString(); 178 | break; 179 | } else if (flag instanceof Duration) { 180 | this.flags[name] = flag.quantity + ''; 181 | break; 182 | } else { 183 | throw new SfdxError(`Unexpected value type for flag ${name}`, 'UnexpectedFlagValueType'); 184 | } 185 | default: 186 | throw new SfdxError(`Unexpected value type for flag ${name}`, 'UnexpectedFlagValueType'); 187 | } 188 | }); 189 | } 190 | } 191 | 192 | OclifAdapter.getCommand = function(cli, commandName) { 193 | if (!this._command) { 194 | this._command = configHelper.getCommandExpanded(cli, commandName); 195 | } 196 | return this._command; 197 | }; 198 | 199 | module.exports = OclifAdapter; 200 | -------------------------------------------------------------------------------- /shared/outputColors.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'red': '\x1b[31;1m', 3 | 'green': '\x1b[32;1m', 4 | 'yellow': '\x1b[33;1m', 5 | 'blue': '\x1b[34;1m', 6 | 'magenta': '\x1b[35;1m', 7 | 'cyan': '\x1b[36;1m', 8 | 'reset': '\x1b[0m' 9 | }; 10 | -------------------------------------------------------------------------------- /shared/store.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "title" : "Store configuration schema", 3 | "description" : "Use this schema to validate userstore.json or globalstore.json", 4 | "definitions": { 5 | "index": { 6 | "type": "object", 7 | "properties": { 8 | "path": { 9 | "type": "string" 10 | }, 11 | "type": { 12 | "type": "string", 13 | "enum": ["string", "integer", "floating", "json1", "full_text"] 14 | } 15 | }, 16 | "required": ["path", "type"] 17 | }, 18 | "soup": { 19 | "type": "object", 20 | "properties": { 21 | "soupName": { "type": "string"}, 22 | "indexes": { 23 | "type": "array", 24 | "items": { "$ref": "#/definitions/index" } 25 | } 26 | }, 27 | "required": ["soupName", "indexes"] 28 | } 29 | }, 30 | "type": "object", 31 | "properties": { 32 | "soups": { 33 | "type": "array", 34 | "items": { "$ref": "#/definitions/soup" } 35 | } 36 | }, 37 | "required": ["soups"] 38 | } 39 | -------------------------------------------------------------------------------- /shared/syncs.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "title" : "Syncs configuration schema", 3 | "description" : "Use this schema to validate usersyncs.json or globalsyncs.json", 4 | "definitions": { 5 | "mergeMode": { 6 | "type": "string", 7 | "enum": ["LEAVE_IF_CHANGED", "OVERWRITE"] 8 | }, 9 | "fieldlist": { 10 | "type": "array", 11 | "items": { "type": "string"} 12 | }, 13 | "query": { 14 | "type": "string" 15 | }, 16 | "sobjectType": { 17 | "type": "string" 18 | }, 19 | "sobjectTypePlural": { 20 | "type": "string" 21 | }, 22 | "formFactor": { 23 | "type": "string", 24 | "enum": ["Large", "Medium", "Small"] 25 | }, 26 | "layoutType": { 27 | "type": "string", 28 | "enum": ["Compact", "Full"] 29 | }, 30 | "mode": { 31 | "type": "string", 32 | "enum": ["Create", "Edit", "View"] 33 | }, 34 | "recordTypeId": { 35 | "type": "string" 36 | }, 37 | "syncName": { 38 | "type": "string" 39 | }, 40 | "soupName": { 41 | "type": "string" 42 | }, 43 | "fieldName": { 44 | "type": "string" 45 | }, 46 | "impl": { 47 | "type": "string" 48 | }, 49 | "soqlFilter": { 50 | "type": "string" 51 | }, 52 | "maxBatchSize": { 53 | "type": "integer" 54 | }, 55 | "relationshipType": { 56 | "type": "string", 57 | "enum": ["MASTER_DETAIL", "LOOKUP"] 58 | }, 59 | "parentInfo": { 60 | "type": "object", 61 | "properties": { 62 | "idFieldName": { "$ref": "#/definitions/fieldName" }, 63 | "externalIdFieldName": { "$ref": "#/definitions/fieldName" }, 64 | "modificationDateFieldName": { "$ref": "#/definitions/fieldName" }, 65 | "sobjectType": { "$ref": "#/definitions/sobjectType" }, 66 | "soupName": { "$ref": "#/definitions/soupName" } 67 | }, 68 | "required": ["sobjectType", "soupName"] 69 | }, 70 | "childrenInfo": { 71 | "type": "object", 72 | "properties": { 73 | "idFieldName": { "$ref": "#/definitions/fieldName" }, 74 | "externalIdFieldName": { "$ref": "#/definitions/fieldName" }, 75 | "modificationDateFieldName": { "$ref": "#/definitions/fieldName" }, 76 | "sobjectType": { "$ref": "#/definitions/sobjectType" }, 77 | "soupName": { "$ref": "#/definitions/soupName" }, 78 | "sobjectTypePlural": { "$ref": "#/definitions/sobjectTypePlural" }, 79 | "parentIdFieldName": { "$ref": "#/definitions/fieldName" } 80 | }, 81 | "required": ["sobjectType", "soupName", "sobjectTypePlural", "parentIdFieldName"] 82 | }, 83 | "briefcaseInfo": { 84 | "type": "object", 85 | "properties": { 86 | "sobjectType": { "$ref": "#/definitions/sobjectType" }, 87 | "fieldlist": { "$ref": "#/definitions/fieldlist" }, 88 | "soupName": { "$ref": "#/definitions/soupName" }, 89 | "idFieldName": { "$ref": "#/definitions/fieldName" }, 90 | "modificationDateFieldName": { "$ref": "#/definitions/fieldName" } 91 | }, 92 | "required": ["sobjectType", "fieldlist", "soupName"] 93 | }, 94 | "briefcaseInfos": { 95 | "type": "array", 96 | "items": { "$ref": "#/definitions/briefcaseInfo" }, 97 | "minItems": 1 98 | }, 99 | "syncDownOptions": { 100 | "type": "object", 101 | "properties": { 102 | "mergeMode": { "$ref": "#/definitions/mergeMode" } 103 | }, 104 | "required": ["mergeMode"] 105 | }, 106 | "syncUpOptions": { 107 | "type": "object", 108 | "properties": { 109 | "mergeMode": { "$ref": "#/definitions/mergeMode" }, 110 | "fieldlist": { "$ref": "#/definitions/fieldlist" } 111 | }, 112 | "required": ["mergeMode", "fieldlist"] 113 | }, 114 | "soqlSyncDownTarget": { 115 | "type": "object", 116 | "properties": { 117 | "type": { "const": "soql" }, 118 | "query": { "$ref": "#/definitions/query" }, 119 | "idFieldName": { "$ref": "#/definitions/fieldName" }, 120 | "modificationDateFieldName": { "$ref": "#/definitions/fieldName" }, 121 | "maxBatchSize": { "$ref": "#/definitions/maxBatchSize" } 122 | }, 123 | "required": ["type", "query"] 124 | }, 125 | "soslSyncDownTarget": { 126 | "type": "object", 127 | "properties": { 128 | "type": { "const": "sosl" }, 129 | "query": { "$ref": "#/definitions/query" }, 130 | "idFieldName": { "$ref": "#/definitions/fieldName" }, 131 | "modificationDateFieldName": { "$ref": "#/definitions/fieldName" } 132 | }, 133 | "required": ["type", "query"] 134 | }, 135 | "mruSyncDownTarget": { 136 | "type": "object", 137 | "properties": { 138 | "type": { "const": "mru" }, 139 | "sobjectType": { "$ref": "#/definitions/sobjectType" }, 140 | "fieldlist": { "$ref": "#/definitions/fieldlist" }, 141 | "idFieldName": { "$ref": "#/definitions/fieldName" }, 142 | "modificationDateFieldName": { "$ref": "#/definitions/fieldName" } 143 | }, 144 | "required": ["type", "sobjectType", "fieldlist"] 145 | }, 146 | "refreshSyncDownTarget": { 147 | "type": "object", 148 | "properties": { 149 | "type": { "const": "refresh" }, 150 | "sobjectType": { "$ref": "#/definitions/sobjectType" }, 151 | "fieldlist": { "$ref": "#/definitions/fieldlist" }, 152 | "soupName": { "$ref": "#/definitions/soupName" }, 153 | "idFieldName": { "$ref": "#/definitions/fieldName" }, 154 | "modificationDateFieldName": { "$ref": "#/definitions/fieldName" } 155 | }, 156 | "required": ["type", "sobjectType", "fieldlist", "soupName"] 157 | }, 158 | "layoutSyncDownTarget": { 159 | "type": "object", 160 | "properties": { 161 | "type": { "const": "layout" }, 162 | "sobjectType": { "$ref": "#/definitions/sobjectType" }, 163 | "formFactor": { "$ref": "#/definitions/formFactor" }, 164 | "layoutType": { "$ref": "#/definitions/layoutType" }, 165 | "mode": { "$ref": "#/definitions/mode" }, 166 | "recordTypeId": { "$ref": "#/definitions/recordTypeId" }, 167 | "idFieldName": { "$ref": "#/definitions/fieldName" }, 168 | "modificationDateFieldName": { "$ref": "#/definitions/fieldName" } 169 | }, 170 | "required": ["type", "sobjectType", "layoutType"] 171 | }, 172 | "metadataSyncDownTarget": { 173 | "type": "object", 174 | "properties": { 175 | "type": { "const": "metadata" }, 176 | "sobjectType": { "$ref": "#/definitions/sobjectType" }, 177 | "idFieldName": { "$ref": "#/definitions/fieldName" }, 178 | "modificationDateFieldName": { "$ref": "#/definitions/fieldName" } 179 | }, 180 | "required": ["type", "sobjectType"] 181 | }, 182 | "parentChildrenSyncDownTarget": { 183 | "type": "object", 184 | "properties": { 185 | "type": { "const": "parent_children" }, 186 | "parent": { "$ref": "#/definitions/parentInfo" }, 187 | "parentFieldlist": { "$ref": "#/definitions/fieldlist" }, 188 | "children": { "$ref": "#/definitions/childrenInfo" }, 189 | "childrenFieldlist": { "$ref": "#/definitions/fieldlist" }, 190 | "relationshipType": { "$ref": "#/definitions/relationshipType" }, 191 | "parentSoqlFilter": { "$ref": "#/definitions/soqlFilter" } 192 | }, 193 | "required": ["type", "parent", "parentFieldlist", "children", "childrenFieldlist", "relationshipType"] 194 | }, 195 | "briefcaseSyncDownTarget": { 196 | "type": "object", 197 | "properties": { 198 | "type": { "const": "briefcase" }, 199 | "infos": { "$ref": "#/definitions/briefcaseInfos"} 200 | }, 201 | "required": ["type", "infos"] 202 | }, 203 | "customSyncDownTarget": { 204 | "type": "object", 205 | "properties": { 206 | "type": { "const": "custom" }, 207 | "iOSImpl" : { "$ref": "#/definitions/impl" }, 208 | "androidImpl" : { "$ref": "#/definitions/impl" }, 209 | "idFieldName": { "$ref": "#/definitions/fieldName" }, 210 | "modificationDateFieldName": { "$ref": "#/definitions/fieldName" } 211 | }, 212 | "required": ["type", "iOSImpl", "androidImpl"] 213 | }, 214 | "syncDownTarget": { 215 | "anyOf": [ 216 | { "$ref": "#/definitions/soqlSyncDownTarget" }, 217 | { "$ref": "#/definitions/soslSyncDownTarget" }, 218 | { "$ref": "#/definitions/mruSyncDownTarget" }, 219 | { "$ref": "#/definitions/refreshSyncDownTarget" }, 220 | { "$ref": "#/definitions/layoutSyncDownTarget" }, 221 | { "$ref": "#/definitions/metadataSyncDownTarget" }, 222 | { "$ref": "#/definitions/parentChildrenSyncDownTarget" }, 223 | { "$ref": "#/definitions/briefcaseSyncDownTarget" }, 224 | { "$ref": "#/definitions/customSyncDownTarget" } 225 | ] 226 | }, 227 | "singleRecordSyncUpTarget": { 228 | "type": "object", 229 | "properties": { 230 | "iOSImpl" : { "const": "SFSyncUpTarget" }, 231 | "androidImpl": { "const": "com.salesforce.androidsdk.mobilesync.target.SyncUpTarget" }, 232 | "createFieldlist": { "$ref": "#/definitions/fieldlist" }, 233 | "updateFieldlist": { "$ref": "#/definitions/fieldlist" }, 234 | "externalIdFieldName": { "$ref": "#/definitions/fieldName" } 235 | }, 236 | "required": ["iOSImpl", "androidImpl"] 237 | }, 238 | "batchSyncUpTarget": { 239 | "type": "object", 240 | "properties": { 241 | "iOSImpl" : { "const": "SFBatchSyncUpTarget" }, 242 | "androidImpl": { "const": "com.salesforce.androidsdk.mobilesync.target.BatchSyncUpTarget" }, 243 | "createFieldlist": { "$ref": "#/definitions/fieldlist" }, 244 | "updateFieldlist": { "$ref": "#/definitions/fieldlist" }, 245 | "externalIdFieldName": { "$ref": "#/definitions/fieldName" } 246 | }, 247 | "required": ["iOSImpl", "androidImpl"] 248 | }, 249 | "collectionSyncUpTarget": { 250 | "type": "object", 251 | "properties": { 252 | "createFieldlist": { "$ref": "#/definitions/fieldlist" }, 253 | "updateFieldlist": { "$ref": "#/definitions/fieldlist" }, 254 | "externalIdFieldName": { "$ref": "#/definitions/fieldName" } 255 | } 256 | }, 257 | "parentChildrenSyncUpTarget": { 258 | "type": "object", 259 | "properties": { 260 | "iOSImpl" : { "const": "SFParentChildrenSyncUpTarget" }, 261 | "androidImpl": { "const": "com.salesforce.androidsdk.mobilesync.target.ParentChildrenSyncUpTarget" }, 262 | "parent": { "$ref": "#/definitions/parentInfo" }, 263 | "createFieldlist": { "$ref": "#/definitions/fieldlist" }, 264 | "updateFieldlist": { "$ref": "#/definitions/fieldlist" }, 265 | "children": { "$ref": "#/definitions/childrenInfo" }, 266 | "childrenCreateFieldlist": { "$ref": "#/definitions/fieldlist" }, 267 | "childrenUpdateFieldlist": { "$ref": "#/definitions/fieldlist" }, 268 | "relationshipType": { "$ref": "#/definitions/relationshipType" } 269 | }, 270 | "required": ["iOSImpl", "androidImpl", 271 | "parent", "createFieldlist", "updateFieldlist", 272 | "children", "childrenCreateFieldlist", "childrenUpdateFieldlist", 273 | "relationshipType"] 274 | }, 275 | "customSyncUpTarget": { 276 | "type": "object", 277 | "properties": { 278 | "iOSImpl" : { "$ref": "#/definitions/impl" }, 279 | "androidImpl" : { "$ref": "#/definitions/impl" }, 280 | "createFieldlist": { "$ref": "#/definitions/fieldlist" }, 281 | "updateFieldlist": { "$ref": "#/definitions/fieldlist" } 282 | }, 283 | "required": ["iOSImpl", "androidImpl"] 284 | }, 285 | "syncUpTarget": { 286 | "anyOf": [ 287 | { "$ref": "#/definitions/parentChildrenSyncUpTarget" }, 288 | { "$ref": "#/definitions/singleRecordSyncUpTarget" }, 289 | { "$ref": "#/definitions/batchSyncUpTarget" }, 290 | { "$ref": "#/definitions/customSyncUpTarget" }, 291 | { "$ref": "#/definitions/collectionSyncUpTarget" } 292 | ] 293 | }, 294 | "syncDown": { 295 | "type": "object", 296 | "properties": { 297 | "syncName": { "$ref": "#/definitions/syncName" }, 298 | "soupName": { "$ref": "#/definitions/soupName" }, 299 | "syncType": { "const": "syncDown" }, 300 | "target": { "$ref": "#/definitions/syncDownTarget" }, 301 | "options": { "$ref": "#/definitions/syncDownOptions"} 302 | }, 303 | "required": ["syncName", "soupName", "syncType", "target", "options"] 304 | }, 305 | "syncUp": { 306 | "type": "object", 307 | "properties": { 308 | "syncName": { "$ref": "#/definitions/syncName" }, 309 | "soupName": { "$ref": "#/definitions/soupName" }, 310 | "syncType": { "const": "syncUp" }, 311 | "target": { "$ref": "#/definitions/syncUpTarget" }, 312 | "options": { "$ref": "#/definitions/syncUpOptions"} 313 | }, 314 | "required": ["syncName", "soupName", "syncType", "target", "options"] 315 | }, 316 | "sync": { 317 | "oneOf": [ 318 | { "$ref": "#/definitions/syncDown" }, 319 | { "$ref": "#/definitions/syncUp" } 320 | ] 321 | } 322 | }, 323 | 324 | "type": "object", 325 | "properties": { 326 | "syncs": { 327 | "type": "array", 328 | "items": { "$ref": "#/definitions/sync" } 329 | } 330 | }, 331 | "required": ["syncs"] 332 | } 333 | -------------------------------------------------------------------------------- /shared/templateHelper.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018-present, salesforce.com, inc. 3 | * All rights reserved. 4 | * Redistribution and use of this software in source and binary forms, with or 5 | * without modification, are permitted provided that the following conditions 6 | * are met: 7 | * - Redistributions of source code must retain the above copyright notice, this 8 | * list of conditions and the following disclaimer. 9 | * - Redistributions in binary form must reproduce the above copyright notice, 10 | * this list of conditions and the following disclaimer in the documentation 11 | * and/or other materials provided with the distribution. 12 | * - Neither the name of salesforce.com, inc. nor the names of its contributors 13 | * may be used to endorse or promote products derived from this software without 14 | * specific prior written permission of salesforce.com, inc. 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | // Dependencies 29 | var path = require('path'), 30 | SDK = require('./constants'), 31 | utils = require('./utils'); 32 | 33 | // 34 | // Helper to prepare template 35 | // 36 | function prepareTemplate(config, templateDir) { 37 | var template = require(path.join(templateDir, 'template.js')); 38 | return utils.runFunctionThrowError( 39 | function() { 40 | return template.prepare(config, utils.replaceInFiles, utils.moveFile, utils.removeFile); 41 | }, 42 | templateDir); 43 | } 44 | 45 | // 46 | // Get templates for the given cli 47 | // 48 | function getTemplates(cli) { 49 | try { 50 | 51 | // Creating tmp dir for template clone 52 | var tmpDir = utils.mkTmpDir(); 53 | 54 | // Cloning template repo 55 | var repoDir = utils.cloneRepo(tmpDir, SDK.templatesRepoUri); 56 | 57 | // Getting list of templates 58 | var templates = require(path.join(repoDir, 'templates.json')); 59 | 60 | // Keeping only applicable templates, adding full template url 61 | var applicableTemplates = templates 62 | .filter(template => cli.appTypes.includes(template.appType) && cli.platforms.filter(platform => template.platforms.includes(platform)).length > 0); 63 | 64 | // Cleanup 65 | utils.removeFile(tmpDir); 66 | 67 | return applicableTemplates; 68 | } 69 | catch (error) { 70 | utils.logError(cli.name + ' failed\n', error); 71 | process.exit(1); 72 | } 73 | } 74 | 75 | // 76 | // Get appType for the given template given by its uri 77 | // 78 | function getAppTypeFromTemplate(templateRepoUriWithPossiblePath) { 79 | var templateUriParsed = utils.separateRepoUrlPathBranch(templateRepoUriWithPossiblePath); 80 | var templateRepoUri = templateUriParsed.repo + '#' + templateUriParsed.branch; 81 | var templatePath = templateUriParsed.path; 82 | 83 | // Creating tmp dir for template clone 84 | var tmpDir = utils.mkTmpDir(); 85 | 86 | // Cloning template repo 87 | var repoDir = utils.cloneRepo(tmpDir, templateRepoUri); 88 | 89 | // Getting template 90 | var appType = require(path.join(repoDir, templatePath, 'template.js')).appType; 91 | 92 | // Cleanup 93 | utils.removeFile(tmpDir); 94 | 95 | // Done 96 | return appType; 97 | } 98 | 99 | 100 | module.exports = { 101 | prepareTemplate, 102 | getTemplates, 103 | getAppTypeFromTemplate 104 | }; 105 | -------------------------------------------------------------------------------- /shared/utils.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016-present, salesforce.com, inc. 3 | * All rights reserved. 4 | * Redistribution and use of this software in source and binary forms, with or 5 | * without modification, are permitted provided that the following conditions 6 | * are met: 7 | * - Redistributions of source code must retain the above copyright notice, this 8 | * list of conditions and the following disclaimer. 9 | * - Redistributions in binary form must reproduce the above copyright notice, 10 | * this list of conditions and the following disclaimer in the documentation 11 | * and/or other materials provided with the distribution. 12 | * - Neither the name of salesforce.com, inc. nor the names of its contributors 13 | * may be used to endorse or promote products derived from this software without 14 | * specific prior written permission of salesforce.com, inc. 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | var shelljs = require('shelljs'), 29 | execSync = require('child_process').execSync, 30 | fs = require('fs'), 31 | path = require('path'), 32 | COLOR = require('./outputColors'); 33 | 34 | var LOG_LEVELS = { 35 | OFF: 0, 36 | FATAL: 100, 37 | ERROR: 200, 38 | WARN: 300, 39 | INFO: 400, 40 | DEBUG: 500, 41 | TRACE: 600, 42 | ALL: Number.MAX_SAFE_INTEGER 43 | }; 44 | 45 | var LOG_LEVEL = LOG_LEVELS.INFO; 46 | 47 | var exitOnFailure = false; 48 | 49 | /** 50 | * Set log level 51 | * 52 | * @param {int} logLevel 53 | */ 54 | function setLogLevel(logLevel) { 55 | LOG_LEVEL = logLevel; 56 | } 57 | 58 | /** 59 | * Creates a comparable version number from a version string in the format x[.y[.ignored]]. 60 | * Currently only looks for major and minor version numbers. 61 | * 62 | * Examples: 63 | * getVersionNumberFromString('5') will return 5000. 64 | * getVersionNumberFromString('5.8') will return 5008 65 | * getVersionNumberFromString('5.11.26-43.3.7') will return 5011 66 | * getVersionNumberFromString('sandwich') will log an error and return 0 67 | * 68 | * @param {String} versionString The string representation of the version. 69 | * @return {Number} The numeric version number, or 0 if the version string isn't a valid format. 70 | */ 71 | function getVersionNumberFromString(versionString) { 72 | // Only supporting major/minor version checking at this point. 73 | var versionRegex = new RegExp(/^[^\d]*(\d+)(\.(\d+))?(\.(\d+))?/, 'm'); 74 | var matchArray = versionString.match(versionRegex); 75 | if (matchArray === null) { 76 | log(LOG_LEVELS.WARN, 'Invalid version string "' + versionString + '". Should be in the format x[.y[.z[.ignored]]]'); 77 | return 0; 78 | } else { 79 | var majorVersion = parseInt(matchArray[1]); 80 | var minorVersion = (matchArray[3] === undefined ? 0 : parseInt(matchArray[3])); 81 | var patchVersion = (matchArray[5] === undefined ? 0 : parseInt(matchArray[5])); 82 | var combinedVersion = (1000000 * majorVersion) + (1000 * minorVersion) + patchVersion; 83 | return combinedVersion; 84 | } 85 | } 86 | 87 | /** 88 | * Checks the version of a tool by running the given command 89 | * 90 | * @param {String} cmd Command to run to get the tool version 91 | * @param {String} minVersionRequired Minimum version required 92 | * @param {String} maxVersionSupported Maximum version supported 93 | * @param {String} toolName Name of tool 94 | * 95 | * @throws {Error} if tool not found or version too low 96 | */ 97 | function checkToolVersion(cmd, minVersionRequired, maxVersionSupported, toolName) { 98 | var toolVersionNum = getToolVersion(cmd); 99 | var minVersionRequiredNum = getVersionNumberFromString(minVersionRequired); 100 | 101 | if (toolVersionNum < minVersionRequiredNum) { 102 | throw new Error('Installed ' + toolName + ' is less than the minimum required version (' 103 | + minVersionRequired + ').\nPlease upgrade your version of ' + toolName + '.'); 104 | } 105 | 106 | if (maxVersionSupported) { 107 | var maxVersionSupportedNum = getVersionNumberFromString(maxVersionSupported); 108 | if (toolVersionNum > maxVersionSupportedNum) { 109 | throw new Error('Installed ' + toolName + ' is more than the maximum supported version (' 110 | + maxVersionSupported + ').\nPlease downgrade your version of ' + toolName + '.'); 111 | } 112 | } 113 | } 114 | 115 | /** 116 | * Returns the version of a tool as a number by running the given command 117 | * 118 | * @param {String} cmd Command to run to get the tool version 119 | * @return {Number} The numeric version number, or 0 if the version string isn't a valid format. 120 | */ 121 | function getToolVersion(cmd) { 122 | var toolName = cmd.split(' ')[0]; 123 | var toolVersion; 124 | try { 125 | var result = runProcessThrowError(cmd, null, true /* return output */); 126 | // Remove @salesforce/cli/ from the beginning of sf cli version. 127 | toolVersion = result.replace(/\r?\n|\r/, '').replace(/[^0-9\.]*/, '').replace('@salesforce/cli/', ''); 128 | } 129 | catch (error) { 130 | throw new Error(toolName + ' is required but could not be found. Please install ' + toolName + '.'); 131 | } 132 | 133 | var toolVersionNum = getVersionNumberFromString(toolVersion); 134 | return toolVersionNum; 135 | } 136 | 137 | 138 | /** 139 | * Replaces text in a file 140 | * 141 | * @param {String} fileName The file in which the text needs to be replaced. 142 | * @param {String} textInFile Text in the file to be replaced. 143 | * @param {String} replacementText Text used to replace the text in file. 144 | */ 145 | function replaceTextInFile(fileName, textInFile, replacementText) { 146 | var contents = fs.readFileSync(fileName, 'utf8'); 147 | var lines = contents.split(/\r*\n/); 148 | var result = lines.map(function (line) { 149 | return line.replace(textInFile, replacementText); 150 | }).join('\n'); 151 | 152 | fs.writeFileSync(fileName, result, 'utf8'); 153 | } 154 | 155 | 156 | /** 157 | * Run shell command - throws error if any 158 | * 159 | * @param {String} cmd The command to execute. 160 | * @param {String} dir Optional. The directory the command should be executed in. 161 | * @param {Boolean} returnOutput. If true, returns output as string. If false, pipes output through. 162 | */ 163 | function runProcessThrowError(cmd, dir, returnOutput) { 164 | logDebug('Running: ' + cmd); 165 | if (returnOutput) { 166 | return execSync(cmd, {cwd: dir}).toString(); 167 | } 168 | else { 169 | var stdio = []; 170 | if (LOG_LEVEL >= LOG_LEVELS.DEBUG) { 171 | stdio = [0,1,2] 172 | } 173 | else if (LOG_LEVEL >= LOG_LEVELS.ERROR) { 174 | stdio = [0,2] 175 | } 176 | 177 | execSync(cmd, {cwd: dir, stdio: stdio}); 178 | } 179 | } 180 | 181 | /** 182 | * Run shell command - catch error if any (unless setExitOnFailure(true) was called) 183 | * 184 | * @param {String} cmd The command to execute. 185 | * @param {String} msg Message to print on success/failure. 186 | * @param {String} dir Optional. The directory the command should be executed in. 187 | * 188 | * @return true if successful, false otherwise 189 | */ 190 | function runProcessCatchError(cmd, msg, dir) { 191 | var success = false; 192 | logDebug('Running: ' + cmd); 193 | try { 194 | runProcessThrowError(cmd, dir); 195 | if (msg) logInfo('!SUCCESS! ' + msg, COLOR.green); 196 | success = true; 197 | } catch (err) { 198 | logError(msg ? '!FAILURE! ' + msg : '', err); 199 | if (exitOnFailure) { 200 | process.exit(1); 201 | } 202 | } 203 | finally { 204 | return success; 205 | } 206 | } 207 | 208 | /** 209 | * Run function - throws error if any 210 | * 211 | * @param {Function} func The function to execute. 212 | * @param {String} dir Optional. The directory the function should be executed from. 213 | */ 214 | function runFunctionThrowError(func, dir) { 215 | if (dir) shelljs.pushd(dir); 216 | try { 217 | return func(); 218 | } 219 | finally { 220 | if (dir) shelljs.popd(); 221 | } 222 | } 223 | 224 | /** 225 | * Makes temp directory. 226 | * 227 | * @return {String} Path of temp directory 228 | */ 229 | 230 | function mkTmpDir() { 231 | var d = new Date(); 232 | var timestamp = new Date(d.getTime() - 1000*60*d.getTimezoneOffset()).toISOString().replace(/[^0-9T.]/g, ''); // e.g. 20190510T134348.528 for Fri May 10 2019 13:43:48 GMT-0700 (Pacific Daylight Time) 233 | var tmpDir = path.resolve('tmp' + timestamp); 234 | logDebug('Making temp dir:' + tmpDir); 235 | shelljs.mkdir('-p', tmpDir); 236 | return tmpDir; 237 | } 238 | 239 | /** 240 | * Make directory if it does not exist 241 | * 242 | * @param {string} Path of directory to create 243 | */ 244 | function mkDirIfNeeded(dir) { 245 | if (dir != '') { 246 | shelljs.mkdir('-p', dir); 247 | } 248 | } 249 | 250 | /** 251 | * Replace string in files. 252 | * 253 | * @param {String or RegExp} from String to match. 254 | * @param {String} to Replacement string. 255 | * @param {Array} files List of files to do the replacements in. 256 | */ 257 | function replaceInFiles(from, to, files) { 258 | var fromRegexp = typeof(from) === 'string' ? new RegExp(from, 'g') : from; 259 | for (var i=0; i 1 ? parts[1] : 'master'; 335 | var repo = repoWithPath.split('/').splice(0,5).join('/'); 336 | var path = repoWithPath.split('/').splice(5).join('/'); 337 | return {repo:repo, branch:branch, path:path}; 338 | } 339 | 340 | /** 341 | * Clone repo. 342 | * 343 | * @param {String} tmpDir Parent dir for clone 344 | * @param {String} repoUrlWithBranch Repo URL e.g. https://github.com/xyz/abc or https://github.com/xyz/abc#branch 345 | * 346 | * @return repoDir 347 | */ 348 | 349 | function cloneRepo(tmpDir, repoUrlWithBranch) { 350 | var parts = repoUrlWithBranch.split('#'); 351 | var repoUrl = parts[0]; 352 | var branch = parts.length > 1 ? parts[1] : 'master'; 353 | var subparts = repoUrl.split('/'); 354 | var repoName = subparts[subparts.length - 1]; 355 | var repoDir = path.join(tmpDir, repoName); 356 | 357 | shelljs.mkdir('-p', repoDir); 358 | runProcessThrowError('git clone --branch ' + branch + ' --single-branch --depth 1 --recurse-submodules ' + repoUrl + ' ' + '"' + repoDir + '"'); 359 | return repoDir; 360 | } 361 | 362 | /** 363 | * Log paragraph (header or footer) 364 | * 365 | * @param {String} lines 366 | */ 367 | function logParagraph(lines, color) { 368 | color = color || COLOR.green; 369 | logInfo(""); 370 | logInfo("********************************************************************************", color); 371 | logInfo("*", color); 372 | for (var i=0; i