├── .eslintrc ├── .gitignore ├── .jshintrc ├── .profile ├── .travis.yml ├── LICENSE ├── Procfile ├── README.md ├── app.json ├── assets └── deployments.sql ├── dist └── assets │ ├── css │ └── main.css │ ├── favicons │ ├── android-icon-144x144.png │ ├── android-icon-192x192.png │ ├── android-icon-36x36.png │ ├── android-icon-48x48.png │ ├── android-icon-72x72.png │ ├── android-icon-96x96.png │ ├── apple-icon-114x114.png │ ├── apple-icon-120x120.png │ ├── apple-icon-144x144.png │ ├── apple-icon-152x152.png │ ├── apple-icon-180x180.png │ ├── apple-icon-57x57.png │ ├── apple-icon-60x60.png │ ├── apple-icon-72x72.png │ ├── apple-icon-76x76.png │ ├── apple-icon-precomposed.png │ ├── apple-icon.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon-96x96.png │ ├── favicon.ico │ ├── manifest.json │ ├── ms-icon-144x144.png │ ├── ms-icon-150x150.png │ ├── ms-icon-310x310.png │ └── ms-icon-70x70.png │ ├── fonts │ ├── License-for-font.txt │ ├── SalesforceSans-Bold.ttf │ ├── SalesforceSans-BoldItalic.ttf │ ├── SalesforceSans-Book.ttf │ ├── SalesforceSans-Italic.ttf │ ├── SalesforceSans-Light.ttf │ ├── SalesforceSans-LightItalic.ttf │ ├── SalesforceSans-Regular.ttf │ ├── SalesforceSans-Semibold.ttf │ ├── SalesforceSans-Thin.ttf │ ├── SalesforceSans-ThinItalic.ttf │ └── webfonts │ │ ├── SalesforceSans-Bold.woff │ │ ├── SalesforceSans-Bold.woff2 │ │ ├── SalesforceSans-BoldItalic.woff │ │ ├── SalesforceSans-BoldItalic.woff2 │ │ ├── SalesforceSans-Italic.woff │ │ ├── SalesforceSans-Italic.woff2 │ │ ├── SalesforceSans-Light.woff │ │ ├── SalesforceSans-Light.woff2 │ │ ├── SalesforceSans-LightItalic.woff │ │ ├── SalesforceSans-LightItalic.woff2 │ │ ├── SalesforceSans-Regular.woff │ │ ├── SalesforceSans-Regular.woff2 │ │ ├── SalesforceSans-Thin.woff │ │ ├── SalesforceSans-Thin.woff2 │ │ ├── SalesforceSans-ThinItalic.woff │ │ └── SalesforceSans-ThinItalic.woff2 │ └── images │ ├── DeployToSFDX.svg │ ├── launch.png │ ├── loader.gif │ └── salesforce_cloud.png ├── lib ├── apis.js ├── commands.js ├── postgres.js ├── steps.js └── web.js ├── package.json ├── scripts ├── choose.js ├── deploy.js ├── deploying.js └── js-yaml.min.js ├── src ├── assets │ ├── favicons │ │ ├── android-icon-144x144.png │ │ ├── android-icon-192x192.png │ │ ├── android-icon-36x36.png │ │ ├── android-icon-48x48.png │ │ ├── android-icon-72x72.png │ │ ├── android-icon-96x96.png │ │ ├── apple-icon-114x114.png │ │ ├── apple-icon-120x120.png │ │ ├── apple-icon-144x144.png │ │ ├── apple-icon-152x152.png │ │ ├── apple-icon-180x180.png │ │ ├── apple-icon-57x57.png │ │ ├── apple-icon-60x60.png │ │ ├── apple-icon-72x72.png │ │ ├── apple-icon-76x76.png │ │ ├── apple-icon-precomposed.png │ │ ├── apple-icon.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon-96x96.png │ │ ├── favicon.ico │ │ ├── manifest.json │ │ ├── ms-icon-144x144.png │ │ ├── ms-icon-150x150.png │ │ ├── ms-icon-310x310.png │ │ └── ms-icon-70x70.png │ └── images │ │ ├── DeployToSFDX.svg │ │ ├── launch.png │ │ ├── loader.gif │ │ └── salesforce_cloud.png └── scss │ └── main.scss ├── views ├── pages │ ├── about.ejs │ ├── choose.ejs │ ├── deploy.ejs │ ├── deploying.ejs │ ├── error.ejs │ ├── index.ejs │ ├── notdevhub.ejs │ └── repo.ejs └── partials │ ├── actions.ejs │ ├── footer.ejs │ └── head.ejs ├── web.js └── worker.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb-base", 3 | "parserOptions": { 4 | "ecmaVersion": 6 5 | }, 6 | "env": { 7 | "node": true, 8 | "es6": true 9 | }, 10 | "rules" : { 11 | "arrow-body-style": ["error", "as-needed"], 12 | "no-alert": 2, 13 | "no-array-constructor": 2, 14 | "no-bitwise": 0, 15 | "no-caller": 0, 16 | "no-catch-shadow": 2, 17 | "no-comma-dangle": 0, 18 | "no-cond-assign": 2, 19 | "no-console": 0, 20 | "no-constant-condition": 2, 21 | "no-control-regex": 2, 22 | "no-debugger": 2, 23 | "no-delete-var": 2, 24 | "no-div-regex": 0, 25 | "no-dupe-keys": 2, 26 | "no-dupe-args": 2, 27 | "no-duplicate-case": 2, 28 | "no-else-return": 0, 29 | "no-empty": 2, 30 | "no-empty-character-class": 2, 31 | "no-eq-null": 0, 32 | "no-eval": 2, 33 | "no-ex-assign": 2, 34 | "no-extend-native": 0, 35 | "no-extra-bind": 2, 36 | "no-extra-boolean-cast": 2, 37 | "no-extra-parens": 0, 38 | "no-extra-semi": 2, 39 | "no-fallthrough": 2, 40 | "no-floating-decimal": 0, 41 | "no-func-assign": 2, 42 | "no-implied-eval": 2, 43 | "no-inline-comments": 0, 44 | "no-inner-declarations": [2, "functions"], 45 | "no-invalid-regexp": 2, 46 | "no-irregular-whitespace": 2, 47 | "no-iterator": 2, 48 | "no-label-var": 2, 49 | "no-labels": 2, 50 | "no-lone-blocks": 2, 51 | "no-lonely-if": 0, 52 | "no-loop-func": 2, 53 | "no-mixed-requires": [0, false], 54 | "no-mixed-spaces-and-tabs": 0, 55 | "no-multi-spaces": 0, 56 | "no-multi-str": 2, 57 | "no-multiple-empty-lines": 0, 58 | "no-native-reassign": 2, 59 | "no-negated-in-lhs": 2, 60 | "no-nested-ternary": 0, 61 | "no-new": 2, 62 | "no-new-func": 0, 63 | "no-new-object": 2, 64 | "no-new-require": 0, 65 | "no-new-wrappers": 2, 66 | "no-obj-calls": 2, 67 | "no-octal": 2, 68 | "no-octal-escape": 2, 69 | "no-param-reassign": 0, 70 | "no-path-concat": 0, 71 | "no-plusplus": 0, 72 | "no-process-env": 0, 73 | "no-process-exit": 2, 74 | "no-proto": 1, 75 | "no-redeclare": 2, 76 | "no-regex-spaces": 2, 77 | "no-reserved-keys": 0, 78 | "no-restricted-modules": 0, 79 | "no-return-assign": 2, 80 | "no-script-url": 2, 81 | "no-self-compare": 0, 82 | "no-sequences": 2, 83 | "no-shadow": 2, 84 | "no-shadow-restricted-names": 2, 85 | "no-space-before-semi": 0, 86 | "no-spaced-func": 2, 87 | "no-sparse-arrays": 2, 88 | "no-sync": 0, 89 | "no-tabs": 2, 90 | "no-ternary": 0, 91 | "no-trailing-spaces": 0, 92 | "no-throw-literal": 0, 93 | "no-undef": 2, 94 | "no-undef-init": 0, 95 | "no-undefined": 0, 96 | "no-underscore-dangle": 0, 97 | "no-unreachable": 2, 98 | "no-unused-expressions": 0, 99 | "no-unused-vars": [2, {"vars": "local", "args": "after-used"}], 100 | "no-use-before-define": 1, 101 | "no-void": 0, 102 | "no-warning-comments": [0, { "terms": ["todo", "fixme", "xxx"], "location": "start" }], 103 | "no-with": 2, 104 | 105 | "block-scoped-var": 0, 106 | "brace-style": [0, "1tbs"], 107 | "camelcase": 0, 108 | "comma-dangle": [2, "never"], 109 | "comma-spacing": 0, 110 | "comma-style": 0, 111 | "complexity": [0, 11], 112 | "consistent-return": 2, 113 | "consistent-this": [0, "that"], 114 | "curly": [2, "all"], 115 | "default-case": 0, 116 | "dot-notation": [0, { "allowKeywords": true }], 117 | "eol-last": 0, 118 | "eqeqeq": 2, 119 | "func-names": 0, 120 | "func-style": [0, "declaration"], 121 | "generator-star": 0, 122 | "generator-star-spacing": 0, 123 | "global-strict": [0, "never"], 124 | "guard-for-in": 0, 125 | "handle-callback-err": 0, 126 | "indent": 0, 127 | "key-spacing": 0, 128 | "linebreak-style": [0, "windows"], 129 | "max-depth": [0, 4], 130 | "max-len": [0, 80, 4], 131 | "max-nested-callbacks": [0, 2], 132 | "max-params": [0, 3], 133 | "max-statements": [0, 10], 134 | "new-cap": 0, 135 | "new-parens": 2, 136 | "newline-after-var": 0, 137 | "one-var": 0, 138 | "operator-assignment": [0, "always"], 139 | "padded-blocks": 0, 140 | "quote-props": 0, 141 | "quotes": "error", 142 | "radix": 0, 143 | "semi": 2, 144 | "semi-spacing": [0, {"before": false, "after": true}], 145 | "sort-vars": 0, 146 | "space-after-function-name": [0, "never"], 147 | "space-after-keywords": [0, "always"], 148 | "space-before-blocks": [0, "always"], 149 | "space-before-function-paren": [0, "always"], 150 | "space-before-function-parentheses": [0, "always"], 151 | "space-in-brackets": [0, "never"], 152 | "space-in-parens": [0, "never"], 153 | "space-infix-ops": 0, 154 | "keyword-spacing": [2, {"before": true, "after": true}], 155 | "space-unary-ops": [2, { "words": true, "nonwords": false }], 156 | "spaced-line-comment": [0, "always"], 157 | "strict": 0, 158 | "use-isnan": 2, 159 | "valid-jsdoc": 0, 160 | "valid-typeof": 2, 161 | "vars-on-top": 0, 162 | "wrap-iife": 0, 163 | "wrap-regex": 0, 164 | "yoda": [2, "never"] 165 | } 166 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://docs.npmjs.com/cli/shrinkwrap#caveats 27 | node_modules 28 | 29 | # Debug log from npm 30 | npm-debug.log 31 | 32 | # Environment settings 33 | .env 34 | 35 | # Generated files and dirs 36 | public 37 | 38 | # Local resources 39 | tmp 40 | cert.pem 41 | key.pem -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esversion": 6 3 | } -------------------------------------------------------------------------------- /.profile: -------------------------------------------------------------------------------- 1 | echo "Installing JQ for JSON parsing ..." 2 | 3 | wget -O jq https://github.com/stedolan/jq/releases/download/jq-1.5/jq-linux64 4 | chmod +x ./jq 5 | 6 | echo "Updating PATH to include jq ..." 7 | export PATH=$PATH:/app 8 | 9 | echo "Updating PATH to include Salesforce CLI ..." 10 | export PATH=$PATH:/app/.local/share/sfdx/cli/bin/ 11 | 12 | echo "Updating Salesforce CLI plugin ..." 13 | sfdx update 14 | 15 | echo "Return version info ..." 16 | sfdx version 17 | sfdx plugins --core 18 | 19 | echo "Creating local resources ..." 20 | mkdir /app/tmp 21 | 22 | echo "Completed!" -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "stable" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: node web.js 2 | worker: node worker.js -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deploy to Salesforce DX 2 | 3 | An open-source and community-driven tool for one-click Salesforce DX deployments from public repositories to Scratch Orgs. You can us this tool by visiting [https://deploy-to-sfdx.com/](https://deploy-to-sfdx.com/) and logging in with your Dev Hub credentials. 4 | 5 | ## Local Setup 6 | 7 | You'll need the following setup to run this project locally. 8 | 9 | 1. Create a Salesforce DX Dev Hub. You can learn more [here](https://developer.salesforce.com/docs/atlas.en-us.sfdx_setup.meta/sfdx_setup/sfdx_setup_enable_devhub.htm). 10 | 11 | 2. Install the Salesforce CLI from [here](https://developer.salesforce.com/tools/sfdxcli). 12 | 13 | 3. Create a Connected App in your Dev Hub. 14 | 15 | - Callback: https://localhost:8443/oauth/callback 16 | - Scopes 17 | 18 | - Access your basic information (id, profile, email, address, phone) 19 | - Access and manage your data (api) 20 | - Provide access to your data via the Web (web) 21 | - Allow access to your unique identifier (openid) 22 | 23 | - Note down the consumer key and consumer secret for later. 24 | 25 | 4. Create a Postgres database in Heroku. 26 | 27 | 5. Create the `deployments` and `deployment_steps` tables by running the `deployments.sql` script against your Postgres database. 28 | 29 | 6. Create a `.env` file to store your local environment settings. 30 | 31 | 7. Update your `.env` file to include the following 32 | 33 | ``` 34 | BUILDPACK_URL=https://github.com/wadewegner/sfdx-buildpack 35 | CALLBACKURL=https://localhost:8443/oauth/callback 36 | CLI_PATH= 37 | CONSUMERKEY=[your_consumer_key] 38 | CONSUMERSECRET=[your_consumer_secret] 39 | STARTINGDIRECTORY=cd tmp/; 40 | PORT=8443 41 | NODE_ENV=dev 42 | DATABASE_URL= 43 | PASS_PHRASE= 44 | CERT_PEM= 45 | KEY_PEM= 46 | ``` 47 | 48 | 8. Get your Postgres `DATABASE_URL` by running `heroku config:get DATABASE_URL --app deploy-to-sfdx` and update. 49 | 50 | 9. Create your own local certificates or use these defaults: 51 | 52 | ``` 53 | PASS_PHRASE=test1234 54 | CERT_PEM=-----BEGIN CERTIFICATE-----\nMIIDtTCCAp2gAwIBAgIJAIC3Ts6WBYTlMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV\nBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX\naWRnaXRzIFB0eSBMdGQwHhcNMTcwOTEwMDExODQ4WhcNMTgwOTEwMDExODQ4WjBF\nMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50\nZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\nCgKCAQEAqMnT1RxpfKvc8HB8IoWMH1HSlsFyAw7Vg8I5xao6MfaydO3/C5Dw+i0P\n/CMmWdHSbhk/bOB+0FFHNYo0UOhMaxSouE+U9VHa2HZnwxvcOTJwT5NdRbPiIdkJ\n6G9CTfxe8KJV7vNlZ/ig1tZjSe/4MW5pg57d47o/Kg2YDgyeZTuR1zsxlwlR0LZS\nUhkrqG5JI8ouI7iTwdl3g3HAMV4k5AW2ox9UmAbXnvOt0DWWExcWkysYVnJ98wXs\nIv91nFFiz+P8MPhQ8EIyujerHuqZ2G2id2Wo6hVHG0o1ja4klI3F/xgcyw3CrXSD\nQcZO/CZ1nIJAffAvwaPqCY8/N9UofwIDAQABo4GnMIGkMB0GA1UdDgQWBBQ+xRiZ\nvg4qd18XG4Ivi2VEwSt3tjB1BgNVHSMEbjBsgBQ+xRiZvg4qd18XG4Ivi2VEwSt3\ntqFJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNV\nBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAIC3Ts6WBYTlMAwGA1UdEwQF\nMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBACPWZRPN6iAdWkAaU/FTeD4uMsw9N+Jw\npeYGNk0GVLoIXYCkr7W/X0Zoz27kqb2/QiZiKkuH2PD+VOGgKrerzQ6HFpPYEUgN\nCGNjGe6lug7o6tDGQTqD2U0YM0XS8s3nkT172NmXi/ZtAdS7qMOnO4UdD3HdOzRN\nrDVDUVtZcbC5T6iRzQ35XZgmBj+3uRq6cOOt+W8HKgu70hKPILj4k/05jfnvwXQ4\nwesGND5BgYvunr1RqdcNxKm4rqMYOC6wnz16lRAYsT33CJsd4tc7jIxX4HzOKnVI\nrNoL90NA1mU2v6jSoYCNcwvnRwZgfFhjYAxROGDSjNI2bDDXRji2Lt8=\n-----END CERTIFICATE-----\n 55 | KEY_PEM=-----BEGIN RSA PRIVATE KEY-----\nProc-Type: 4,ENCRYPTED\nDEK-Info: DES-EDE3-CBC,87779AB98C3E6E95\n\niA6Uc2F53GxcFMCr+Lo9y82aflMu4YeEHgUYuzFcfAvhky0GIx3Mn2IrYYHzhzkc\n+pCOLAsclhFCHhh0jOftFIXXDc2dhO/tef/IMYOozk2Qz0mMf9Ee89k5LYEFr7/o\npnov4NxIrsw70oMeXCe89HRGMfYcjQMlkD7E1OV3G+SoCefw4uzJChssz21KdMdN\n5O1GaA5s+P1U37E/ilaSfYKUksiW2T27mtv1LelhUgeYmc9E+WyV6f7FZosD0mnU\nzHmvGgHDRJJ84v3UiPJ2Kui6ELRhhyzYkYm4yhPWsNUy9R6iaSCXWibM99ME5eMl\n3horfx5/5BaXBmnpN4pnf4pIE+xO/JUSD0cl13yHJzs1ALW8XhSmEp+sPAeTYIDn\nhHDh8AXyxwQe0BPQWyhn/1a4Fcv7BRIBwmQiTvuieq0teE62PUwlQr3eU+mg2p1w\nnnM2E7dm4rEUoyR/kxKUmpS1uvw8+SauzWnrv7ooRPbcIBuqMLpGJpK7cNzwWFhA\nHAtwWy804i4vgopekBI83pFQOOwMc4I/qYHEiyRE3EHuNiEam4R3q6czE61wGNVT\ntbt4kD8cVHtIx1+pUQjSZBpo1iTIOpm86SCytEitzLIPl600kD1LUo6AL4LsI9WD\nN275OBau/FuD+PN4nlKpRzj89XSecoRsOrLmJzLdQUgwoDW62mAKgWx8tt4zTV8W\n4OuK2jPZyN72dxD6LbHdLgKGE+J9XWFeNRj/1OxJj83AylS+nH6HOvtTOsPjiaUT\nJil+ixfeEB6/TcbQ2DWFY8xJ0QkhuTLr+aqJTKKImt+HmtgpexqO5gbuybDVLcG4\nwC25ZSy9WJxgaK1OwczpodHs4z6QDw6NAZDa+wbGyH7rXu04CJHeeSuAlhWpMmtX\n8jLHPmqU1m8ctkhO4n/z6lFnxmMx3VJlNBunJodkzH9GaEt9no3OdPT62TczFyYy\nJf89noStmXbV/DK3EFzGL77w8estQKHAtsuZcXeAnd7dLOhs5+hzR2q6MW8h6nQm\nO1w7YgheBjHY2f+Kte+DJdQCp1IssYzjBq6dVlFu4Qw35vHMjlBYnnLXjB8pyPIA\n35cNGNY580dP96VNmDFGklxZQGLi2W/f4GzcSPQLcflRUJZ8YJT3pAOO1n4ATaUU\nTNN4OnwqYbrU91+pkDJPIZSoEEZErTWRMO9oTgCK68mE1NanXnMOVpgMiheq5POj\nzs8Q6HDS4XMr+prbsvXNC3GKkVLDQOSUc6wMbF9nOu3fUXgr/LOfZGBagkktP6FE\ntU0w+8PA2CUm6s4zCbW/5O0BkC4nvxTfSj5i+FVbT9gbuHjFldyoqTvzaODrcHF4\ncAId/HwdyqvE2i4jeauotyaazJZxea3rGNS0Rjum/0BHivGiPZyPb01M4HiRz2ll\ndhD7fC6O8sSxswSU6b+F5D8w8BCg7u8dX2iXXM4FEyBtvdOfFtRj6p/uKsqol3Pu\nLwt5guvYlEJOA68VnrfofFaKHqygoN7bklOhU6qoBKxdpTAve8zLTwDWymR10ZF1\nPu7NqPrcnesVuhpvylInLg1c5ccuZmsr/FwEi9C83bGMVxUXLBFUZSCEiRsmfR0F\n-----END RSA PRIVATE KEY-----\n 56 | ``` 57 | 58 | ## Run Locally 59 | 60 | Once you have the above setup correctly, you can run by running `heroku local` and then browsing to [https://localhost:8443/](https://localhost:8443/). 61 | 62 | ## Updating SCSS or UX assets 63 | 64 | If you make changes to SCSS or UX assets, be sure you regenerate the `dist` files by running the command `npm run css-build`. 65 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Deploy to Salesforce DX", 3 | "description": "An application that will deploy an SFDX project from a public Github repo", 4 | "repository": "https://github.com/wadewegner/deploy-to-sfdx", 5 | "logo": "", 6 | "keywords": ["deploy", "sfdx", "salesforcedx"], 7 | "image": "", 8 | "scripts": { 9 | "postdeploy": "bin/deploy" 10 | }, 11 | "env": { 12 | "CALLBACKURL": { 13 | "description": "The callback URL for your Connected App", 14 | "value": "" 15 | }, 16 | "CONSUMERKEY": { 17 | "description": "The consumer key for your Connected App.", 18 | "value": "" 19 | }, 20 | "CONSUMERSECRET": { 21 | "description": "The consumer secret for your Connected App.", 22 | "value": "" 23 | }, 24 | "STARTINGDIRECTORY": { 25 | "description": "The base directory for the CLI", 26 | "value": "" 27 | } 28 | }, 29 | "buildpacks": [{ 30 | "url": "https://github.com/wadewegner/salesforce-cli-buildpack" 31 | }, 32 | { 33 | "url": "heroku/nodejs" 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /assets/deployments.sql: -------------------------------------------------------------------------------- 1 | SET statement_timeout = 0; 2 | SET lock_timeout = 0; 3 | SET idle_in_transaction_session_timeout = 0; 4 | SET client_encoding = 'UTF8'; 5 | SET standard_conforming_strings = on; 6 | SET check_function_bodies = false; 7 | SET client_min_messages = warning; 8 | SET row_security = off; 9 | SET search_path = public, pg_catalog; 10 | SET default_tablespace = ''; 11 | SET default_with_oids = false; 12 | 13 | CREATE TABLE deployments ( 14 | guid text NOT NULL, 15 | username text, 16 | created_at timestamp without time zone DEFAULT now(), 17 | repo text, 18 | stage text DEFAULT 'init', 19 | complete boolean DEFAULT false, 20 | error_message text, 21 | settings text, 22 | scratch_url text 23 | ); 24 | 25 | CREATE TABLE deployment_steps ( 26 | guid text NOT NULL, 27 | created_at timestamp without time zone DEFAULT now(), 28 | stage text, 29 | message text 30 | ); 31 | 32 | CREATE SEQUENCE deployments_id_seq 33 | START WITH 1 34 | INCREMENT BY 1 35 | NO MINVALUE 36 | NO MAXVALUE 37 | CACHE 1; 38 | 39 | ALTER SEQUENCE deployments_id_seq OWNED BY deployments.guid; 40 | ALTER TABLE ONLY deployments ALTER COLUMN guid SET DEFAULT nextval('deployments_id_seq'::regclass); 41 | ALTER TABLE ONLY deployments 42 | ADD CONSTRAINT deployments_pkey PRIMARY KEY (guid); -------------------------------------------------------------------------------- /dist/assets/favicons/android-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/favicons/android-icon-144x144.png -------------------------------------------------------------------------------- /dist/assets/favicons/android-icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/favicons/android-icon-192x192.png -------------------------------------------------------------------------------- /dist/assets/favicons/android-icon-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/favicons/android-icon-36x36.png -------------------------------------------------------------------------------- /dist/assets/favicons/android-icon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/favicons/android-icon-48x48.png -------------------------------------------------------------------------------- /dist/assets/favicons/android-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/favicons/android-icon-72x72.png -------------------------------------------------------------------------------- /dist/assets/favicons/android-icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/favicons/android-icon-96x96.png -------------------------------------------------------------------------------- /dist/assets/favicons/apple-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/favicons/apple-icon-114x114.png -------------------------------------------------------------------------------- /dist/assets/favicons/apple-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/favicons/apple-icon-120x120.png -------------------------------------------------------------------------------- /dist/assets/favicons/apple-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/favicons/apple-icon-144x144.png -------------------------------------------------------------------------------- /dist/assets/favicons/apple-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/favicons/apple-icon-152x152.png -------------------------------------------------------------------------------- /dist/assets/favicons/apple-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/favicons/apple-icon-180x180.png -------------------------------------------------------------------------------- /dist/assets/favicons/apple-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/favicons/apple-icon-57x57.png -------------------------------------------------------------------------------- /dist/assets/favicons/apple-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/favicons/apple-icon-60x60.png -------------------------------------------------------------------------------- /dist/assets/favicons/apple-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/favicons/apple-icon-72x72.png -------------------------------------------------------------------------------- /dist/assets/favicons/apple-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/favicons/apple-icon-76x76.png -------------------------------------------------------------------------------- /dist/assets/favicons/apple-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/favicons/apple-icon-precomposed.png -------------------------------------------------------------------------------- /dist/assets/favicons/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/favicons/apple-icon.png -------------------------------------------------------------------------------- /dist/assets/favicons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/favicons/favicon-16x16.png -------------------------------------------------------------------------------- /dist/assets/favicons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/favicons/favicon-32x32.png -------------------------------------------------------------------------------- /dist/assets/favicons/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/favicons/favicon-96x96.png -------------------------------------------------------------------------------- /dist/assets/favicons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/favicons/favicon.ico -------------------------------------------------------------------------------- /dist/assets/favicons/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "App", 3 | "icons": [ 4 | { 5 | "src": "\/android-icon-36x36.png", 6 | "sizes": "36x36", 7 | "type": "image\/png", 8 | "density": "0.75" 9 | }, 10 | { 11 | "src": "\/android-icon-48x48.png", 12 | "sizes": "48x48", 13 | "type": "image\/png", 14 | "density": "1.0" 15 | }, 16 | { 17 | "src": "\/android-icon-72x72.png", 18 | "sizes": "72x72", 19 | "type": "image\/png", 20 | "density": "1.5" 21 | }, 22 | { 23 | "src": "\/android-icon-96x96.png", 24 | "sizes": "96x96", 25 | "type": "image\/png", 26 | "density": "2.0" 27 | }, 28 | { 29 | "src": "\/android-icon-144x144.png", 30 | "sizes": "144x144", 31 | "type": "image\/png", 32 | "density": "3.0" 33 | }, 34 | { 35 | "src": "\/android-icon-192x192.png", 36 | "sizes": "192x192", 37 | "type": "image\/png", 38 | "density": "4.0" 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /dist/assets/favicons/ms-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/favicons/ms-icon-144x144.png -------------------------------------------------------------------------------- /dist/assets/favicons/ms-icon-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/favicons/ms-icon-150x150.png -------------------------------------------------------------------------------- /dist/assets/favicons/ms-icon-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/favicons/ms-icon-310x310.png -------------------------------------------------------------------------------- /dist/assets/favicons/ms-icon-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/favicons/ms-icon-70x70.png -------------------------------------------------------------------------------- /dist/assets/fonts/License-for-font.txt: -------------------------------------------------------------------------------- 1 | FONT LICENSE AGREEMENT 2 | 3 | THIS FONT LICENSE AGREEMENT (“AGREEMENT”) IS A LEGAL AGREEMENT BETWEEN YOU AND 4 | SALESFORCE.COM, INC. (“WE”, “US”, “OUR”, AND “SALESFORCE”) THAT GOVERNS YOUR 5 | ACQUISITION AND USE OF THE SALESFORCE SANS FONT (E.G., TYPEFACE, TYPOGRAPHIC 6 | CHARACTERS, ALPHANUMERICS, SYMBOLS, DESIGNS, AND ORNAMENTS) AND THE RELATED 7 | FONT FILES (E.G., TRUETYPE (TTF), WEB OPEN FONT FORMAT (WOFF,WOFF2), EMBEDDED 8 | OPENTYPE (EOT), AND SCALABLE VECTOR GRAPHICS (SVG) FILES) (COLLECTIVELY, THE 9 | “FONT”). BY DOWNLOADING OR USING THE FONT, YOU AGREE TO THE TERMS OF THIS 10 | AGREEMENT. IF YOU ARE DOWNLOADING OR USING THE FONT ON BEHALF OF A COMPANY OR 11 | OTHER LEGAL ENTITY, YOU REPRESENT THAT YOU HAVE THE AUTHORITY TO BIND SUCH 12 | ENTITY AND ITS AFFILIATES TO THIS AGREEMENT, IN WHICH CASE THE TERMS “YOU” OR 13 | “YOUR” WILL REFER TO SUCH ENTITY AND ITS AFFILIATES. IF YOU DO NOT HAVE SUCH 14 | AUTHORITY, OR IF YOU DO NOT AGREE WITH THE TERMS OF THIS AGREEMENT, YOU MUST 15 | NOT DOWNLOAD OR USE THE FONT. This Agreement was last updated on August 21, 16 | 2015. It is effective between You and Us as of the date You accept this 17 | Agreement by downloading or using the Font. 18 | 19 | 1. License Grant 20 | Subject to the terms of this Agreement and any other 21 | applicable Salesforce terms, conditions, and acceptable use policies (AUPs), 22 | We hereby grant to You a revocable, non-transferable, non-exclusive, and non- 23 | sublicenseable limited license to, without modification, reproduce and use the 24 | Font solely to create applications with the Salesforce Lightning Design System 25 | that run in Salesforce or on a Salesforce platform (e.g., Lightning, Heroku, 26 | Visualforce) (“Applications”). 27 | 28 | 2. Restrictions 29 | To the extent your Application contains copyright notices, 30 | together with all other copyright notices included with each Application, You 31 | will include the following copyright notice: “The Salesforce Sans Font is used 32 | under license from salesforce.com, inc. Copyright 2015 Salesforce.com, Inc.” 33 | You may not modify, adapt, translate, reverse engineer, decompile, 34 | disassemble, or create derivative works based on the Font. You must include 35 | the Font in an Application in a manner that does not allow a user to access 36 | the Font outside of the Application. You will not use the Font on a 37 | standalone basis and will only use the Font as part of the Salesforce 38 | Lightning Design System. You must not take any action which will have the 39 | direct or indirect effect of causing the Font to become subject to the terms 40 | of an open source license or any similar terms. You may refer to the Font as 41 | “Salesforce Sans”, but You may not use any other Salesforce trademark in 42 | connection with the Font except as may be expressly agreed to by Salesforce in 43 | writing or as set forth in other applicable Salesforce terms, conditions, and 44 | acceptable use policies (AUPs) and in any event You must comply at all times 45 | with the Salesforce Trademark and Copyright Usage Guidelines located at http:/ 46 | /www2.sfdcstatic.com/assets/pdf/misc/salesforce_Trademark_Usage_Guidelines.pdf 47 | and any other supplemental guidelines that may apply to you. You must not 48 | license, sublicense, sell, resell, rent, lease, transfer, assign, distribute, 49 | time share, or otherwise commercially exploit the Font nor make the Font 50 | available to any third party, other than as expressly permitted by this 51 | Agreement. To the extent any Font documentation, style guides, or other 52 | applicable Salesforce terms, conditions, and acceptable use policies (AUPs) 53 | impose guidelines or restrictions for the use of the Font, You will abide by 54 | those guidelines and restrictions. 55 | 56 | 3. Ownership 57 | Subject to the limited rights expressly granted hereunder, We 58 | reserve all rights, title, and interest in and to the Font, including all 59 | related intellectual property rights. No rights are granted to You hereunder 60 | other than as expressly set forth herein. We shall have a royalty-free, 61 | worldwide, irrevocable, perpetual license to use and incorporate into the Font 62 | any suggestions, enhancement requests, recommendations, or other feedback 63 | provided by You. 64 | 65 | 4. Term and Termination 66 | This Agreement will take effect when you download or 67 | use the Font and will terminate upon the earlier of: (a) Your failure to 68 | comply with any term of this Agreement or any other applicable Salesforce 69 | terms, conditions, and acceptable use policies (AUPs); (b) return, 70 | destruction, or deletion of all copies of the Font in your possession; or, (c) 71 | 60 days after Salesforce provides You with written notice of termination. 72 | Salesforce’s rights and your obligations will survive the termination of this 73 | Agreement. Upon termination of this Agreement by Salesforce, if requested by 74 | Salesforce, you will destroy or delete all copies of the Font in your 75 | possession and cease using the Font in all Applications. 76 | 77 | 5. No Warranty 78 | THE FONT IS PROVIDED “AS-IS,” EXCLUSIVE OF ANY WARRANTY 79 | WHATSOEVER. WE DISCLAIM ALL IMPLIED WARRANTIES, INCLUDING WITHOUT LIMITATION 80 | ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, 81 | TITLE, AND NON-INFRINGEMENT. The Font may contain bugs or errors. Any use of 82 | the Font is at Your sole risk. You acknowledge that We may discontinue making 83 | the Font available to You at any time in Our sole discretion. 84 | 85 | 6. No Damages 86 | IN NO EVENT SHALL WE HAVE ANY LIABILITY HEREUNDER TO YOU FOR 87 | ANY DAMAGES WHATSOEVER, INCLUDING BUT NOT LIMITED TO DIRECT, INDIRECT, 88 | SPECIAL, INCIDENTAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES, OR DAMAGES BASED ON 89 | LOST PROFITS, DATA OR USE, HOWEVER CAUSED AND, WHETHER IN CONTRACT, TORT OR 90 | UNDER ANY OTHER THEORY OF LIABILITY, WHETHER OR NOT YOU HAVE BEEN ADVISED OF 91 | THE POSSIBILITY OF SUCH DAMAGES. 92 | 93 | 7. General Provisions 94 | You may not assign any of Your rights or obligations 95 | hereunder, whether by operation of law or otherwise, without Our prior written 96 | consent. This Agreement shall be governed exclusively by the internal laws of 97 | the State of California, without regard to its conflicts of laws rules. Each 98 | party hereby consents to the exclusive jurisdiction of the state and federal 99 | courts located in San Francisco County, California to adjudicate any dispute 100 | arising out of or relating to this Agreement. This Agreement constitutes the 101 | entire agreement between the parties, and supersedes all prior and 102 | contemporaneous agreements, proposals or representations, written or oral, 103 | concerning its subject matter. No modification, amendment, or waiver of any 104 | provision of this Agreement shall be effective unless in writing and either 105 | signed or accepted electronically by the party against whom the modification, 106 | amendment or waiver is to be asserted. 107 | -------------------------------------------------------------------------------- /dist/assets/fonts/SalesforceSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/fonts/SalesforceSans-Bold.ttf -------------------------------------------------------------------------------- /dist/assets/fonts/SalesforceSans-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/fonts/SalesforceSans-BoldItalic.ttf -------------------------------------------------------------------------------- /dist/assets/fonts/SalesforceSans-Book.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/fonts/SalesforceSans-Book.ttf -------------------------------------------------------------------------------- /dist/assets/fonts/SalesforceSans-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/fonts/SalesforceSans-Italic.ttf -------------------------------------------------------------------------------- /dist/assets/fonts/SalesforceSans-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/fonts/SalesforceSans-Light.ttf -------------------------------------------------------------------------------- /dist/assets/fonts/SalesforceSans-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/fonts/SalesforceSans-LightItalic.ttf -------------------------------------------------------------------------------- /dist/assets/fonts/SalesforceSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/fonts/SalesforceSans-Regular.ttf -------------------------------------------------------------------------------- /dist/assets/fonts/SalesforceSans-Semibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/fonts/SalesforceSans-Semibold.ttf -------------------------------------------------------------------------------- /dist/assets/fonts/SalesforceSans-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/fonts/SalesforceSans-Thin.ttf -------------------------------------------------------------------------------- /dist/assets/fonts/SalesforceSans-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/fonts/SalesforceSans-ThinItalic.ttf -------------------------------------------------------------------------------- /dist/assets/fonts/webfonts/SalesforceSans-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/fonts/webfonts/SalesforceSans-Bold.woff -------------------------------------------------------------------------------- /dist/assets/fonts/webfonts/SalesforceSans-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/fonts/webfonts/SalesforceSans-Bold.woff2 -------------------------------------------------------------------------------- /dist/assets/fonts/webfonts/SalesforceSans-BoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/fonts/webfonts/SalesforceSans-BoldItalic.woff -------------------------------------------------------------------------------- /dist/assets/fonts/webfonts/SalesforceSans-BoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/fonts/webfonts/SalesforceSans-BoldItalic.woff2 -------------------------------------------------------------------------------- /dist/assets/fonts/webfonts/SalesforceSans-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/fonts/webfonts/SalesforceSans-Italic.woff -------------------------------------------------------------------------------- /dist/assets/fonts/webfonts/SalesforceSans-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/fonts/webfonts/SalesforceSans-Italic.woff2 -------------------------------------------------------------------------------- /dist/assets/fonts/webfonts/SalesforceSans-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/fonts/webfonts/SalesforceSans-Light.woff -------------------------------------------------------------------------------- /dist/assets/fonts/webfonts/SalesforceSans-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/fonts/webfonts/SalesforceSans-Light.woff2 -------------------------------------------------------------------------------- /dist/assets/fonts/webfonts/SalesforceSans-LightItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/fonts/webfonts/SalesforceSans-LightItalic.woff -------------------------------------------------------------------------------- /dist/assets/fonts/webfonts/SalesforceSans-LightItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/fonts/webfonts/SalesforceSans-LightItalic.woff2 -------------------------------------------------------------------------------- /dist/assets/fonts/webfonts/SalesforceSans-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/fonts/webfonts/SalesforceSans-Regular.woff -------------------------------------------------------------------------------- /dist/assets/fonts/webfonts/SalesforceSans-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/fonts/webfonts/SalesforceSans-Regular.woff2 -------------------------------------------------------------------------------- /dist/assets/fonts/webfonts/SalesforceSans-Thin.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/fonts/webfonts/SalesforceSans-Thin.woff -------------------------------------------------------------------------------- /dist/assets/fonts/webfonts/SalesforceSans-Thin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/fonts/webfonts/SalesforceSans-Thin.woff2 -------------------------------------------------------------------------------- /dist/assets/fonts/webfonts/SalesforceSans-ThinItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/fonts/webfonts/SalesforceSans-ThinItalic.woff -------------------------------------------------------------------------------- /dist/assets/fonts/webfonts/SalesforceSans-ThinItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/fonts/webfonts/SalesforceSans-ThinItalic.woff2 -------------------------------------------------------------------------------- /dist/assets/images/DeployToSFDX.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 3 Copy 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /dist/assets/images/launch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/images/launch.png -------------------------------------------------------------------------------- /dist/assets/images/loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/images/loader.gif -------------------------------------------------------------------------------- /dist/assets/images/salesforce_cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/dist/assets/images/salesforce_cloud.png -------------------------------------------------------------------------------- /lib/apis.js: -------------------------------------------------------------------------------- 1 | const commands = require('./commands.js'); 2 | const postgresHelper = require('./postgres.js'); 3 | 4 | module.exports = function (router) { 5 | 6 | router.get('/test', (req, res) => { 7 | 8 | const script = 'jq --help'; 9 | 10 | commands.run('test', script, (result) => { 11 | res.json({ 12 | message: result 13 | }); 14 | }); 15 | }); 16 | 17 | router.post('/status', (req, res) => { 18 | 19 | const guid = req.body.guid; 20 | 21 | postgresHelper.getDeploymentStatus(guid) 22 | .then((result) => { 23 | 24 | const rows = result.rows; 25 | 26 | let resultMessage = ''; 27 | let complete = false; 28 | let scratch_url; 29 | let stage = ''; 30 | let error_message = ''; 31 | 32 | let count = 1; 33 | 34 | rows.forEach((row) => { 35 | const message = row.message; 36 | 37 | complete = row.complete; 38 | scratch_url = row.scratch_url; 39 | stage = row.stage; 40 | error_message = row.error_message; 41 | 42 | if (stage === 'error') { 43 | complete = true; 44 | } 45 | 46 | resultMessage = `${count}) ${message}
${resultMessage}`; 47 | 48 | count++; 49 | }); 50 | 51 | res.json({ 52 | message: resultMessage, 53 | complete, 54 | scratch_url, 55 | stage, 56 | error_message 57 | }); 58 | }); 59 | }); 60 | 61 | router.post('/deploy', (req, res) => { 62 | 63 | const settings = req.body; 64 | 65 | // add additional info to the settings 66 | settings.access_token = req.cookies.access_token; 67 | settings.instance_url = req.cookies.instance_url; 68 | settings.user_name = req.cookies.user_name; 69 | 70 | // create deployment 71 | const insertQuery = `INSERT INTO deployments (guid, username, repo, settings) VALUES ('${settings.guid}', '${settings.user_name}', '${settings.githubRepo}', '${JSON.stringify(settings)}')`; 72 | postgresHelper.insertDeployment(insertQuery).then(() => { 73 | res.json({ 74 | message: settings.guid 75 | }); 76 | }); 77 | }); 78 | 79 | }; -------------------------------------------------------------------------------- /lib/commands.js: -------------------------------------------------------------------------------- 1 | const { 2 | exec 3 | } = require('child_process'); 4 | 5 | exports.run = (command, commandScript) => { 6 | 7 | return new Promise((resolve) => { 8 | 9 | exec(commandScript, (err, stdout, stderr) => { 10 | if (stderr && err) { 11 | console.error('run:err', command, commandScript, stdout); 12 | resolve(null, stderr.replace(/\r?\n|\r/, '').trim()); 13 | } 14 | 15 | resolve(stdout.replace(/\r?\n|\r/, '').trim(), null); 16 | }); 17 | }); 18 | }; -------------------------------------------------------------------------------- /lib/postgres.js: -------------------------------------------------------------------------------- 1 | const Pool = require('pg-pool'); 2 | const dbUrl = require('url'); 3 | 4 | const dbParams = dbUrl.parse(process.env.DATABASE_URL); 5 | const auth = dbParams.auth.split(':'); 6 | 7 | const config = { 8 | host: dbParams.hostname, 9 | port: dbParams.port, 10 | user: auth[0], 11 | ssl: true, 12 | password: auth[1], 13 | database: dbParams.pathname.split('/')[1], 14 | idleTimeoutMillis: 1000, 15 | max: 10 16 | }; 17 | 18 | const pool = new Pool(config); 19 | 20 | exports.getDeploymentCount = () => new Promise((resolve) => { 21 | 22 | const selectQuery = 'SELECT count(guid) FROM deployments WHERE complete = true;'; 23 | 24 | pool.query(selectQuery, (queryErr, result) => { 25 | if (queryErr) { 26 | resolve(queryErr); 27 | } 28 | resolve(result); 29 | }); 30 | }); 31 | 32 | exports.getNewDeployment = () => new Promise((resolve, reject) => { 33 | 34 | const selectQuery = "SELECT TOP 1 guid, username, repo, settings FROM deployments WHERE stage = 'init'"; 35 | 36 | pool.query(selectQuery, (queryErr, result) => { 37 | if (queryErr) { 38 | reject(queryErr); 39 | } 40 | resolve(result); 41 | }); 42 | }); 43 | 44 | exports.insertDeploymentStep = (guid, stage, message) => new Promise((resolve, reject) => { 45 | 46 | const insertQuery = `INSERT INTO deployment_steps (guid, stage, message) VALUES ('${guid}', '${stage}', '${message}')`; 47 | 48 | pool.query(insertQuery, (insertErr) => { 49 | if (insertErr) { 50 | console.error('insertDeploymentStep', insertErr); 51 | reject(insertErr); 52 | } 53 | resolve(); 54 | }); 55 | }); 56 | 57 | 58 | exports.insertDeployment = insertQuery => new Promise((resolve) => { 59 | 60 | pool.query(insertQuery, (insertErr) => { 61 | if (insertErr) { 62 | console.error('insertDeployment', insertErr); 63 | resolve(insertErr); 64 | } 65 | resolve(); 66 | }); 67 | }); 68 | 69 | exports.updateDeploymentStatus = (guid, stage) => new Promise((resolve) => { 70 | 71 | const selectQuery = `UPDATE deployments SET stage = '${stage}' WHERE guid = '${guid}'`; 72 | 73 | pool.query(selectQuery, (queryErr, result) => { 74 | if (queryErr) { 75 | console.error('updateDeploymentStatus', queryErr); 76 | resolve(queryErr); 77 | } 78 | resolve(result); 79 | }); 80 | }); 81 | 82 | exports.getDeploymentStatus = guid => new Promise((resolve) => { 83 | 84 | const selectQuery = `SELECT deployment_steps.message, deployment_steps.created_at, deployments.complete, deployments.stage, deployments.error_message, deployments.scratch_url FROM deployment_steps INNER JOIN deployments ON deployments.guid = deployment_steps.guid WHERE deployment_steps.guid = '${guid}' ORDER BY created_at;`; 85 | 86 | pool.query(selectQuery, (queryErr, result) => { 87 | if (queryErr) { 88 | console.error('getDeploymentStatus', queryErr); 89 | resolve(queryErr); 90 | } 91 | resolve(result); 92 | }); 93 | }); 94 | 95 | exports.getChoices = () => new Promise((resolve) => { 96 | const selectQuery = 'SELECT count(*) as depl, repo FROM deployments WHERE complete = true GROUP BY repo ORDER BY depl DESC'; 97 | 98 | pool.query(selectQuery, (queryErr, result) => { 99 | if (queryErr) { 100 | console.error('getChoices', queryErr); 101 | resolve(queryErr); 102 | } 103 | resolve(result); 104 | }); 105 | }); -------------------------------------------------------------------------------- /lib/steps.js: -------------------------------------------------------------------------------- 1 | // const commands = require('./commands.js'); 2 | const postgresHelper = require('./postgres.js'); 3 | const exec = require('child-process-promise').exec; 4 | // const exec = require('child_process').exec; 5 | 6 | function run(command, commandScript) { 7 | 8 | return new Promise((resolve) => { 9 | 10 | exec(commandScript, (error, stdout, stderr) => { 11 | 12 | if (stderr && error) { 13 | resolve(null, stderr.replace(/\r?\n|\r/, '').trim()); 14 | } else { 15 | resolve(stdout.replace(/\r?\n|\r/, '').trim(), null); 16 | } 17 | 18 | }); 19 | }); 20 | } 21 | 22 | function cloneMessage(result, githubRepo) { 23 | 24 | let message = ''; 25 | const stderr = result.stderr; 26 | 27 | if (stderr) { 28 | message = `Error: ${stderr}.`; 29 | } else { 30 | message = `Successfully cloned ${githubRepo}.`; 31 | } 32 | 33 | return message; 34 | } 35 | 36 | function createMessage(result) { 37 | 38 | let message = ''; 39 | const stdout = result.stdout; 40 | const stderr = result.stderr; 41 | 42 | if (stderr) { 43 | message = `Error: ${stderr}.`; 44 | } else { 45 | message = `${stdout}.`; 46 | } 47 | 48 | return message; 49 | } 50 | 51 | exports.clone = (guid, script, githubRepo) => { 52 | 53 | const stage = 'clone'; 54 | 55 | return postgresHelper.updateDeploymentStatus(guid, stage) 56 | .then(exec(script)) 57 | .then((result) => { 58 | return cloneMessage(result, githubRepo); 59 | }) 60 | .then((message) => { 61 | return postgresHelper.insertDeploymentStep(guid, stage, message) 62 | .then(() => (message)); 63 | }) 64 | .catch(() => { 65 | 66 | }); 67 | }; 68 | 69 | exports.create = (guid, script) => { 70 | 71 | const stage = 'create'; 72 | 73 | return postgresHelper.updateDeploymentStatus(guid, stage) 74 | .then(exec(script)) 75 | .then((result) => { 76 | return createMessage(result); 77 | }) 78 | .then((message) => { 79 | return postgresHelper.insertDeploymentStep(guid, stage, message) 80 | .then(() => (message)); 81 | }) 82 | .catch(() => { 83 | 84 | }); 85 | }; 86 | 87 | exports.create2 = () => { 88 | 89 | return new Promise((resolve) => { 90 | resolve('result3'); 91 | }); 92 | 93 | }; -------------------------------------------------------------------------------- /lib/web.js: -------------------------------------------------------------------------------- 1 | const oauth2 = require('salesforce-oauth2'); 2 | const jsforce = require('jsforce'); 3 | const Guid = require('guid'); 4 | const postgresHelper = require('../lib/postgres'); 5 | 6 | const callbackUrl = process.env.CALLBACKURL; 7 | const consumerKey = process.env.CONSUMERKEY; 8 | const consumerSecret = process.env.CONSUMERSECRET; 9 | 10 | module.exports = function (app) { 11 | 12 | app.get('*', (req, res, next) => { 13 | if (process.env.NODE_ENV === 'production' && req.headers['x-forwarded-proto'] !== 'https') { 14 | res.redirect(`https://deploy-to-sfdx.com${req.url}`); 15 | } 16 | 17 | return next(); 18 | }); 19 | 20 | app.get('/', (req, res) => { 21 | postgresHelper.getDeploymentCount().then((deploymentCountResult) => { 22 | 23 | const deploymentCount = parseInt(deploymentCountResult.rows[0].count); 24 | const template = req.query.template; 25 | const referrer = req.headers.referer; 26 | 27 | res.render('pages/index', { 28 | deploymentCount, 29 | template: template ? template : referrer && referrer.startsWith('https://github.com') ? referrer : null 30 | }); 31 | }); 32 | }); 33 | 34 | app.get('/about', (req, res) => { 35 | res.render('pages/about'); 36 | }); 37 | 38 | app.get('/error', (req, res) => { 39 | res.render('pages/error'); 40 | }); 41 | 42 | app.get('/notdevhub', (req, res) => { 43 | const template = req.query.template; 44 | const user_name = req.cookies.user_name; 45 | 46 | res.render('pages/notdevhub', { 47 | template, 48 | user_name 49 | }); 50 | }); 51 | 52 | app.get('/choose', (req, res) => { 53 | const user_name = req.cookies.user_name; 54 | const guid = Guid.raw(); 55 | 56 | postgresHelper.getChoices() 57 | .then((result) => { 58 | 59 | res.render('pages/choose', { 60 | user_name, 61 | guid, 62 | rows: result.rows 63 | }); 64 | }); 65 | }); 66 | 67 | app.get('/repo', (req, res) => { 68 | res.render('pages/repo', {}); 69 | }); 70 | 71 | app.get('/deploy', (req, res) => { 72 | 73 | const template = req.query.template; 74 | const access_token = req.cookies.access_token; 75 | const instance_url = req.cookies.instance_url; 76 | const user_name = req.cookies.user_name; 77 | const guid = Guid.raw(); 78 | 79 | if (access_token && instance_url) { 80 | res.render('pages/deploy', { 81 | template, 82 | access_token, 83 | instance_url, 84 | user_name, 85 | guid 86 | }); 87 | } else { 88 | if (template) { 89 | return res.redirect(`/login?template=${template}`); 90 | } else { 91 | return res.redirect('/login'); 92 | } 93 | } 94 | }); 95 | 96 | app.get('/deploying', (req, res) => { 97 | const template = req.query.template; 98 | const guid = req.query.guid; 99 | 100 | res.render('pages/deploying', { 101 | template, 102 | guid 103 | }); 104 | }); 105 | 106 | app.get('/login', (req, res) => { 107 | const template = req.query.template; 108 | 109 | const uri = oauth2.getAuthorizationUrl({ 110 | redirect_uri: callbackUrl, 111 | client_id: consumerKey, 112 | scope: 'id api openid', 113 | state: template, 114 | prompt: 'select_account' 115 | }); 116 | 117 | return res.redirect(uri); 118 | }); 119 | 120 | app.get('/logout', (req, res) => { 121 | const access_token = req.cookies.access_token; 122 | const instance_url = req.cookies.instance_url; 123 | 124 | const conn = new jsforce.Connection({ 125 | instanceUrl: instance_url, 126 | accessToken: access_token 127 | }); 128 | 129 | conn.logout((err) => { 130 | if (err) { 131 | return console.error(err); 132 | } 133 | res.clearCookie('access_token'); 134 | res.clearCookie('instance_url'); 135 | 136 | return res.redirect('/'); 137 | }); 138 | }); 139 | 140 | app.get('/oauth/callback', (req, res) => { 141 | const authorizationCode = req.param('code'); 142 | const template = req.param('state'); 143 | 144 | oauth2.authenticate({ 145 | redirect_uri: callbackUrl, 146 | client_id: consumerKey, 147 | client_secret: consumerSecret, 148 | code: authorizationCode 149 | }, (error, payload) => { 150 | 151 | try { 152 | 153 | res.cookie('access_token', payload.access_token); 154 | res.cookie('instance_url', payload.instance_url); 155 | 156 | } catch (tokenErr) { 157 | console.error('payload.access_token undefined', tokenErr); 158 | 159 | return res.redirect('/error'); 160 | } 161 | 162 | // check to see if org is a dev hub 163 | const conn = new jsforce.Connection({ 164 | instanceUrl: payload.instance_url, 165 | accessToken: payload.access_token 166 | }); 167 | 168 | conn.identity((err, identity) => { 169 | if (err) { 170 | return console.error(err); 171 | } 172 | 173 | res.cookie('user_name', identity.username); 174 | 175 | conn.tooling.query("SELECT DurableId, SettingValue FROM OrganizationSettingsDetail WHERE SettingName = 'ScratchOrgManagementPref'", (devHubErr, result) => { 176 | if (devHubErr) { 177 | return console.error(devHubErr); 178 | } 179 | 180 | if (result.size > 0) { 181 | const devHubEnabled = result.records[0].SettingValue; 182 | 183 | if (devHubEnabled === true) { 184 | if (template) { 185 | return res.redirect(`/deploy?template=${template}`); 186 | } else { 187 | return res.redirect('/choose'); 188 | } 189 | } else { 190 | return res.redirect(template ? `/notdevhub?template=${template}` : '/notdevhub'); 191 | } 192 | } else { 193 | return res.redirect(template ? `/notdevhub?template=${template}` : '/notdevhub'); 194 | } 195 | }); 196 | }); 197 | }); 198 | }); 199 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "deploy-to-sfdx", 3 | "version": "0.0.1", 4 | "description": "An application that will deploy an SFDX project from a public Github repo", 5 | "main": "web.js", 6 | "scripts": { 7 | "start": "node .", 8 | "css-build": "mkdirp dist/assets/css && mkdirp dist/assets/fonts && ncp src/assets dist/assets && ncp src/assets/favicons dist/assets/favicons && node-sass src/scss -o dist/assets/css && ncp node_modules/@salesforce-ux/design-system/assets/fonts dist/assets/fonts", 9 | "css-watch": "node-sass --watch src/scss -o dist/assets/css" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+ssh://git@github.com/wadewegner/deploy-to-sfdx.git" 14 | }, 15 | "author": "Wade Wegner", 16 | "license": "Apache-2.0", 17 | "bugs": { 18 | "url": "https://github.com/wadewegner/deploy-to-sfdx/issues" 19 | }, 20 | "homepage": "https://github.com/wadewegner/deploy-to-sfdx#readme", 21 | "dependencies": { 22 | "@salesforce-ux/design-system": "^2.3.1", 23 | "amqplib": "^0.5.1", 24 | "async": "^2.5.0", 25 | "bluebird": "^3.5.1", 26 | "body-parser": "^1.17.2", 27 | "child-process-promise": "^2.2.1", 28 | "child_process": "^1.0.2", 29 | "cookie-parser": "^1.4.3", 30 | "ejs": "^2.5.6", 31 | "express": "^4.15.3", 32 | "guid": "0.0.12", 33 | "https": "^1.0.0", 34 | "jsforce": "^1.8.0", 35 | "pg": "^7.1.2", 36 | "pg-escape": "^0.2.0", 37 | "pg-pool": "^2.0.3", 38 | "pg-promise": "^7.0.3", 39 | "salesforce-oauth2": "^0.1.8", 40 | "sleep": "^5.1.1", 41 | "yamljs": "^0.3.0" 42 | }, 43 | "devDependencies": { 44 | "chai": "^3.5.0", 45 | "eslint": "^3.17.1", 46 | "eslint-config-airbnb-base": "^11.1.1", 47 | "eslint-plugin-import": "^2.2.0", 48 | "mkdirp": "^0.5.1", 49 | "mocha": "^3.2.0", 50 | "ncp": "^2.0.0", 51 | "node-sass": "^4.5.3" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /scripts/choose.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function () { 2 | 3 | $('#yes').click(function () { 4 | 5 | var template = $('input[name=options]:checked').val(); 6 | var guid = $('input#guid').val(); 7 | 8 | window.location.href = '/deploying?template=' + template + '&guid=' + guid; 9 | 10 | }); 11 | 12 | $('#no').click(function () { 13 | window.location.href = '/'; 14 | }); 15 | }); -------------------------------------------------------------------------------- /scripts/deploy.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function () { 2 | 3 | $("#yes").click(function () { 4 | var template = $('input#template').val(); 5 | var guid = $('input#guid').val(); 6 | 7 | window.location.href = '/deploying?template=' + template + '&guid=' + guid; 8 | }); 9 | 10 | $("#no").click(function () { 11 | window.location.href = "/"; 12 | }); 13 | }); -------------------------------------------------------------------------------- /scripts/deploying.js: -------------------------------------------------------------------------------- 1 | $(document).ready(() => { 2 | 3 | function update_status(newMessage) { 4 | 5 | let message = ''; 6 | newMessage = newMessage.replace(/^\s+|\s+$/g, ''); 7 | message = `${newMessage}
${message}`; 8 | 9 | $('#status').text(message); 10 | $('#status').html($('#status').text()); 11 | } 12 | 13 | function poll(guid) { 14 | 15 | var complete = false; 16 | var data = {}; 17 | data.guid = guid; 18 | 19 | $.ajax({ 20 | url: '/api/status', 21 | type: 'POST', 22 | data: data, 23 | success: function (response) { 24 | 25 | var message = response.message; 26 | var scratch_url = response.scratch_url; 27 | var stage = response.stage; 28 | var error_message = response.error_message; 29 | 30 | complete = response.complete; 31 | 32 | if (stage === 'error') { 33 | message = `${error_message}


${message}`; 34 | } 35 | 36 | update_status(message); 37 | 38 | if (complete && stage === 'error') { 39 | $('div#loaderBlock').hide(); 40 | $('#errorBlock').show(); 41 | } 42 | 43 | if (complete && stage !== 'error') { 44 | $('#loginUrl').attr('href', scratch_url); 45 | $('#loginUrl').text(`${scratch_url.substring(0, 80)}...`); 46 | $('#loginBlock').show(); 47 | $('div#loaderBlock').hide(); 48 | } 49 | }, 50 | dataType: 'json', 51 | complete: setTimeout(function () { 52 | if (!complete) { 53 | poll(guid); 54 | } 55 | }, 2500), 56 | timeout: 2000 57 | }); 58 | } 59 | 60 | function createJob(settings) { 61 | 62 | $.ajax({ 63 | type: 'POST', 64 | url: '/api/deploy', 65 | data: JSON.stringify(settings), 66 | contentType: 'application/json; charset=utf-8', 67 | dataType: 'json', 68 | async: false, 69 | success: () => { 70 | // update_status(`Started job: ${settings.guid}`); 71 | }, 72 | error: (commandDataResponse) => { 73 | update_status(`Sorry, something went wrong. Please log an issue on github: https://github.com/wadewegner/deploy-to-sfdx/issues.\n\nError: ${commandDataResponse.responseText}\n`); 74 | $('div#loaderBlock').hide(); 75 | } 76 | }); 77 | } 78 | 79 | const githubRepo = $('input#template').val(); 80 | const guid = $('input#guid').val(); 81 | 82 | let yamlFile = githubRepo.replace('github.com', 'raw.githubusercontent.com'); 83 | yamlFile += '/master/.salesforcedx.yaml'; 84 | 85 | const settings = {}; 86 | settings.githubRepo = githubRepo; 87 | settings.guid = guid; 88 | settings.dataPlans = []; 89 | 90 | $.ajax({ 91 | url: yamlFile, 92 | type: 'GET', 93 | async: false, 94 | error: () => { 95 | 96 | settings.yamlExists = false; 97 | settings.sfdxSource = true; 98 | settings.sourceFolder = ''; 99 | settings.assignPermset = 'false'; 100 | settings.permsetName = ''; 101 | settings.deleteScratchOrg = 'false'; 102 | settings.runApexTests = 'false'; 103 | settings.scratchOrgDef = 'config/project-scratch-def.json'; 104 | settings.showScratchOrgUrl = 'true'; 105 | settings.openPath = ''; 106 | 107 | }, 108 | success: (yamlFileDataResponse) => { 109 | 110 | const doc = jsyaml.load(yamlFileDataResponse); 111 | 112 | settings.yamlExists = true; 113 | if (doc['sfdx-source']) { 114 | settings.sfdxSource = doc['sfdx-source']; 115 | } else { 116 | settings.sfdxSource = true; 117 | } 118 | if (!settings.sfdxSource) { 119 | settings.sourceFolder = doc['source-folder']; 120 | } 121 | settings.assignPermset = doc['assign-permset']; 122 | settings.permsetName = doc['permset-name']; 123 | settings.deleteScratchOrg = doc['delete-scratch-org']; 124 | settings.runApexTests = doc['run-apex-tests']; 125 | settings.scratchOrgDef = doc['scratch-org-def']; 126 | settings.showScratchOrgUrl = doc['show-scratch-org-url']; 127 | settings.openPath = doc['open-path']; 128 | 129 | const dataPlanCount = doc['data-plans'] ? doc['data-plans'].length : 0; 130 | 131 | if (dataPlanCount > 0) { 132 | 133 | for (var i = 0; i < dataPlanCount; i++) { 134 | const dataPlan = doc['data-plans'][i]; 135 | settings.dataPlans.push(dataPlan); 136 | } 137 | } 138 | } 139 | }); 140 | 141 | createJob(settings); 142 | 143 | poll(guid); 144 | 145 | }); -------------------------------------------------------------------------------- /scripts/js-yaml.min.js: -------------------------------------------------------------------------------- 1 | !function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).jsyaml=e()}}(function(){return function e(t,n,i){function r(a,s){if(!n[a]){if(!t[a]){var c="function"==typeof require&&require;if(!s&&c)return c(a,!0);if(o)return o(a,!0);var u=new Error("Cannot find module '"+a+"'");throw u.code="MODULE_NOT_FOUND",u}var l=n[a]={exports:{}};t[a][0].call(l.exports,function(e){var n=t[a][1][e];return r(n||e)},l,l.exports,e,t,n,i)}return n[a].exports}for(var o="function"==typeof require&&require,a=0;ai&&" "!==e[h+1],h=o);else if(!l(a))return ue;m=m&&p(a)}c=c||d&&o-h-1>i&&" "!==e[h+1]}return s||c?" "===e[0]&&n>9?ue:c?ce:se:m&&!r(e)?oe:ae}function h(e,t,n,i){e.dump=function(){if(0===t.length)return"''";if(!e.noCompatMode&&-1!==re.indexOf(t))return"'"+t+"'";var r=e.indent*Math.max(1,n),o=-1===e.lineWidth?-1:Math.max(Math.min(e.lineWidth,40),e.lineWidth-r),s=i||e.flowLevel>-1&&n>=e.flowLevel;switch(d(t,s,e.indent,o,function(t){return c(e,t)})){case oe:return t;case ae:return"'"+t.replace(/'/g,"''")+"'";case se:return"|"+m(t,e.indent)+g(a(t,r));case ce:return">"+m(t,e.indent)+g(a(y(t,o),r));case ue:return'"'+v(t)+'"';default:throw new F("impossible error: invalid scalar style")}}()}function m(e,t){var n=" "===e[0]?String(t):"",i="\n"===e[e.length-1];return n+(i&&("\n"===e[e.length-2]||"\n"===e)?"+":i?"":"-")+"\n"}function g(e){return"\n"===e[e.length-1]?e.slice(0,-1):e}function y(e,t){for(var n,i,r=/(\n+)([^\n]*)/g,o=function(){var n=e.indexOf("\n");return n=-1!==n?n:e.length,r.lastIndex=n,x(e.slice(0,n),t)}(),a="\n"===e[0]||" "===e[0];i=r.exec(e);){var s=i[1],c=i[2];n=" "===c[0],o+=s+(a||n||""===c?"":"\n")+x(c,t),a=n}return o}function x(e,t){if(""===e||" "===e[0])return e;for(var n,i,r=/ [^ ]/g,o=0,a=0,s=0,c="";n=r.exec(e);)(s=n.index)-o>t&&(i=a>o?a:s,c+="\n"+e.slice(o,i),o=i+1),a=s;return c+="\n",e.length-o>t&&a>o?c+=e.slice(o,a)+"\n"+e.slice(a+1):c+=e.slice(o),c.slice(1)}function v(e){for(var t,n,i="",o=0;o1024&&(s+="? "),s+=e.dump+":"+(e.condenseFlow?"":" "),j(e,t,a,!1,!1)&&(c+=s+=e.dump));e.tag=u,e.dump="{"+c+"}"}function C(e,t,n,i){var r,o,a,c,u,l,p="",f=e.tag,d=Object.keys(n);if(!0===e.sortKeys)d.sort();else if("function"==typeof e.sortKeys)d.sort(e.sortKeys);else if(e.sortKeys)throw new F("sortKeys must be a boolean or a function");for(r=0,o=d.length;r1024)&&(e.dump&&D===e.dump.charCodeAt(0)?l+="?":l+="? "),l+=e.dump,u&&(l+=s(e,t)),j(e,t+1,c,!0,u)&&(e.dump&&D===e.dump.charCodeAt(0)?l+=":":l+=": ",p+=l+=e.dump));e.tag=f,e.dump=p||"{}"}function k(e,t,n){var i,r,o,a,s,c;for(o=0,a=(r=n?e.explicitTypes:e.implicitTypes).length;o tag resolver accepts not "'+c+'" style');i=s.represent[c](t,c)}e.dump=i}return!0}return!1}function j(e,t,n,i,r,o){e.tag=null,e.dump=n,k(e,n,!1)||k(e,n,!0);var a=M.call(e.dump);i&&(i=e.flowLevel<0||e.flowLevel>t);var s,c,u="[object Object]"===a||"[object Array]"===a;if(u&&(c=-1!==(s=e.duplicates.indexOf(n))),(null!==e.tag&&"?"!==e.tag||c||2!==e.indent&&t>0)&&(r=!1),c&&e.usedDuplicates[s])e.dump="*ref_"+s;else{if(u&&c&&!e.usedDuplicates[s]&&(e.usedDuplicates[s]=!0),"[object Object]"===a)i&&0!==Object.keys(e.dump).length?(C(e,t,e.dump,r),c&&(e.dump="&ref_"+s+e.dump)):(w(e,t,e.dump),c&&(e.dump="&ref_"+s+" "+e.dump));else if("[object Array]"===a)i&&0!==e.dump.length?(b(e,t,e.dump,r),c&&(e.dump="&ref_"+s+e.dump)):(A(e,t,e.dump),c&&(e.dump="&ref_"+s+" "+e.dump));else{if("[object String]"!==a){if(e.skipInvalid)return!1;throw new F("unacceptable kind of an object to dump "+a)}"?"!==e.tag&&h(e,e.dump,t,o)}null!==e.tag&&"?"!==e.tag&&(e.dump="!<"+e.tag+"> "+e.dump)}return!0}function I(e,t){var n,i,r=[],o=[];for(S(e,r,o),n=0,i=o.length;n>10),56320+(e-65536&1023))}function f(e,t){this.input=e,this.filename=t.filename||null,this.schema=t.schema||W,this.onWarning=t.onWarning||null,this.legacy=t.legacy||!1,this.json=t.json||!1,this.listener=t.listener||null,this.implicitTypes=this.schema.compiledImplicit,this.typeMap=this.schema.compiledTypeMap,this.length=e.length,this.position=0,this.line=0,this.lineStart=0,this.lineIndent=0,this.documents=[]}function d(e,t){return new Y(t,new R(e.filename,e.input,e.position,e.line,e.position-e.lineStart))}function h(e,t){throw d(e,t)}function m(e,t){e.onWarning&&e.onWarning.call(null,d(e,t))}function g(e,t,n,i){var r,o,a,s;if(t1&&(e.result+=q.repeat("\n",t-1))}function C(e,t,n){var s,c,u,l,p,f,d,h,m,y=e.kind,x=e.result;if(m=e.input.charCodeAt(e.position),o(m)||a(m)||35===m||38===m||42===m||33===m||124===m||62===m||39===m||34===m||37===m||64===m||96===m)return!1;if((63===m||45===m)&&(c=e.input.charCodeAt(e.position+1),o(c)||n&&a(c)))return!1;for(e.kind="scalar",e.result="",u=l=e.position,p=!1;0!==m;){if(58===m){if(c=e.input.charCodeAt(e.position+1),o(c)||n&&a(c))break}else if(35===m){if(s=e.input.charCodeAt(e.position-1),o(s))break}else{if(e.position===e.lineStart&&b(e)||n&&a(m))break;if(i(m)){if(f=e.line,d=e.lineStart,h=e.lineIndent,A(e,!1,-1),e.lineIndent>=t){p=!0,m=e.input.charCodeAt(e.position);continue}e.position=l,e.line=f,e.lineStart=d,e.lineIndent=h;break}}p&&(g(e,u,l,!1),w(e,e.line-f),u=l=e.position,p=!1),r(m)||(l=e.position+1),m=e.input.charCodeAt(++e.position)}return g(e,u,l,!1),!!e.result||(e.kind=y,e.result=x,!1)}function k(e,t){var n,r,o;if(39!==(n=e.input.charCodeAt(e.position)))return!1;for(e.kind="scalar",e.result="",e.position++,r=o=e.position;0!==(n=e.input.charCodeAt(e.position));)if(39===n){if(g(e,r,e.position,!0),39!==(n=e.input.charCodeAt(++e.position)))return!0;r=e.position,e.position++,o=e.position}else i(n)?(g(e,r,o,!0),w(e,A(e,!1,t)),r=o=e.position):e.position===e.lineStart&&b(e)?h(e,"unexpected end of the document within a single quoted scalar"):(e.position++,o=e.position);h(e,"unexpected end of the stream within a single quoted scalar")}function j(e,t){var n,r,o,a,u,l;if(34!==(l=e.input.charCodeAt(e.position)))return!1;for(e.kind="scalar",e.result="",e.position++,n=r=e.position;0!==(l=e.input.charCodeAt(e.position));){if(34===l)return g(e,n,e.position,!0),e.position++,!0;if(92===l){if(g(e,n,e.position,!0),l=e.input.charCodeAt(++e.position),i(l))A(e,!1,t);else if(l<256&&ne[l])e.result+=ie[l],e.position++;else if((u=c(l))>0){for(o=u,a=0;o>0;o--)(u=s(l=e.input.charCodeAt(++e.position)))>=0?a=(a<<4)+u:h(e,"expected hexadecimal character");e.result+=p(a),e.position++}else h(e,"unknown escape sequence");n=r=e.position}else i(l)?(g(e,n,r,!0),w(e,A(e,!1,t)),n=r=e.position):e.position===e.lineStart&&b(e)?h(e,"unexpected end of the document within a double quoted scalar"):(e.position++,r=e.position)}h(e,"unexpected end of the stream within a double quoted scalar")}function I(e,t){var n,i,r,a,s,c,u,l,p,f,d=!0,m=e.tag,g=e.anchor,y={};if(91===(f=e.input.charCodeAt(e.position)))r=93,c=!1,i=[];else{if(123!==f)return!1;r=125,c=!0,i={}}for(null!==e.anchor&&(e.anchorMap[e.anchor]=i),f=e.input.charCodeAt(++e.position);0!==f;){if(A(e,!0,t),(f=e.input.charCodeAt(e.position))===r)return e.position++,e.tag=m,e.anchor=g,e.kind=c?"mapping":"sequence",e.result=i,!0;d||h(e,"missed comma between flow collection entries"),l=u=p=null,a=s=!1,63===f&&o(e.input.charCodeAt(e.position+1))&&(a=s=!0,e.position++,A(e,!0,t)),n=e.line,M(e,t,K,!1,!0),l=e.tag,u=e.result,A(e,!0,t),f=e.input.charCodeAt(e.position),!s&&e.line!==n||58!==f||(a=!0,f=e.input.charCodeAt(++e.position),A(e,!0,t),M(e,t,K,!1,!0),p=e.result),c?x(e,i,y,l,u,p):a?i.push(x(e,null,y,l,u,p)):i.push(u),A(e,!0,t),44===(f=e.input.charCodeAt(e.position))?(d=!0,f=e.input.charCodeAt(++e.position)):d=!1}h(e,"unexpected end of the stream within a flow collection")}function S(e,t){var n,o,a,s,c=V,l=!1,p=!1,f=t,d=0,m=!1;if(124===(s=e.input.charCodeAt(e.position)))o=!1;else{if(62!==s)return!1;o=!0}for(e.kind="scalar",e.result="";0!==s;)if(43===(s=e.input.charCodeAt(++e.position))||45===s)V===c?c=43===s?z:Z:h(e,"repeat of a chomping mode identifier");else{if(!((a=u(s))>=0))break;0===a?h(e,"bad explicit indentation width of a block scalar; it cannot be less than one"):p?h(e,"repeat of an indentation width identifier"):(f=t+a-1,p=!0)}if(r(s)){do{s=e.input.charCodeAt(++e.position)}while(r(s));if(35===s)do{s=e.input.charCodeAt(++e.position)}while(!i(s)&&0!==s)}for(;0!==s;){for(v(e),e.lineIndent=0,s=e.input.charCodeAt(e.position);(!p||e.lineIndentf&&(f=e.lineIndent),i(s))d++;else{if(e.lineIndentt)&&0!==r)h(e,"bad indentation of a sequence entry");else if(e.lineIndentt)&&(M(e,t,G,!0,a)&&(v?g=e.result:y=e.result),v||(x(e,f,d,m,g,y,s,c),m=g=y=null),A(e,!0,-1),u=e.input.charCodeAt(e.position)),e.lineIndent>t&&0!==u)h(e,"bad indentation of a mapping entry");else if(e.lineIndentt?d=1:e.lineIndent===t?d=0:e.lineIndentt?d=1:e.lineIndent===t?d=0:e.lineIndent tag; it should be "'+l.kind+'", not "'+e.kind+'"'),l.resolve(e.result)?(e.result=l.construct(e.result),null!==e.anchor&&(e.anchorMap[e.anchor]=e.result)):h(e,"cannot resolve a node with !<"+e.tag+"> explicit tag")):h(e,"unknown tag !<"+e.tag+">");return null!==e.listener&&e.listener("close",e),null!==e.tag||null!==e.anchor||g}function T(e){var t,n,a,s,c=e.position,u=!1;for(e.version=null,e.checkLineBreaks=e.legacy,e.tagMap={},e.anchorMap={};0!==(s=e.input.charCodeAt(e.position))&&(A(e,!0,-1),s=e.input.charCodeAt(e.position),!(e.lineIndent>0||37!==s));){for(u=!0,s=e.input.charCodeAt(++e.position),t=e.position;0!==s&&!o(s);)s=e.input.charCodeAt(++e.position);for(a=[],(n=e.input.slice(t,e.position)).length<1&&h(e,"directive name must not be less than one character in length");0!==s;){for(;r(s);)s=e.input.charCodeAt(++e.position);if(35===s){do{s=e.input.charCodeAt(++e.position)}while(0!==s&&!i(s));break}if(i(s))break;for(t=e.position;0!==s&&!o(s);)s=e.input.charCodeAt(++e.position);a.push(e.input.slice(t,e.position))}0!==s&&v(e),B.call(oe,n)?oe[n](e,n,a):m(e,'unknown document directive "'+n+'"')}A(e,!0,-1),0===e.lineIndent&&45===e.input.charCodeAt(e.position)&&45===e.input.charCodeAt(e.position+1)&&45===e.input.charCodeAt(e.position+2)?(e.position+=3,A(e,!0,-1)):u&&h(e,"directives end mark is expected"),M(e,e.lineIndent-1,G,!1,!0),A(e,!0,-1),e.checkLineBreaks&&Q.test(e.input.slice(c,e.position))&&m(e,"non-ASCII line breaks are interpreted as content"),e.documents.push(e.result),e.position===e.lineStart&&b(e)?46===e.input.charCodeAt(e.position)&&(e.position+=3,A(e,!0,-1)):e.position0&&-1==="\0\r\n…\u2028\u2029".indexOf(this.buffer.charAt(i-1));)if(i-=1,this.position-i>t/2-1){n=" ... ",i+=5;break}for(o="",a=this.position;at/2-1){o=" ... ",a-=5;break}return s=this.buffer.slice(i,a),r.repeat(" ",e)+n+s+o+"\n"+r.repeat(" ",e+this.position-i+n.length)+"^"},i.prototype.toString=function(e){var t,n="";return this.name&&(n+='in "'+this.name+'" '),n+="at line "+(this.line+1)+", column "+(this.column+1),e||(t=this.getSnippet())&&(n+=":\n"+t),n},t.exports=i},{"./common":2}],7:[function(e,t,n){"use strict";function i(e,t,n){var r=[];return e.include.forEach(function(e){n=i(e,t,n)}),e[t].forEach(function(e){n.forEach(function(t,n){t.tag===e.tag&&t.kind===e.kind&&r.push(n)}),n.push(e)}),n.filter(function(e,t){return-1===r.indexOf(t)})}function r(){var e,t,n={scalar:{},sequence:{},mapping:{},fallback:{}};for(e=0,t=arguments.length;e64)){if(t<0)return!1;i+=6}return i%8==0},construct:function(e){var t,n,r=e.replace(/[\r\n=]/g,""),a=r.length,s=o,c=0,u=[];for(t=0;t>16&255),u.push(c>>8&255),u.push(255&c)),c=c<<6|s.indexOf(r.charAt(t));return 0==(n=a%4*6)?(u.push(c>>16&255),u.push(c>>8&255),u.push(255&c)):18===n?(u.push(c>>10&255),u.push(c>>2&255)):12===n&&u.push(c>>4&255),i?i.from?i.from(u):new i(u):u},predicate:function(e){return i&&i.isBuffer(e)},represent:function(e){var t,n,i="",r=0,a=e.length,s=o;for(t=0;t>18&63],i+=s[r>>12&63],i+=s[r>>6&63],i+=s[63&r]),r=(r<<8)+e[t];return 0==(n=a%3)?(i+=s[r>>18&63],i+=s[r>>12&63],i+=s[r>>6&63],i+=s[63&r]):2===n?(i+=s[r>>10&63],i+=s[r>>4&63],i+=s[r<<2&63],i+=s[64]):1===n&&(i+=s[r>>2&63],i+=s[r<<4&63],i+=s[64],i+=s[64]),i}})},{"../type":13}],15:[function(e,t,n){"use strict";var i=e("../type");t.exports=new i("tag:yaml.org,2002:bool",{kind:"scalar",resolve:function(e){if(null===e)return!1;var t=e.length;return 4===t&&("true"===e||"True"===e||"TRUE"===e)||5===t&&("false"===e||"False"===e||"FALSE"===e)},construct:function(e){return"true"===e||"True"===e||"TRUE"===e},predicate:function(e){return"[object Boolean]"===Object.prototype.toString.call(e)},represent:{lowercase:function(e){return e?"true":"false"},uppercase:function(e){return e?"TRUE":"FALSE"},camelcase:function(e){return e?"True":"False"}},defaultStyle:"lowercase"})},{"../type":13}],16:[function(e,t,n){"use strict";var i=e("../common"),r=e("../type"),o=new RegExp("^(?:[-+]?(?:0|[1-9][0-9_]*)(?:\\.[0-9_]*)?(?:[eE][-+]?[0-9]+)?|\\.[0-9_]+(?:[eE][-+]?[0-9]+)?|[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\\.[0-9_]*|[-+]?\\.(?:inf|Inf|INF)|\\.(?:nan|NaN|NAN))$"),a=/^[-+]?[0-9]+e/;t.exports=new r("tag:yaml.org,2002:float",{kind:"scalar",resolve:function(e){return null!==e&&!(!o.test(e)||"_"===e[e.length-1])},construct:function(e){var t,n,i,r;return t=e.replace(/_/g,"").toLowerCase(),n="-"===t[0]?-1:1,r=[],"+-".indexOf(t[0])>=0&&(t=t.slice(1)),".inf"===t?1===n?Number.POSITIVE_INFINITY:Number.NEGATIVE_INFINITY:".nan"===t?NaN:t.indexOf(":")>=0?(t.split(":").forEach(function(e){r.unshift(parseFloat(e,10))}),t=0,i=1,r.forEach(function(e){t+=e*i,i*=60}),n*t):n*parseFloat(t,10)},predicate:function(e){return"[object Number]"===Object.prototype.toString.call(e)&&(e%1!=0||i.isNegativeZero(e))},represent:function(e,t){var n;if(isNaN(e))switch(t){case"lowercase":return".nan";case"uppercase":return".NAN";case"camelcase":return".NaN"}else if(Number.POSITIVE_INFINITY===e)switch(t){case"lowercase":return".inf";case"uppercase":return".INF";case"camelcase":return".Inf"}else if(Number.NEGATIVE_INFINITY===e)switch(t){case"lowercase":return"-.inf";case"uppercase":return"-.INF";case"camelcase":return"-.Inf"}else if(i.isNegativeZero(e))return"-0.0";return n=e.toString(10),a.test(n)?n.replace("e",".e"):n},defaultStyle:"lowercase"})},{"../common":2,"../type":13}],17:[function(e,t,n){"use strict";function i(e){return 48<=e&&e<=57||65<=e&&e<=70||97<=e&&e<=102}function r(e){return 48<=e&&e<=55}function o(e){return 48<=e&&e<=57}var a=e("../common"),s=e("../type");t.exports=new s("tag:yaml.org,2002:int",{kind:"scalar",resolve:function(e){if(null===e)return!1;var t,n=e.length,a=0,s=!1;if(!n)return!1;if("-"!==(t=e[a])&&"+"!==t||(t=e[++a]),"0"===t){if(a+1===n)return!0;if("b"===(t=e[++a])){for(a++;a3)return!1;if("/"!==t[t.length-i.length-1])return!1}return!0},construct:function(e){var t=e,n=/\/([gim]*)$/.exec(e),i="";return"/"===t[0]&&(n&&(i=n[1]),t=t.slice(1,t.length-i.length-1)),new RegExp(t,i)},predicate:function(e){return"[object RegExp]"===Object.prototype.toString.call(e)},represent:function(e){var t="/"+e.source+"/";return e.global&&(t+="g"),e.multiline&&(t+="m"),e.ignoreCase&&(t+="i"),t}})},{"../../type":13}],20:[function(e,t,n){"use strict";var i=e("../../type");t.exports=new i("tag:yaml.org,2002:js/undefined",{kind:"scalar",resolve:function(){return!0},construct:function(){},predicate:function(e){return void 0===e},represent:function(){return""}})},{"../../type":13}],21:[function(e,t,n){"use strict";var i=e("../type");t.exports=new i("tag:yaml.org,2002:map",{kind:"mapping",construct:function(e){return null!==e?e:{}}})},{"../type":13}],22:[function(e,t,n){"use strict";var i=e("../type");t.exports=new i("tag:yaml.org,2002:merge",{kind:"scalar",resolve:function(e){return"<<"===e||null===e}})},{"../type":13}],23:[function(e,t,n){"use strict";var i=e("../type");t.exports=new i("tag:yaml.org,2002:null",{kind:"scalar",resolve:function(e){if(null===e)return!0;var t=e.length;return 1===t&&"~"===e||4===t&&("null"===e||"Null"===e||"NULL"===e)},construct:function(){return null},predicate:function(e){return null===e},represent:{canonical:function(){return"~"},lowercase:function(){return"null"},uppercase:function(){return"NULL"},camelcase:function(){return"Null"}},defaultStyle:"lowercase"})},{"../type":13}],24:[function(e,t,n){"use strict";var i=e("../type"),r=Object.prototype.hasOwnProperty,o=Object.prototype.toString;t.exports=new i("tag:yaml.org,2002:omap",{kind:"sequence",resolve:function(e){if(null===e)return!0;var t,n,i,a,s,c=[],u=e;for(t=0,n=u.length;t 2 | 3 | 4 | Group 3 Copy 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/assets/images/launch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/src/assets/images/launch.png -------------------------------------------------------------------------------- /src/assets/images/loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/src/assets/images/loader.gif -------------------------------------------------------------------------------- /src/assets/images/salesforce_cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadewegner/deploy-to-sfdx/2501db3c1592c24b3cbc5b42617f46d94c15f5ba/src/assets/images/salesforce_cloud.png -------------------------------------------------------------------------------- /src/scss/main.scss: -------------------------------------------------------------------------------- 1 | @import '../../node_modules/@salesforce-ux/design-system/scss/index'; 2 | 3 | body { 4 | width: 650px; 5 | margin: 0 auto; 6 | line-height: 1.5rem; 7 | } 8 | 9 | a:hover { 10 | text-decoration: none; 11 | } 12 | 13 | a.sfdx-logo, 14 | a.sfdx-logo:hover, 15 | a.sfdx-logo:visited { 16 | color: $color-text-default; 17 | } 18 | 19 | /* START SFDX Specific Styles */ 20 | .sfdx-button_large { 21 | width: 100%; 22 | padding: $spacing-xx-small $spacing-medium; 23 | } 24 | 25 | .sfdx-output { 26 | width: 100%; 27 | height: 400px; 28 | padding: 0.75rem; 29 | line-height: 1.5rem; 30 | color: #54698d; 31 | resize: none; 32 | word-break:break-all; 33 | overflow: scroll; 34 | } 35 | 36 | .sfdx-slim { 37 | margin:0 5%; 38 | } 39 | 40 | .sfdx-wrap { 41 | word-wrap: break-word; 42 | } 43 | 44 | .sfdx-note { 45 | color: $color-text-browser-active; 46 | } 47 | 48 | .slds-card.sfdx-code { 49 | width: 100%; 50 | padding: $spacing-small; 51 | font-family: monospace; 52 | font-size: 1rem; 53 | white-space: pre; 54 | overflow: scroll; 55 | } 56 | /* END SFDX Specific Styles */ 57 | 58 | /* START SLDS Overrides */ 59 | .slds-card { 60 | padding: $spacing-medium $spacing-large; 61 | background: $color-background-modal; 62 | } 63 | 64 | .slds-button + .slds-button { 65 | margin-left: $spacing-medium; 66 | } 67 | 68 | .slds-notify_toast, .slds-notify--toast { 69 | width: 100%; 70 | min-width: 15rem; 71 | margin: 0; 72 | } 73 | 74 | ol, ul { 75 | list-style: inherit; 76 | padding: 0 0 0 $spacing-large; 77 | } 78 | 79 | .slds-notify__content { 80 | width: 100%; 81 | } 82 | 83 | .slds-notify_toast { 84 | padding-right: $card-spacing-large; 85 | } 86 | /* END SLDS Overrides */ 87 | 88 | @media all and (max-width: 750px) { 89 | html { 90 | background: $color-background-modal; 91 | } 92 | 93 | body { 94 | width: 95%; 95 | } 96 | 97 | .slds-card { 98 | border: none; 99 | } 100 | 101 | .sfdx-button_mobile { 102 | width: auto; 103 | margin: 0 $spacing-large; 104 | } 105 | 106 | .sfdx-slim { 107 | margin:0; 108 | } 109 | } -------------------------------------------------------------------------------- /views/pages/about.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | <% include ../partials/head %> 4 | 5 |

6 |
7 |

About

8 |

9 | Deploy to Salesforce DX is a tool that can deploy an application to Salesforce directly from Github using 10 | the tools provided by Salesforce DX. When you login with your Dev Hub 11 | credentials, you generate an access token that is used to create a scratch org and run all subsequent operations. 12 | This access token is only used during the deployment process and is stored in a browser cookie; it is not stored anywhere else. 13 |

14 |

15 | Deploy to Salesforce DX is a community project intended to help developers explore Salesforce DX. There is no official support, but you can log an issue at 16 | https://github.com/wadewegner/deploy-to-sfdx/issues. While every effort 17 | is made to ensure it operates without error, please use at your own risk. 18 |

19 |

20 | To learn more about Salesforce DX, take a look at these resources:
21 |

22 |

23 | Salesforce DX Setup Guide (Beta)
24 | Salesforce DX Developer Guide (Beta)
25 | Salesforce CLI Command Reference (Beta)
26 | Force.com IDE 2 Developer Guide (Beta)
27 | Salesforce DX Beta Success Community Group 28 |

29 | 30 | 31 |

Want to try your own repo?

32 |

Just add the button. Learn more.

33 | 34 |
35 |
36 | 37 | <% include ../partials/footer %> -------------------------------------------------------------------------------- /views/pages/choose.ejs: -------------------------------------------------------------------------------- 1 | <% include ../partials/head %> 2 | 3 | 4 |
5 | 6 |

Confirm Deployment Details

7 | 8 |

9 | Deploying to:
10 | <%= user_name %> 11 |

12 |

13 | Choose a repository:
14 | 15 |

16 |
17 | 18 | <% for (var i = 0; i < rows.length; i++) { %> 19 | 20 | 21 | checked="checked"<% } %> value="<%= rows[i].repo %>"> 22 | 26 | 27 | 28 | <% } %> 29 | 30 |
31 |
32 | 33 |

34 | 35 |
36 | 37 | 38 |
39 | 40 |
41 | 42 |
43 | 44 |

Want to try your own repo?

45 |

Just add the button. Learn more.

46 | 47 |
48 | 49 | 50 | 51 | <% include ../partials/footer %> -------------------------------------------------------------------------------- /views/pages/deploy.ejs: -------------------------------------------------------------------------------- 1 | <% include ../partials/head %> 2 | 3 | 4 |
5 | 6 |

Confirm Deployment Details

7 | 8 |

9 | Deploying to:
10 | <%= user_name %> 11 |

12 |

13 | From repository:
14 | <%= template %> 15 |

16 | 17 |
18 | 19 | 20 |
21 |
22 | 23 | 24 | 25 | 26 | <% include ../partials/footer %> -------------------------------------------------------------------------------- /views/pages/deploying.ejs: -------------------------------------------------------------------------------- 1 | <% include ../partials/head %> 2 | 3 | 4 | 5 |
6 |
7 |

8 | Deploying 9 | 10 | <%= template %> 11 | 12 |

13 |
14 | 15 | 36 | 37 | 49 | 50 |
51 |
52 |
53 | 54 |
55 | 56 | 57 | 58 | 59 | <% include ../partials/footer %> -------------------------------------------------------------------------------- /views/pages/error.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | <% include ../partials/head %> 4 | 5 |
6 |
7 |

Sorry, something went wrong!

8 | Salesforce.com Logo 9 |

10 | Please send a message to @WadeWegner for help. 11 |

12 |

13 | If you work for Salesforce and it failed during login, it's likely the browser keeps trying to access your Org62 or GUS token (which won't work). Try a fresh browser. 14 |

15 | 16 |
17 |
18 | 19 | <% include ../partials/footer %> -------------------------------------------------------------------------------- /views/pages/index.ejs: -------------------------------------------------------------------------------- 1 | <% include ../partials/head %> 2 | 3 |
4 |
5 |

Salesforce Deployment Made Easy

6 | Salesforce.com Logo 7 |

An open-source and community-driven tool for one-click Salesforce DX deployments from public repositories to Scratch Orgs.

8 | <% if (template) { %> 9 |

10 | From Repo
11 | <%= template %> 12 |

13 | <% } %> 14 | <% include ../partials/actions %> 15 | 16 |

Need a Dev Hub org? Learn how to get one here.

17 |
18 | 19 |
20 |

Currently <%= deploymentCount %> successful deployments!

21 |
22 | 23 | Learn More 24 |
25 | 26 | <% include ../partials/footer %> -------------------------------------------------------------------------------- /views/pages/notdevhub.ejs: -------------------------------------------------------------------------------- 1 | <% include ../partials/head %> 2 | 3 |
4 |
5 | 11 |
12 | 13 |

Consequently, you cannot currently use Deploy to Salesforce DX to create a scratch org from a Github repo. You can learn more about the Dev Hub here.

14 | 15 |

To try this out, you have two options:

16 | 17 |
    18 |
  1. Enable dev hub in a Production or Business Org (i.e. not a DE org). Search on “Dev Hub” from Setup and then click Enabled.
  2. 19 |
  3. Get a 30-day free trial.
  4. 20 |
21 | 22 |

Please try again once you've gotten this resolved.

23 | 24 | <% include ../partials/actions %> 25 |
26 | 27 | <% include ../partials/footer %> -------------------------------------------------------------------------------- /views/pages/repo.ejs: -------------------------------------------------------------------------------- 1 | <% include ../partials/head %> 2 | 3 |
4 | 5 |

Want to try your own repo?

6 | 7 |

It's easy. Just use the button.

8 | 9 |

Add the following source to your GitHub repo README:

10 | 11 |
[![Deploy](https://deploy-to-sfdx.com/dist/assets/images/DeployToSFDX.svg)](https://deploy-to-sfdx.com)
12 | 13 |

Next, create a .salesforcedx.yaml file in your repo (use what you need):

14 | 15 |
scratch-org-def: config/project-scratch-def.json 16 | assign-permset: true 17 | permset-name: yourpermset 18 | run-apex-tests: true 19 | delete-scratch-org: false 20 | show-scratch-org-url: true 21 | open-path: c/YourCustomApp.app 22 | data-plans: 23 | - ./data/YourCustomObject1__c-plan.json 24 | - ./data/YourCustomObject2__c-plan.json 25 |
26 | 27 |

That's it! See the https://github.com/wadewegner/sfdx-simple for an example of the button in action!

28 | 29 |

Embedding the button in an article or blog? Be sure to use the template parameter adjusting it to your GitHub repo.

30 | 31 |
[![Deploy](https://deploy-to-sfdx.com/dist/assets/images/DeployToSFDX.svg)](https://deploy-to-sfdx.com?template=https://github.com/wadewegner/sfdx-simple/)
32 | 33 |
34 | 35 | <% include ../partials/footer %> -------------------------------------------------------------------------------- /views/partials/actions.ejs: -------------------------------------------------------------------------------- 1 |
2 | Login with your Dev Hub org 3 |
-------------------------------------------------------------------------------- /views/partials/footer.ejs: -------------------------------------------------------------------------------- 1 |

2 |

3 | Written and shared by Wade Wegner under Apache 2.0 License and available at https://github.com/wadewegner/deploy-to-sfdx. 4 | User experience designed by Ben Snyder. 5 | Contributions from Andrew Fawcett. 6 |
7 | 8 | 9 | -------------------------------------------------------------------------------- /views/partials/head.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Deploy to Salesforce DX 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
32 |

33 |
-------------------------------------------------------------------------------- /web.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const bodyParser = require('body-parser'); 3 | const cookieParser = require('cookie-parser'); 4 | const https = require('https'); 5 | 6 | const app = express(); 7 | const router = express.Router(); 8 | 9 | app.use('/scripts', express.static(`${__dirname}/scripts`)); 10 | app.use('/dist', express.static(`${__dirname}/dist`)); 11 | 12 | app.use(bodyParser.urlencoded({ 13 | extended: true 14 | })); 15 | 16 | app.use(bodyParser.json()); 17 | app.set('view engine', 'ejs'); 18 | app.use(cookieParser()); 19 | 20 | require('./lib/web')(app); 21 | require('./lib/apis')(router); 22 | 23 | app.use('/api', router); 24 | 25 | const port = process.env.PORT || 8443; 26 | 27 | // if local, use 8443 and certificate 28 | if (process.env.NODE_ENV === 'dev') { 29 | 30 | const passPhrase = process.env.PASS_PHRASE; 31 | const certPem = process.env.CERT_PEM.replace(/\\n/g, '\n'); 32 | const keyPem = process.env.KEY_PEM.replace(/\\n/g, '\n'); 33 | 34 | const sslOptions = { 35 | key: keyPem, 36 | cert: certPem, 37 | passphrase: passPhrase 38 | }; 39 | 40 | const httpsServer = https.createServer(sslOptions, app); 41 | 42 | httpsServer.listen(port, () => { 43 | console.log(`Example app listening on port ${port}!`); 44 | }); 45 | 46 | } else { 47 | 48 | app.listen(port, () => { 49 | console.log(`Example app listening on port ${port}!`); 50 | }); 51 | 52 | } -------------------------------------------------------------------------------- /worker.js: -------------------------------------------------------------------------------- 1 | const async = require('async'); 2 | const pgp = require('pg-promise')(); 3 | const dbUrl = require('url'); 4 | const exec = require('child-process-promise').exec; 5 | 6 | const dbParams = dbUrl.parse(process.env.DATABASE_URL); 7 | const auth = dbParams.auth.split(':'); 8 | 9 | const config = { 10 | host: dbParams.hostname, 11 | port: dbParams.port, 12 | user: auth[0], 13 | ssl: true, 14 | password: auth[1], 15 | database: dbParams.pathname.split('/')[1], 16 | idleTimeoutMillis: 1000, 17 | max: 10 18 | }; 19 | 20 | const db = pgp(config); 21 | 22 | function setNewStage(settings, stage) { 23 | settings.stage = stage; 24 | return settings; 25 | } 26 | 27 | function deploymentStage(settings, complete = false) { 28 | 29 | let scratchOrgUrlSql = ''; 30 | if (complete) { 31 | scratchOrgUrlSql = `, scratch_url = '${settings.scratchOrgUrl}'`; 32 | } 33 | 34 | const updateQuery = `UPDATE deployments SET stage = '${settings.stage}', complete = ${complete}${scratchOrgUrlSql} WHERE guid = '${settings.guid}'`; 35 | db.any(updateQuery, [true]); 36 | return settings; 37 | } 38 | 39 | function deploymentSteps(settings) { 40 | 41 | const message = settings.message.replace("'", "''"); 42 | const insertQuery = `INSERT INTO deployment_steps (guid, stage, message) VALUES ('${settings.guid}', '${settings.stage}', '${message}')`; 43 | db.any(insertQuery, [true]); 44 | return settings; 45 | } 46 | 47 | function deploymentError(guid, message) { 48 | 49 | message = message.replace(/'/g, "''"); 50 | 51 | const insertQuery = `INSERT INTO deployment_steps (guid, stage, message) VALUES ('${guid}', 'error', '${message}')`; 52 | db.any(insertQuery, [true]); 53 | 54 | const updateQuery = `UPDATE deployments SET stage = 'error', error_message ='${message}', complete = false WHERE guid = '${guid}'`; 55 | db.any(updateQuery, [true]); 56 | } 57 | 58 | function formatMessage(settings) { 59 | 60 | let message = ''; 61 | 62 | if (settings.stderr) { 63 | 64 | message = `Error: ${settings.stderr}.`; 65 | 66 | if (settings.stderr.indexOf('Flag --permsetname expects a value') > -1) { 67 | message = 'No permset specified.'; 68 | } else { 69 | throw new Error(`GUID: ${settings.guid} ${settings.stderr}`); 70 | } 71 | 72 | } else { 73 | message = `${settings.stdout}.`; 74 | 75 | if (settings.stage === 'clone') { 76 | message = `Successfully cloned ${settings.githubRepo}.`; 77 | } 78 | if (settings.stage === 'instanceUrl') { 79 | message = `Set instance url to ${settings.instance_url}.`; 80 | } 81 | if (settings.stage === 'push') { 82 | message = `Pushed ${settings.stdout} source files.`; 83 | } 84 | if (settings.stage === 'permset') { 85 | if (!settings.assignPermset) { 86 | message = 'No permset specified.'; 87 | } else { 88 | message = `Permset '${settings.permsetName}' assigned.`; 89 | } 90 | } 91 | if (settings.stage === 'test') { 92 | if (settings.stdout !== '') { 93 | message = `Apex tests: ${settings.stdout}.`; 94 | } else { 95 | message = 'No Apex tests.'; 96 | } 97 | } 98 | if (settings.stage === 'dataplans') { 99 | if (settings.dataPlans.length > 0) { 100 | message = 'Data import successful.'; 101 | } else { 102 | message = 'No data plan specified.'; 103 | } 104 | } 105 | if (settings.stage === 'url') { 106 | settings.scratchOrgUrl = settings.stdout; 107 | message = `Scratch org URL: ${settings.scratchOrgUrl}.`; 108 | } 109 | if (settings.stage === 'yaml') { 110 | console.log('yaml', settings.yamlExists); 111 | if (!settings.yamlExists) { 112 | message = 'No .salesforcedx.yaml found in repository. Using defaults.'; 113 | } else { 114 | message = 'Using .salesforcedx.yaml found in repository.'; 115 | } 116 | } 117 | } 118 | 119 | settings.stderr = ''; 120 | 121 | console.log('message', settings.stage, message); 122 | settings.message = message; 123 | return settings; 124 | } 125 | 126 | function executeScript(settings, script) { 127 | return new Promise((resolve) => { 128 | exec(script, (error, stdout, stderr) => { 129 | 130 | if (stderr && error) { 131 | settings.stderr = stderr.replace(/\r?\n|\r/, '').trim(); 132 | } 133 | settings.stdout = stdout.replace(/\r?\n|\r/, '').trim(); 134 | 135 | resolve(settings); 136 | }); 137 | }); 138 | } 139 | 140 | // check for anything at init 141 | async.whilst( 142 | () => true, 143 | (callback) => { 144 | 145 | const selectQuery = "SELECT guid, username, repo, settings FROM deployments WHERE stage = 'init' AND complete = false LIMIT 1"; 146 | let guid = ''; 147 | 148 | db.any(selectQuery, [true]) 149 | .then((data) => { 150 | 151 | // throw if no data to skip the subsequent promises 152 | if (data.length === 0) { 153 | throw new Error('norecords'); 154 | } 155 | 156 | const settings = JSON.parse(data[0].settings); 157 | 158 | settings.guid = data[0].guid; 159 | guid = settings.guid; 160 | 161 | console.log('found job', guid); 162 | 163 | settings.tokenName = settings.access_token.replace(/\W/g, ''); 164 | settings.startingDirectory = process.env.STARTINGDIRECTORY; 165 | settings.directory = `${settings.tokenName}-${settings.guid}`; 166 | 167 | settings.cloneScript = `${settings.startingDirectory}rm -rf ${settings.directory};mkdir ${settings.directory};cd ${settings.directory};git clone ${settings.githubRepo} .`; 168 | settings.instanceUrlScript = `${settings.startingDirectory}cd ${settings.directory};export FORCE_SHOW_SPINNER=;sfdx force:config:set instanceUrl=${settings.instance_url};`; 169 | settings.createScript = `${settings.startingDirectory}cd ${settings.directory};export FORCE_SHOW_SPINNER=;sfdx force:org:create -v '${settings.access_token}' -s -f ${settings.scratchOrgDef}`; 170 | settings.pushScript = `${settings.startingDirectory}cd ${settings.directory};export FORCE_SHOW_SPINNER=;sfdx force:source:push --json | jq '.result.pushedSource | length'`; 171 | settings.permSetScript = `${settings.startingDirectory}cd ${settings.directory};export FORCE_SHOW_SPINNER=;sfdx force:user:permset:assign -n ${settings.permsetName}`; 172 | 173 | settings.dataPlanScript = `${settings.startingDirectory}cd ${settings.directory};export FORCE_SHOW_SPINNER=;`; 174 | 175 | for (let i = 0, len = settings.dataPlans.length; i < len; i++) { 176 | settings.dataPlanScript += `sfdx force:data:tree:import --plan ${settings.dataPlans[i]};`; 177 | } 178 | 179 | settings.testScript = `${settings.startingDirectory}cd ${settings.directory};export FORCE_SHOW_SPINNER=;sfdx force:apex:test:run -r human --json | jq -r .result | jq -r .summary | jq -r .outcome`; 180 | settings.urlScript = `${settings.startingDirectory}cd ${settings.directory};export FORCE_SHOW_SPINNER=;echo $(sfdx force:org:display --json | jq -r .result.instanceUrl)"/secur/frontdoor.jsp?sid="$(sfdx force:org:display --json | jq -r .result.accessToken)`; 181 | // add path if specified in yaml 182 | if (settings.openPath) { 183 | 184 | settings.urlScript += `"&retURL="${encodeURIComponent(settings.openPath)}`; 185 | } 186 | settings.scratchOrgUrl = ''; 187 | settings.stderr = ''; 188 | settings.stdout = ''; 189 | 190 | return settings; 191 | }) 192 | // yaml details 193 | .then(settings => setNewStage(settings, 'yaml')) 194 | .then(settings => deploymentStage(settings)) 195 | .then(settings => formatMessage(settings)) 196 | .then(settings => deploymentSteps(settings)) 197 | // clone 198 | .then(settings => setNewStage(settings, 'clone')) 199 | .then(settings => deploymentStage(settings)) 200 | .then(settings => executeScript(settings, settings.cloneScript)) 201 | .then(settings => formatMessage(settings)) 202 | .then(settings => deploymentSteps(settings)) 203 | // instanceUrl 204 | .then(settings => setNewStage(settings, 'instanceUrl')) 205 | .then(settings => deploymentStage(settings)) 206 | .then(settings => executeScript(settings, settings.instanceUrlScript)) 207 | .then(settings => formatMessage(settings)) 208 | .then(settings => deploymentSteps(settings)) 209 | // create 210 | .then(settings => setNewStage(settings, 'create')) 211 | .then(settings => deploymentStage(settings)) 212 | .then(settings => executeScript(settings, settings.createScript)) 213 | .then(settings => formatMessage(settings)) 214 | .then(settings => deploymentSteps(settings)) 215 | // push 216 | .then(settings => setNewStage(settings, 'push')) 217 | .then(settings => deploymentStage(settings)) 218 | .then(settings => executeScript(settings, settings.pushScript)) 219 | .then(settings => formatMessage(settings)) 220 | .then(settings => deploymentSteps(settings)) 221 | // permset 222 | .then(settings => setNewStage(settings, 'permset')) 223 | .then(settings => deploymentStage(settings)) 224 | .then(settings => executeScript(settings, settings.permSetScript)) 225 | .then(settings => formatMessage(settings)) 226 | .then(settings => deploymentSteps(settings)) 227 | // dataplans 228 | .then(settings => setNewStage(settings, 'dataplans')) 229 | .then(settings => deploymentStage(settings)) 230 | .then(settings => executeScript(settings, settings.dataPlanScript)) 231 | .then(settings => formatMessage(settings)) 232 | .then(settings => deploymentSteps(settings)) 233 | // test 234 | .then(settings => setNewStage(settings, 'test')) 235 | .then(settings => deploymentStage(settings)) 236 | .then(settings => executeScript(settings, settings.testScript)) 237 | .then(settings => formatMessage(settings)) 238 | .then(settings => deploymentSteps(settings)) 239 | // url 240 | .then(settings => setNewStage(settings, 'url')) 241 | .then(settings => deploymentStage(settings)) 242 | .then(settings => executeScript(settings, settings.urlScript)) 243 | .then(settings => formatMessage(settings)) 244 | .then(settings => deploymentSteps(settings)) 245 | // completed 246 | .then(settings => setNewStage(settings, 'complete')) 247 | .then(settings => deploymentStage(settings, true)) 248 | .then((settings) => { 249 | console.log('finished job', settings.guid); 250 | }) 251 | .catch((error) => { 252 | // handles cases where there are no records 253 | if (error.message !== 'norecords') { 254 | console.error('guid', guid); 255 | console.error('error', error); 256 | 257 | deploymentError(guid, error.message); 258 | } 259 | }); 260 | 261 | setTimeout(() => { 262 | callback(null, true); 263 | }, 3000); 264 | }, 265 | (err) => { 266 | console.error(`err: ${err}`); 267 | } 268 | ); --------------------------------------------------------------------------------