├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github └── CODEOWNERS ├── .gitignore ├── .vscode ├── extensions.json └── settings.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── COPYING ├── LICENSE ├── NOTICE ├── README.md ├── bin └── index.js ├── docs ├── .nojekyll ├── README.md ├── _sidebar.md ├── commands │ ├── build.md │ ├── create.md │ ├── dev.md │ ├── dist.md │ ├── docs.md │ ├── index.md │ ├── serve.md │ ├── update.md │ ├── upload.md │ └── watch.md ├── environmentvariables.md ├── index.html ├── index.md └── installation │ └── index.md ├── fixtures ├── common │ ├── eslint │ │ ├── editorconfig │ │ ├── eslintignore │ │ └── husky │ │ │ └── pre-commit │ └── lightning-app │ │ ├── metadata.json │ │ ├── settings.json │ │ └── static │ │ ├── fonts │ │ └── Roboto-Regular.ttf │ │ ├── icon.png │ │ └── images │ │ ├── background.png │ │ └── logo.png ├── dist │ ├── index.es5.html │ └── index.es6.html ├── js │ ├── eslint │ │ ├── eslintrc.js │ │ └── package.json │ ├── git │ │ └── gitignore │ ├── ide │ │ └── .vscode │ │ │ ├── extensions.json │ │ │ └── settings.json │ └── lightning-app │ │ ├── README.md │ │ ├── package.json │ │ └── src │ │ ├── App.js │ │ └── index.js └── ts │ ├── eslint │ ├── eslintrc.js │ └── package.json │ ├── git │ └── gitignore │ ├── ide │ └── .vscode │ │ ├── extensions.json │ │ └── settings.json │ └── lightning-app │ ├── README.md │ ├── package.json │ ├── src │ ├── App.ts │ ├── augmentations.d.ts │ └── index.ts │ ├── static │ └── images │ │ └── mystery.png │ └── tsconfig.json ├── package-lock.json ├── package.json ├── src ├── actions │ ├── build.js │ ├── create.js │ ├── dev.js │ ├── dist.js │ ├── docs.js │ ├── serve.js │ └── watch.js ├── alias │ ├── lightningjs-core.js │ └── wpe-lightning.js ├── configs │ ├── esbuild.es5.config.js │ ├── esbuild.es6.config.js │ ├── rollup.es5.config.js │ └── rollup.es6.config.js ├── helpers │ ├── ask.js │ ├── build.js │ ├── dist.js │ ├── distWatch.js │ ├── esbuildbabel.js │ ├── exit.js │ ├── generateObject.js │ ├── localinstallationcheck.js │ ├── packageVersion.js │ ├── sequence.js │ ├── spinner.js │ └── uptodate.js └── plugins │ └── esbuild-alias.js └── tests ├── 01.lng_create.test.js ├── 02.lng_build.test.js ├── 03.lng_dev.test.js ├── 04.lng_watch.test.js ├── 05.lng_dist.test.js ├── 06.lng_serve.test.js ├── 07.lng_docs.test.js ├── 08.lng_update.test.js ├── 10.lng_upload.test.js └── global_configs ├── globalMocks.js ├── globalSetup.js ├── globalTeardown.js └── runSequence.js /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset = utf-8 3 | indent_size = 2 4 | indent_style = space 5 | end_of_line = lf 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | browser: true, 5 | es6: true, 6 | node: true, 7 | jest: true, 8 | }, 9 | plugins: ['prettier'], 10 | extends: ['eslint:recommended', 'plugin:prettier/recommended', 'prettier'], 11 | rules: { 12 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 13 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 14 | quotes: [2, 'single', 'avoid-escape'], 15 | semi: [2, 'never'], 16 | 'no-extra-boolean-cast': 'off', 17 | 'no-unused-vars': [ 18 | 1, 19 | { 20 | argsIgnorePattern: 'res|next|^err', 21 | }, 22 | ], 23 | 'prettier/prettier': [ 24 | 'error', 25 | { 26 | trailingComma: 'es5', 27 | singleQuote: true, 28 | tabWidth: 2, 29 | semi: false, 30 | printWidth: 100, 31 | }, 32 | ], 33 | }, 34 | parserOptions: { 35 | parser: 'babel-eslint', 36 | ecmaVersion: 2018, 37 | sourceType: 'module', 38 | }, 39 | } 40 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @michielvandergeest, @erikhaandrikman, @wouterlucas 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_store 3 | com.domain.app.MyAwesomeApp 4 | com.domain.app.invalid41 5 | tests/__image_snapshots__ 6 | tests_report 7 | tests_coverage 8 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "editorconfig.editorconfig", 4 | "dbaeumer.vscode-eslint", 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": false, 3 | "eslint.validate": [ 4 | "javascript", 5 | ], 6 | "eslint.alwaysShowStatus": true, 7 | "editor.codeActionsOnSave": { 8 | "source.fixAll.eslint": "explicit" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v2.14.2 4 | 5 | *15 apr 2024* 6 | 7 | - Fixed issue related to custom config file extension for both esbuild & rollup. 8 | 9 | ## v2.14.1 10 | 11 | *11 apr 2024* 12 | 13 | - Fixed issues related to custom config implementations for both esbuild & rollup ([#257](https://github.com/rdkcentral/Lightning-CLI/issues/257)) 14 | 15 | ## v2.14.0 16 | 17 | *03 apr 2024* 18 | 19 | - Added support for providing a custom rollup configuration ([#254](https://github.com/rdkcentral/Lightning-CLI/issues/254)) 20 | - Added support for providing a custom esbuild configuration ([#255](https://github.com/rdkcentral/Lightning-CLI/issues/255)) 21 | 22 | 23 | ## v2.13.0 24 | 25 | *26 oct 2023* 26 | 27 | - Added unit tests for CLI ([#251](https://github.com/rdkcentral/Lightning-CLI/issues/251)) 28 | - Added support for esbuild and rollup bundler options. ([#249](https://github.com/rdkcentral/Lightning-CLI/issues/249)) 29 | - Updated bundler config files to maintain consistency. ([#242](https://github.com/rdkcentral/Lightning-CLI/issues/242)) 30 | 31 | ## v2.12.0 32 | 33 | *27 jul 2023* 34 | 35 | - Fixed issue with rollup typescript project throwing error when accessing process env ([#235](https://github.com/rdkcentral/Lightning-CLI/issues/235)) 36 | - Added "include" config in tsconfig.json for lng create command 37 | - Added support for getting "esEnv" from settings.json file for lng dist ([#224](https://github.com/rdkcentral/Lightning-CLI/issues/224)) 38 | - Added support for transpiling .mjs files to ES5 with rollup 39 | - Fixed the issue related to Babel ignore is not possible to use in a babel.config.json ([#177](https://github.com/rdkcentral/Lightning-CLI/issues/177)) 40 | 41 | ## v2.11.0 42 | 43 | *28 apr 2023* 44 | 45 | - Removed license texts from fixtures, so new Apps created with the CLI don't come with unnecessary licenses anymore ([#226](https://github.com/rdkcentral/Lightning-CLI/issues/226)) 46 | - Fixed build issue related to rollup path when using NPX. Solves issue for Metrological CLI. ([#222](https://github.com/rdkcentral/Lightning-CLI/issues/222)) 47 | - Fixed issue where sourcemap files were not generated when using esbuild ([#228](https://github.com/rdkcentral/Lightning-CLI/issues/228)) 48 | - Added support for CORS in `lng serve` via a new environment variable (`LNG_SERVE_CORS`) 49 | 50 | ## v2.10.0 51 | 52 | *15 feb 2023* 53 | 54 | - Fixed `.gitignore` is rewritten on each build. ([#209](https://github.com/rdkcentral/Lightning-CLI/issues/209)) 55 | - Removed `lng upload` command. 56 | - Fixed `_states()` is broken for esbuild bundle when NODE_ENV=production. ([#213](https://github.com/rdkcentral/Lightning-CLI/issues/213)) 57 | - Replaced WebSockets with Socket.io for live reload. 58 | - Added support for using environment variables set on the `process.env` in addition to variables defined in `.env` files ([#214](https://github.com/rdkcentral/Lightning-CLI/issues/214)) 59 | 60 | 61 | ## v2.9.1 62 | 63 | *24 oct 2022* 64 | 65 | - Fixed typo 66 | 67 | ## v2.9.0 68 | 69 | *20 oct 2022* 70 | 71 | - Added support for `--es5` and `--es6` options to `build` command. (#200) 72 | - Added support for Preserving Symbolic Links in the build process for rollup. (#201) 73 | - Added support for environment variable `LNG_LIVE_RELOAD_HOST` and updated the documentation relevant to it. (#203) 74 | - Added support for creating a project in typescript. (#195) 75 | - Replaced 'metrological' with 'domain' in reverse DNS app identifier. 76 | 77 | ## v2.8.1 78 | 79 | *22 aug 2022* 80 | 81 | - Added browser build support 82 | - Fixed issue related to ES5 polyfill path 83 | - Fixed issue related to Dist with Monorepo setup 84 | - Added Safari 12.0 as target for es6 configs in both rollup and esbuild bundlers 85 | 86 | ## v2.8.0 87 | 88 | *21 jun 2022* 89 | 90 | - Added mono repo support 91 | - Added basic Typescript support (in app code) 92 | - Added Source map support for rollup (es5 config) and esbuild (es5 and es6 configs) 93 | - Fixed custom build folder issue in `.env` file 94 | - Updated documentation related to `lng dist --watch` command 95 | - Fixed the issue to have default value for app identifier matches the app name 96 | 97 | ## v2.7.2 98 | 99 | *25 jan 2022* 100 | 101 | - Updated http-server NPM dependency fixing the colors.js corruption issue 102 | 103 | ## v2.7.1 104 | 105 | *29 nov 2021* 106 | 107 | - Updated rollup commonjs plugin (issue #156) 108 | - Fixed broken link in documentation 109 | 110 | ## v2.7.0 111 | 112 | *22 nov 2021* 113 | 114 | - Upgraded esbuild to version 0.12 115 | - Added watcher support for `lng dist` 116 | - Added `LNG_BUILD_FAIL_ON_WARNINGS` environment variable 117 | 118 | ## v2.6.0 119 | 120 | *20 oct 2021* 121 | 122 | - Added support for using `process.env.NODE_ENV` in Apps 123 | - Added using the minified lightning library file in dist builds 124 | - Added minification for esbuild bundler 125 | - Improved error messages and handling 126 | - Added babel-step to es6 config (for rollup and esbuild) to enable usage of class properties proposal syntax 127 | 128 | ## v2.5.1 129 | 130 | *29 jun 2021* 131 | 132 | - Fixes dynamic imports in rollup created bundle 133 | 134 | ## v2.5.0 135 | 136 | *23 apr 2021* 137 | 138 | - Updated CLI documentation 139 | - Added es5 support for esbuild bundler 140 | - Added support for different `settings.json`-files per environment 141 | - Improved error messages 142 | - Fixed missing `.gitignore`-file when creating new project with GIT options selected 143 | - Fixed App specific environment variables not repopulating between builds in esbuild bundler 144 | - Fixed version not showing in VersionLabel for dist version of App 145 | - Added warning when upload package too large 146 | 147 | ## v2.2.0 148 | 149 | *6 nov 2020* 150 | 151 | - Added rollup image-plugin to bundle up image assets (as base64-images) 152 | 153 | ## v2.1.0 154 | 155 | *30 oct 2020* 156 | 157 | - Added `LNG_SERVE_PROXY` environment variable 158 | - Added fix for polyfills not being copied over when using `lng dist --es5`-command 159 | - Fixed auto-update functionality 160 | - Added `lng update`-command 161 | 162 | ## v2.0.2 163 | 164 | *19 oct 2020* 165 | 166 | - Added fix for Lightning library not being copied over when using `lng dist`-command 167 | 168 | ## v2.0.1 169 | 170 | *14 oct 2020* 171 | 172 | - Changed package name from `wpe-lightning-cli` to `@lightningjs/cli` (and published on NPM) 173 | - Updated minimum requirement to Node.js 10 174 | - Support for building apps with new `@lightningjs/sdk` Loghtning-SDK (continued support for legacy `wpe-lightning-sdk`) 175 | - Various smaller updates 176 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | If you would like to contribute code to this project you can do so through GitHub by forking the repository 4 | and sending a pull request. 5 | Before RDK accepts your code into the project you must sign the RDK Contributor License Agreement (CLA). 6 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | LICENSE -------------------------------------------------------------------------------- /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 2019 Metrological 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 | 203 | ISC License 204 | 205 | Copyright (c) Antoine Boulanger (https://github.com/antoineboulanger) 206 | 207 | Permission to use, copy, modify, and/or distribute this software for any 208 | purpose with or without fee is hereby granted, provided that the above 209 | copyright notice and this permission notice appear in all copies. 210 | 211 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 212 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 213 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 214 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 215 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 216 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 217 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 218 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | This component contains software that is Copyright (c) 2020 Metrological. 2 | The component is licensed to you under the Apache License, Version 2.0 (the "License"). 3 | You may not use the component except in compliance with the License. 4 | 5 | The component may include material which is licensed under other 6 | licenses / copyrights as listed below. Your use of this material 7 | within the component is also subject to the terms and conditions 8 | of these licenses. The LICENSE file contains the text of all the 9 | licenses which apply within this component. 10 | 11 | Roboto Regular font is: 12 | Copyright 2017 Google Inc. All Rights Reserved. 13 | Licensed under the Apache License, Version 2.0 14 | 15 | Copyright (c) Antoine Boulanger (https://github.com/antoineboulanger) 16 | Licensed under the ISC License 17 | 18 | Use of the TypeScript logo (mystery.png) is described in https://www.typescriptlang.org/branding/ 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lightning CLI 2 | 3 | The Lightning-CLI is the _Command Line Interface_ tool for a seamless Lightning App Development flow. 4 | 5 | Install the Lightning-CLI _globally_ on your system 6 | 7 | ```bash 8 | npm install -g @lightningjs/cli 9 | ``` 10 | 11 | Usage: 12 | 13 | ```bash 14 | lng [options] 15 | ``` 16 | 17 | Check out the complete [documentation](https://rdkcentral.github.io/Lightning-CLI/) for more information. 18 | 19 | ## Feedback, bugs, questions and support 20 | 21 | In case you find any _bugs_ or have _feature requests_, feel free to open an [issue](https://github.com/rdkcentral/Lightning-CLI/issues/new) on the GitHub repository. 22 | 23 | If you have _questions_ or need _support_ using the Lightning-CLI, then we're happy to 24 | help you out on our [Discourse Forum](https://forum.lightningjs.io/) on [LightningJS.io](http://www.lightningjs.io). 25 | 26 | ## Contributing 27 | 28 | If you want to contribute to the Lightning-CLI, please consider the following: 29 | 30 | - the **master** branch is the latest stable release 31 | - the **dev** branch is used for upcoming releases 32 | - all development should be done in dedicated *topic branches* (from latest `dev`-branch) 33 | - please send in your PR against the `dev`-branch 34 | 35 | Before you submit your PR, make sure you install the projects dependencies, as this will activate automatic 36 | linting and code formatting in a Git commit hook. 37 | 38 | ## Changelog 39 | 40 | Checkout the changelog [here](./CHANGELOG.md). 41 | -------------------------------------------------------------------------------- /bin/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | * If not stated otherwise in this file or this component's LICENSE file the 5 | * following copyright and licenses apply: 6 | * 7 | * Copyright 2020 Metrological 8 | * 9 | * Licensed under the Apache License, Version 2.0 (the License); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | */ 21 | 22 | // load and parse (optional) .env file with 23 | require('dotenv').config() 24 | 25 | const program = require('commander') 26 | const didYouMean = require('didyoumean2').default 27 | const chalk = require('chalk') 28 | 29 | const createAction = require('../src/actions/create') 30 | const buildAction = require('../src/actions/build') 31 | const distAction = require('../src/actions/dist') 32 | const serveAction = require('../src/actions/serve') 33 | const watchAction = require('../src/actions/watch') 34 | const devAction = require('../src/actions/dev') 35 | const docsAction = require('../src/actions/docs') 36 | const upToDate = require('../src/helpers/uptodate') 37 | const generateObject = require('../src/helpers/generateObject') 38 | 39 | const updateCheck = (force = null) => upToDate(force === null ? Math.random() < 0.8 : !force) 40 | 41 | program 42 | .version('Lightning-CLI ' + require('../package').version) 43 | .usage('lightning-cli [options]') 44 | 45 | program 46 | .command('create') 47 | .description(['✨', ' '.repeat(3), 'Create a new Lightning App'].join('')) 48 | .action(() => { 49 | updateCheck(true) 50 | .then(() => createAction()) 51 | .catch(e => { 52 | console.error(e) 53 | process.exit(1) 54 | }) 55 | }) 56 | 57 | program 58 | .command('build') 59 | .option('--es5', 'Build standalone ES5 version of the App') 60 | .option('--es6', 'Build standalone ES6 version of the App') 61 | .option('--rollup-bundler-options ', 'Specify rollup bundler options') 62 | .option('--esbuild-bundler-options ', 'Specify esbuild bundler options') 63 | .description( 64 | ['👷‍♂️', ' '.repeat(3), 'Build a local development version of the Lightning App'].join('') 65 | ) 66 | .action((input) => { 67 | const defaultTypes = ['default'] 68 | const selectedTypes = Object.keys(input) 69 | .map(type => input[type] === true && type.toLocaleLowerCase()) 70 | .filter(val => !!val) 71 | 72 | const cmdLineBundlerOptionsKey = 73 | process.env.LNG_BUNDLER == 'esbuild' ? 'esbuildBundlerOptions' : 'rollupBundlerOptions' 74 | const envBundlerOptionsKey = 75 | process.env.LNG_BUNDLER == 'esbuild' 76 | ? 'LNG_BUNDLER_ESBUILD_OPTIONS' 77 | : 'LNG_BUNDLER_ROLLUP_OPTIONS' 78 | const cmdLineObj = input[cmdLineBundlerOptionsKey] 79 | ? generateObject(input[cmdLineBundlerOptionsKey]) 80 | : {} 81 | const cmdEnvObj = process.env[envBundlerOptionsKey] 82 | ? generateObject(process.env[envBundlerOptionsKey].split(',')) 83 | : {} 84 | 85 | const bundlerConfig = Object.assign(cmdEnvObj, cmdLineObj) 86 | 87 | updateCheck() 88 | .then(() => 89 | buildAction(true, null, selectedTypes.length ? selectedTypes : defaultTypes, bundlerConfig) 90 | ) 91 | .catch(e => { 92 | console.error(e) 93 | process.exit(1) 94 | }) 95 | }) 96 | 97 | program 98 | .command('serve') 99 | .description( 100 | [ 101 | '🖥', 102 | ' '.repeat(4), 103 | 'Start a local webserver and run a built Lightning App in a web browser', 104 | ].join('') 105 | ) 106 | .action(() => { 107 | updateCheck() 108 | .then(() => serveAction()) 109 | .catch(e => { 110 | console.error(e) 111 | process.exit(1) 112 | }) 113 | }) 114 | 115 | program 116 | .command('watch') 117 | .description( 118 | ['👀', ' '.repeat(3), 'Watch for file changes and automatically rebuild the App'].join('') 119 | ) 120 | .action(() => { 121 | updateCheck() 122 | .then(() => watchAction()) 123 | .catch(e => { 124 | console.error(e) 125 | process.exit(1) 126 | }) 127 | }) 128 | 129 | program 130 | .command('dev') 131 | .description( 132 | [ 133 | '👨‍💻', 134 | ' '.repeat(3), 135 | 'Build a local Lightning App, start a local webserver, run a built Lightning App in a web browser and watch for changes', 136 | ].join('') 137 | ) 138 | .action(() => { 139 | updateCheck() 140 | .then(() => devAction()) 141 | .catch(e => { 142 | console.error(e) 143 | process.exit(1) 144 | }) 145 | }) 146 | 147 | program 148 | .command('docs') 149 | .description(['📖', ' '.repeat(3), 'Open the Lightning-SDK documentation'].join('')) 150 | .action(() => { 151 | updateCheck() 152 | .then(() => docsAction()) 153 | .catch(e => { 154 | console.error(e) 155 | process.exit(1) 156 | }) 157 | }) 158 | 159 | program 160 | .command('dist') 161 | .option('--es5', 'Build standalone ES5 version of the App') 162 | .option('--es6', 'Build standalone ES6 version of the App') 163 | .option( 164 | '--watch', 165 | 'Watch for file changes and automatically update the distributable version of the App' 166 | ) 167 | .description( 168 | ['🌎', ' '.repeat(3), 'Create a standalone distributable version of the Lightning App'].join('') 169 | ) 170 | .action((input) => { 171 | const defaultTypes = ['defaults'] 172 | const isWatchEnabled = input.watch ? input.watch : false 173 | delete input.watch 174 | 175 | const selectedTypes = Object.keys(input) 176 | .map(type => input[type] === true && type.toLocaleLowerCase()) 177 | .filter(val => !!val) 178 | 179 | const cmdLineBundlerOptionsKey = 180 | process.env.LNG_BUNDLER == 'esbuild' ? 'esbuildBundlerOptions' : 'rollupBundlerOptions' 181 | const envBundlerOptionsKey = 182 | process.env.LNG_BUNDLER == 'esbuild' 183 | ? 'LNG_BUNDLER_ESBUILD_OPTIONS' 184 | : 'LNG_BUNDLER_ROLLUP_OPTIONS' 185 | const cmdLineObj = input[cmdLineBundlerOptionsKey] 186 | ? generateObject(input[cmdLineBundlerOptionsKey]) 187 | : {} 188 | const cmdEnvObj = process.env[envBundlerOptionsKey] 189 | ? generateObject(process.env[envBundlerOptionsKey].split(',')) 190 | : {} 191 | 192 | const bundlerConfig = Object.assign(cmdEnvObj, cmdLineObj) 193 | 194 | updateCheck() 195 | .then(() => 196 | distAction({ 197 | types: selectedTypes.length ? selectedTypes : defaultTypes, 198 | isWatchEnabled, 199 | bundlerConfig, 200 | }) 201 | ) 202 | .catch(e => { 203 | console.error(e) 204 | process.exit(1) 205 | }) 206 | }) 207 | 208 | program 209 | .command('update') 210 | .description(['🔄', ' '.repeat(3), 'Update the Lightning-CLI to the latest version'].join('')) 211 | .action(() => { 212 | updateCheck(true) 213 | .then(() => process.exit(1)) 214 | .catch(e => { 215 | console.error(e) 216 | process.exit(1) 217 | }) 218 | }) 219 | 220 | program.on('command:*', () => { 221 | const command = program.args[0] 222 | if (command === 'upload') { 223 | console.log() 224 | console.log(chalk.yellow('WARNING!!')) 225 | console.log() 226 | console.log( 227 | chalk.yellow( 228 | 'The `lng upload` command is no longer part of the Lightning-CLI, but has moved to a separate package.' 229 | ) 230 | ) 231 | console.log( 232 | chalk.yellow('Please see https://www.github.com/Metrological/metrological-cli for more info.') 233 | ) 234 | } else { 235 | const suggestion = didYouMean( 236 | command || '', 237 | program.commands.map(command => command._name) 238 | ) 239 | 240 | console.log("Sorry, that command doesn't seems to exist ...") 241 | console.log('') 242 | if (suggestion) { 243 | console.log('Perhaps you meant: ' + chalk.yellow('lng ' + suggestion) + '?') 244 | console.log('') 245 | } 246 | console.log('Use ' + chalk.yellow('lng -h') + ' to see a full list of available commands') 247 | process.exit(1) 248 | } 249 | }) 250 | 251 | program.parse(process.argv) 252 | 253 | if (!process.argv.slice(2).length) { 254 | program.outputHelp() 255 | } 256 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdkcentral/Lightning-CLI/78916017be66006c69f91a03fbdaab265f345090/docs/.nojekyll -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Lightning CLI 2 | 3 | The Lightning-CLI is the _Command Line Interface_ tool for a seamless Lightning App Development flow. 4 | Although it's primarily intended as a _client side_ developer tool, it can also be used _server side_. 5 | 6 | With the Lightning-CLI you can easily _create_ and _build_ Lightning Apps. 7 | It's also the standard way to _upload_ Lightning Apps to the Metrological AppStore. 8 | 9 | The Lightning-CLI goes hand in hand with the [Lightning-SDK](https://github.com/rdkcentral/Lightning-SDK). 10 | -------------------------------------------------------------------------------- /docs/_sidebar.md: -------------------------------------------------------------------------------- 1 | - [Installation](installation/index.md) 2 | - [Commands](commands/index.md) 3 | - [Create](commands/create.md) 4 | - [Build](commands/build.md) 5 | - [Serve](commands/serve.md) 6 | - [Watch](commands/watch.md) 7 | - [Dev](commands/dev.md) 8 | - [Docs](commands/docs.md) 9 | - [Dist](commands/dist.md) 10 | - [Upload](commands/upload.md) 11 | - [Update](commands/update.md) 12 | - [Environment variables](environmentvariables.md) 13 | -------------------------------------------------------------------------------- /docs/commands/build.md: -------------------------------------------------------------------------------- 1 | # Build 2 | 3 | *Build a local development version of a Lightning App* 4 | 5 | ```bash 6 | lng build 7 | ``` 8 | 9 | > Run this command from the root folder of your Lightning App. 10 | 11 | You can use the `lng build` command to build a version of your App for *local* development. 12 | 13 | The built App is placed in the **build** folder which is located in the folder of your App. 14 | 15 | > In older versions of the Lightning CLI (older than version 1.6.0), the build command placed the built App in the **dist** folder. 16 | 17 | The App version that is generated by the `lng build` command, is intended to simulate the Metrological Application Platform during *local* development. 18 | For example, it loads the appBundle asynchronously and makes an API request to **settings.json** (which is used for App configuration), similar to the boot process of the Metrological Application Platform. 19 | 20 | If you want to use your App *outside* the context of the Metrological Application Platform, you might also be interested in the [`lng dist`](dist.md) command. 21 | 22 | 23 | --------------------------------------- 24 | 25 | The lng build command, by default, generates an ES6-compatible appBundle using predefined bundler options. 26 | However, you have the flexibility to fine-tune the bundling process to meet your specific needs. 27 | You can pass additional bundler options and override the default settings by using the --rollup-bundler-options and --esbuild-bundler-options flags, depending on the bundler you choose. 28 | 29 | ### Rollup Bundler Options 30 | When using the Rollup bundler, you can customize your build process by specifying options using the `--rollup-bundler-options` flag. Here's an example: 31 | 32 | `lng build --rollup-bundler-options sourcemap=both splitting=true format=esm` 33 | 34 | You can pass any options that are supported by Rollup. Make sure to follow the required pattern/format for each type of option. 35 | 36 | ### esbuild Bundler Options 37 | 38 | If you prefer the esbuild bundler, you can similarly tailor your build process with the `--esbuild-bundler-options` flag: 39 | 40 | `lng build --esbuild-bundler-options sourcemap=both splitting=true format=esm` 41 | 42 | Like with Rollup, you can provide any valid esbuild options. Be sure to adhere to the specified pattern/format for different types of options. 43 | 44 | 45 | | S.No | Type | Format | Example| 46 | | -------- | -------- | -------- | -------- | 47 | | 1 | String| key=value | sourcemap=both| 48 | | 2 | Boolean | key=value (or) key | splitting=true (or) splitting| 49 | | 3 | Array | key=[value1, value2, value3] | dropLabels=[DEV,TEST] | 50 | | 4 | Object | optionName:key=value | banner:js=//javascriptcomment | 51 | 52 | Below command includes all the formats 53 | 54 | ```bash 55 | lng build --esbuild-bundler-options banner:js=//javascriptcomment banner:css=/*csscomment*/ resolveExtensions=[.js,.ts] dropLabels=[DEV,TEST] sourcemap=both tree-shaking line-limit=10 56 | ``` 57 | We can also specify the bundler options in the `.env` file and through commandline as `env variable`. Here the priority will be 58 | `.env < commandline env variable < commandline options` 59 | 60 | ### Rollup Custom Config 61 | 62 | If you prefer the rollup bundler to use the custom rollup config(user specified), you need to set the env Variable LNG_CUSTOM_ROLLUP to true. Under the hood the custom config will be merged with the default config options to generate the bundle. 63 | 64 | Also make sure the following : 65 | 66 | | S.No | Info | Description| 67 | | -------- | -------- |---------- 68 | | 1 | Config Location | Project Home | 69 | | 2 | Config File Name | `rollup.es6.config.js`(for es6)| 70 | | | | `rollup.es5.config.js`(for es5) | 71 | | | | | 72 | 73 | Example custom Config file content: 74 | 75 | ```javascript 76 | module.exports = { 77 | input: 'src/index.js', 78 | output: { 79 | format: 'iife', 80 | sourcemap: false, 81 | }, 82 | } 83 | -------------------------------------------------------------------------------- /docs/commands/create.md: -------------------------------------------------------------------------------- 1 | # Create 2 | 3 | *Create a new Lightning App* 4 | 5 | ```bash 6 | lng create 7 | ``` 8 | 9 | The `lng create` command helps you to quickly kick start a new App. 10 | 11 | An interactive prompt takes you through a series of steps and choices. 12 | 13 | Based on your input, it generates a complete *blueprint* App with the appropriate file and folder structure as required by the Lightning SDK. 14 | 15 | You can use the generated blueprint App and files / folder structure as a starting point for the development of a new Lightning App. 16 | -------------------------------------------------------------------------------- /docs/commands/dev.md: -------------------------------------------------------------------------------- 1 | # Dev 2 | 3 | *Build a local Lightning App, start a local webserver, run a built Lightning App in a web browser and watch for changes* 4 | 5 | ```bash 6 | lng dev 7 | ``` 8 | 9 | > Run this command from the root folder of your Lightning App. 10 | 11 | The `lng dev` command combines *three* Lightning CLI commands into *one*, making it the *most convenient* command to use during development. 12 | 13 | The command `lng dev` performs the following actions subsequently: 14 | 15 | 1. Build your Lightning App (see `[lng build](build.md)`) 16 | 2. Start a local webserver and open it in a web browser (see `[lng serve](serve.md)`) 17 | 3. Initiate a *watcher* for any file changes and automatically *rebuild* the App upon each change (see `[lng watch](watch.md)`) 18 | -------------------------------------------------------------------------------- /docs/commands/dist.md: -------------------------------------------------------------------------------- 1 | # Dist 2 | 3 | *Create a standalone, distributable version of the Lightning App* 4 | 5 | ```bash 6 | lng dist 7 | ``` 8 | 9 | > Run this command from the root folder of your Lightning App. 10 | 11 | The `lng dist` command is used to build a *standalone, distributable* version of your App that can either be run 12 | locally (by starting your own local server) or that can be uploaded to a webserver. 13 | 14 | The first time you run this command, it generates the required folder structure and files (in the **dist** folder), 15 | and copies the settings from your **settings.json** file into the **index.html** file. 16 | 17 | After that, you can make your own customizations to **index.html**. 18 | 19 | Each time that you run `lng dist`, the **appBundle.js** and the **static** folders are purged 20 | and regenerated. 21 | 22 | By default, the `lng dist` command generates an *es6*-compatible appBundle. Optionally, you can generate an *es5* version of the App, 23 | by passing `--es5` as an option as shown below: 24 | 25 | ```bash 26 | lng dist --es5 27 | ``` 28 | 29 | Each time you make a change to the source code of your App, you have to execute `lng dist` to generate a new dist build of the app. 30 | This can become a cumbersome process and is easily forgotten. 31 | 32 | By adding the `--watch`-flag to the dist command (i.e `lng dist --watch`) this process is automated. A *watcher* keeps track of any file changes in the **src** and **static** folders and triggers a `lng dist` action for every change. 33 | -------------------------------------------------------------------------------- /docs/commands/docs.md: -------------------------------------------------------------------------------- 1 | # Docs 2 | 3 | *Open the Lightning SDK documentation* 4 | 5 | ```bash 6 | lng docs 7 | ``` 8 | 9 | > Run this command from the root folder of your Lightning App. 10 | 11 | You can use the `lng docs` command if you need to review the *Lightning SDK* documentation for the App that you are currently developing. 12 | 13 | It is important to realize that the *Lightning SDK* documentation is *version controlled*. The `lng docs` command will open 14 | a local webserver that displays the documentation matching the *exact* SDK version that your App is using. 15 | -------------------------------------------------------------------------------- /docs/commands/index.md: -------------------------------------------------------------------------------- 1 | # Commands 2 | 3 | The Lightning CLI offers various useful commands to help you with the development of your Lightning App. These commands are, in chronological usage order: 4 | 5 | * [Create](create.md) 6 | * [Build](build.md) 7 | * [Serve](serve.md) 8 | * [Watch](watch.md) 9 | * [Dev](dev.md) 10 | * [Docs](docs.md) 11 | * [Dist](dist.md) 12 | * [Upload](upload.md) 13 | * [Update](update.md) 14 | 15 | You can run the command `lng -h` (*help*) to print out an overview of the available Lightning CLI commands. 16 | 17 | > You must run *all* Lightning CLI commands (except `lng create` and `lng update`) from the *root folder* of your App. 18 | -------------------------------------------------------------------------------- /docs/commands/serve.md: -------------------------------------------------------------------------------- 1 | # Serve 2 | 3 | *Start a local webserver and run a built Lightning App in a web browser* 4 | 5 | ```bash 6 | lng serve 7 | ``` 8 | 9 | > Run this command from the root folder of your Lightning App. 10 | 11 | The `lng serve` command starts a local webserver and runs an App (that was built with the `lng build` command) from the **build** folder. 12 | 13 | By default, the local webserver launches at the next available port –starting at 8080– and opens your App in a new browser window. 14 | -------------------------------------------------------------------------------- /docs/commands/update.md: -------------------------------------------------------------------------------- 1 | # Update 2 | 3 | *Update the Lightning CLI to the latest version* 4 | 5 | ```bash 6 | lng update 7 | ``` 8 | 9 | The Lightning CLI has an *auto-update* mechanism. It checks periodically if you are still using the latest version and, if you are not, updates automatically. An auto update is triggered by a new version of the CLI being released (which can be related to a change on the SDK side or the Metrological Dashboard). This mechanism ensures that you are always compatible with the latest changes. 10 | 11 | If you want to manually *force* an update to the latest version, use the `lng update` command. 12 | -------------------------------------------------------------------------------- /docs/commands/upload.md: -------------------------------------------------------------------------------- 1 | # Upload 2 | 3 | *Upload the Lightning App to the Metrological Dashboard, so that it can be published to an operator's App Store* 4 | 5 | ```bash 6 | lng upload 7 | ``` 8 | 9 | > Run this command from the root folder of your Lightning App. 10 | 11 | When you have finished developing your App, you can upload your App to the Metrological Dashboard, 12 | so it can be published to an operator s App Store. 13 | 14 | > You need to create an account to be able to upload Apps to the [Metrological Dashboard](http://dashboard.metrological.com/). 15 | 16 | The `lng upload` command takes care of the *entire* uploading process. It prompts you for a valid *API key* to authenticate your account. It will then build and bundle up your App and upload it to the Metrological Dashboard. 17 | 18 | 19 | > WARNING!

20 | > The `lng upload` command has been deprecated and has moved to a separate package.
21 | > Please see [https://www.github.com/Metrological/metrological-cli](https://www.github.com/Metrological/metrological-cli) for more info.
22 | > The upload command will be completely removed from the Lightnng-CLI in the Jan 2023 release
23 | -------------------------------------------------------------------------------- /docs/commands/watch.md: -------------------------------------------------------------------------------- 1 | # Watch 2 | 3 | *Watch for file changes and automatically rebuild the App* 4 | 5 | ```bash 6 | lng watch 7 | ``` 8 | 9 | > Run this command from the root folder of your Lightning App. 10 | 11 | If you make a change to the source code of your App, you have to *rebuild* the App (using `lng build`). 12 | This can become a cumbersome process and is easily forgotten. 13 | 14 | The `lng watch` command performs an automatic build, by initiating a *watcher* that keeps track of any file changes 15 | and that triggers a rebuild for every change. 16 | 17 | The command watches the **src** and **static** folders and the **settings.json** and **metadata.json** files. 18 | 19 | The Lightning CLI intelligently 20 | generates a new build, depending on what has changed. 21 | 22 | If you make changes *outside* the files and folders mentioned above, you have to rebuild your App manually. 23 | 24 | > The `lng watch` command does *not* automatically *hot reload* your App. You still have to refresh the browser to see the latest changes. 25 | -------------------------------------------------------------------------------- /docs/environmentvariables.md: -------------------------------------------------------------------------------- 1 | # Environment Variables 2 | 3 | The Lightning CLI accepts three types of *environment variables*, which are: 4 | 5 | * `NODE_ENV` 6 | * A predefined set of environment variables that *customize the behavior* of CLI commands (for example, by changing the port on which to open `lng serve`) 7 | * Custom, app-specific variables to be injected into your App (for example, an API key) 8 | 9 | You can pass environment variables via the command prompt before calling a command, or you can specify them in the **.env** file. 10 | 11 | Example of passing environment variables via the *command prompt*: 12 | 13 | ```bash 14 | NODE_ENV=staging LNG_SERVE_PORT=3333 lng serve 15 | ``` 16 | 17 | This starts a server on *port 3333* and sets the value of `process.env.NODE_ENV` (accessible from inside you App) to 'staging'. 18 | 19 | Example of specifying environment variables in the **.env** file: 20 | 21 | ``` 22 | NODE_ENV=production 23 | 24 | LNG_SERVE_OPEN=false 25 | LNG_SERVE_PORT=1234 26 | 27 | APP_API_KEY=mysecretapikey 28 | ``` 29 | 30 | ## Types of Environment Variables 31 | 32 | ### NODE_ENV 33 | 34 | The environment variable `NODE_ENV` is used by several external libraries. You can also reference to this environment variable in your App code as `process.env.NODE_ENV`. 35 | 36 | ### Behavior Environment Variables 37 | 38 | You can use the following environment variables to customize the behavior of the Lightning CLI: 39 | 40 | | Name | Default | Description | 41 | |---|---|---| 42 | | `LNG_SERVE_OPEN` | true | Indicates whether or not the Lightning CLI opens a browser window when running `lng serve` or `lng dev`. Possible values: `true`, `false`. | 43 | | `LNG_SERVE_PORT` | auto-incrementing, start at '8080' | Specifies on which port the Lightning CLI must serve when running `lng serve` or `lng dev`. Auto-incrementing and starting port (see Default) depend on available ports. | 44 | | `LNG_SERVE_PROXY` | (N.A.) | Proxies all requests that cannot be resolved locally to the given URL. | 45 | | `LNG_BUILD_SOURCEMAP` | true | Instructs the Lightning CLI whether or not and if so, *how* to generate sourcemaps. Possible values: `true`, `false`, `inline`. The value `true` generates the sourcemaps in a separate file (**appBundle.js.map**). The value `inline` appends the sourcemaps to the **appBundle.js** itself as a data URI. | 46 | | `LNG_BUILD_FOLDER` | build | Specifies the folder in which the built App (using `lng build`) will be generated. | 47 | | `LNG_DIST_FOLDER` | dist | Specifies the folder in which the standalone, distributable App (using `lng dist`) will be generated. | 48 | | `LNG_AUTO_UPDATE` | true | Indicates whether or not the Lightning CLI should automatically update ('auto update'). Possible values: `true`, `false`. **Note**: It is recommended to keep auto updates enabled. | 49 | | `LNG_BUILD_EXIT_ON_FAIL` | false | Specifies whether or not the build process should hard exit when a build error occurs. Note that the build process is triggered in several commands (`lng build`, `lng dev`, `lng watch` and `lng dist`) | 50 | | `LNG_BUILD_FAIL_ON_WARNINGS` | false | Specifies whether or not to show the warning to the user when a build warning occurs. Note that the build process is triggered in several commands (`lng build`, `lng dev`, `lng watch` and `lng dist`) | 51 | | `LNG_BUNDLER` | rollup | Specify which bundler the CLI should use to bundle the app. Possible values: `rollup`, `esbuild`. | 52 | | `LNG_BROWSER_BUILD` | false | Specify whether or not browser build is to be generated. Possible values: `true`, `false`. | 53 | | `LNG_LIVE_RELOAD` | false | Instructs your browser to reload the location when a new app bundle is created (using `lng dev`). When the watcher resolves, `document.location.reload()` is called in the browser (tab) that serves your app. Live reload communication is driven by WebSockets. Possible value: `true`, `false`. | 54 | | `LNG_LIVE_RELOAD_HOST` | localhost | Specifies the Websocket host your application will attempt to connect to listen for livereload events. Possible values: ip or host. | 55 | | `LNG_LIVE_RELOAD_PORT` | 8991 | Specifies the port Websocket is listening on. Possible values: Any numeric value. | 56 | | `LNG_SERVE_CORS` | false | When set to `true`, CORS is enabled by allowing all origins (Access-Control-Allow-Origin: *) and default headers (Origin, X-Requested-With, Content-Type, Accept, Range). To allow additional headers, provide comma-separated header names as the value (e.g. Authorization, X-Debug). This both enables CORS and appends the provided headers to Access-Control-Allow-Headers 57 | | `LNG_BUNDLER_ESBUILD_OPTIONS` | false | Specifies the specific bundler options required for esbuild 58 | | `LNG_BUNDLER_ROLLUP_OPTIONS` | false | Specifies the specific bundler options required for rollup 59 | | `LNG_BUILD_MINIFY` | false | When set to `true` minifies the bundle 60 | | `LNG_CUSTOM_ROLLUP` | false | When set to `true`, uses the custom rollup config file(rollup.{env}.config.js) located in the project home directory 61 | | `LNG_CUSTOM_ESBUILD` | false | When set to `true`, uses the custom esbuild config file located(esbuild.{env}.config.js) in the project home directory 62 | 63 | #### `LNG_SETTINGS_ENV` 64 | Specifies which environment to be used. User need to have `settings.{env}.json` file in the Project home folder with different settings. This will build/dist the application with `settings.{env}.json`. 65 | If `settings.{env}.json` file is not found in the Project home folder, then default settings file(`settings.json`) is considered to build/dist the application. 66 | 67 | For example `LNG_SETTINGS_ENV=dev` picks up the `settings.dev.json` file(in the Project home folder) to build/dist the application 68 | 69 | Defaults to `settings.json` 70 | You can specify custom, app-specific environment variables to be *injected* into your App bundle. This can be useful for specifying an API endpoint or API key, for example. 71 | 72 | App-specific variables must always start with `APP_` and are referenced inside the App code as `process.env.APP_MY_VAR`. 73 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Lightning CLI 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Lightning CLI Reference 2 | 3 | The Reference Documentation for Lightning CLI contains detailed descriptions of: 4 | 5 | 6 | * [Installation](installation/index.md) 7 | * [Commands](commands/index.md) 8 | * [Create](commands/create.md) 9 | * [Build](commands/build.md) 10 | * [Serve](commands/serve.md) 11 | * [Watch](commands/watch.md) 12 | * [Dev](commands/dev.md) 13 | * [Docs](commands/docs.md) 14 | * [Dist](commands/dist.md) 15 | * [Upload](commands/upload.md) 16 | * [Update](commands/update.md) 17 | * [Environment Variables](environmentvariables.md) 18 | 19 | -------------------------------------------------------------------------------- /docs/installation/index.md: -------------------------------------------------------------------------------- 1 | # Install the Lightning CLI 2 | 3 | The easiest way to get up and running with a Lightning App is by installing the *Lightning CLI* globally on your system. 4 | 5 | > You can find the Lightning CLI repository here: [https://github.com/rdkcentral/LightningCLI/.](https://github.com/rdkcentral/Lightning-SDK/) 6 | 7 | The Lightning CLI provides a set of development tools that enable you to do the following: 8 | 9 | * Quickly create a new *blueprint* for a Lightning App 10 | * Build and run Lightning Apps in your browser 11 | * Deploy a new release of your App to the Metrological Dashboard 12 | 13 | > Make sure you are using the *latest* Lightning CLI version. See the [Lightning Release Info](http://www.lightningjs.io/announcements/carbon-release) for more information. 14 | 15 | Perform the following steps to install the Lightning CLI on your system: 16 | 17 | 1. Run the command `npm install -g @lightningjs/cli` in your command prompt. 18 | 2. Check if the installation was successful by running `lng -V`. This should display the version of the Lightning CLI you have installed. 19 | 3. If the Lightning CLI is installed successfully, you can use it anywhere on your machine using the `lng` command. 20 | 21 | > See [Commands](../commands/index.md) in the [Lightning CLI Reference Documentation](../index.md) for a description of the available Lightning CLI commands and options. 22 | -------------------------------------------------------------------------------- /fixtures/common/eslint/editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset = utf-8 3 | indent_size = 2 4 | indent_style = space 5 | end_of_line = lf 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | -------------------------------------------------------------------------------- /fixtures/common/eslint/eslintignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | dist 3 | build 4 | build-ts 5 | -------------------------------------------------------------------------------- /fixtures/common/eslint/husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | npx lint-staged 3 | -------------------------------------------------------------------------------- /fixtures/common/lightning-app/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{$appName}", 3 | "identifier": "{$appId}", 4 | "version": "1.0.0", 5 | "icon": "./static/icon.png" 6 | } 7 | -------------------------------------------------------------------------------- /fixtures/common/lightning-app/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "appSettings": { 3 | "stage": { 4 | "clearColor": "0x00000000", 5 | "useImageWorker": true 6 | }, 7 | "debug": false 8 | }, 9 | "platformSettings": { 10 | "path": "./static", 11 | "log": true, 12 | "showVersion": true 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /fixtures/common/lightning-app/static/fonts/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdkcentral/Lightning-CLI/78916017be66006c69f91a03fbdaab265f345090/fixtures/common/lightning-app/static/fonts/Roboto-Regular.ttf -------------------------------------------------------------------------------- /fixtures/common/lightning-app/static/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdkcentral/Lightning-CLI/78916017be66006c69f91a03fbdaab265f345090/fixtures/common/lightning-app/static/icon.png -------------------------------------------------------------------------------- /fixtures/common/lightning-app/static/images/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdkcentral/Lightning-CLI/78916017be66006c69f91a03fbdaab265f345090/fixtures/common/lightning-app/static/images/background.png -------------------------------------------------------------------------------- /fixtures/common/lightning-app/static/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdkcentral/Lightning-CLI/78916017be66006c69f91a03fbdaab265f345090/fixtures/common/lightning-app/static/images/logo.png -------------------------------------------------------------------------------- /fixtures/dist/index.es5.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /fixtures/dist/index.es6.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /fixtures/js/eslint/eslintrc.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | module.exports = { 3 | root: true, 4 | env: { 5 | browser: true, 6 | es6: true, 7 | }, 8 | plugins: ['prettier'], 9 | extends: ['eslint:recommended', 'plugin:prettier/recommended', 'prettier'], 10 | rules: { 11 | quotes: [2, 'single', 'avoid-escape'], 12 | semi: [2, 'never'], 13 | 'no-extra-boolean-cast': 'off', 14 | 'no-unused-vars': [ 15 | 1, 16 | { 17 | ignoreRestSiblings: true, 18 | argsIgnorePattern: 'res|next|^err', 19 | }, 20 | ], 21 | 'prettier/prettier': [ 22 | 'error', 23 | { 24 | trailingComma: 'all', 25 | singleQuote: true, 26 | tabWidth: 2, 27 | semi: false, 28 | printWidth: 100, 29 | }, 30 | ], 31 | }, 32 | parserOptions: { 33 | parser: 'babel-eslint', 34 | ecmaVersion: 2018, 35 | sourceType: 'module', 36 | }, 37 | } 38 | -------------------------------------------------------------------------------- /fixtures/js/eslint/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lint-staged": { 3 | "*.js": [ 4 | "eslint --fix", 5 | "git add" 6 | ] 7 | }, 8 | "scripts": { 9 | "prepare": "husky install" 10 | }, 11 | "devDependencies": { 12 | "@babel/core": "^7.7.2", 13 | "babel-eslint": "^10.0.3", 14 | "eslint": "^8.21.0", 15 | "eslint-config-prettier": "^8.5.0", 16 | "eslint-plugin-prettier": "^4.2.1", 17 | "husky": "^8.0.1", 18 | "lint-staged": "^13.0.3", 19 | "prettier": "^2.7.1" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /fixtures/js/git/gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_store 3 | .idea 4 | dist 5 | build 6 | releases 7 | .tmp 8 | .env 9 | -------------------------------------------------------------------------------- /fixtures/js/ide/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "editorconfig.editorconfig", 4 | "dbaeumer.vscode-eslint", 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /fixtures/js/ide/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "editor.formatOnPaste": true, 4 | "eslint.validate": [ 5 | "javascript", 6 | ], 7 | "editor.codeActionsOnSave": { 8 | "source.fixAll.eslint": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /fixtures/js/lightning-app/README.md: -------------------------------------------------------------------------------- 1 | # {$appName} 2 | 3 | ## {$appId} 4 | 5 | ### Getting started 6 | 7 | > Before you follow the steps below, make sure you have the 8 | [Lightning-CLI](https://rdkcentral.github.io/Lightning-CLI/#/) installed _globally_ only your system 9 | 10 | ``` 11 | npm install -g @lightningjs/cli 12 | ``` 13 | 14 | #### Running the App 15 | 16 | 1. Install the NPM dependencies by running `npm install` 17 | 18 | 2. Build the App using the _Lightning-CLI_ by running `lng build` inside the root of your project 19 | 20 | 3. Fire up a local webserver and open the App in a browser by running `lng serve` inside the root of your project 21 | 22 | #### Developing the App 23 | 24 | During development you can use the **watcher** functionality of the _Lightning-CLI_. 25 | 26 | - use `lng watch` to automatically _rebuild_ your App whenever you make a change in the `src` or `static` folder 27 | - use `lng dev` to start the watcher and run a local webserver / open the App in a browser _at the same time_ 28 | 29 | #### Documentation 30 | 31 | Use `lng docs` to open up the Lightning-SDK documentation. 32 | -------------------------------------------------------------------------------- /fixtures/js/lightning-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{$appId}", 3 | "description": "{$appName}", 4 | "dependencies": { 5 | "@lightningjs/sdk": "{$sdkVersion}" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /fixtures/js/lightning-app/src/App.js: -------------------------------------------------------------------------------- 1 | import { Lightning, Utils } from '@lightningjs/sdk' 2 | 3 | export default class App extends Lightning.Component { 4 | static getFonts() { 5 | return [{ family: 'Regular', url: Utils.asset('fonts/Roboto-Regular.ttf') }] 6 | } 7 | 8 | static _template() { 9 | return { 10 | Background: { 11 | w: 1920, 12 | h: 1080, 13 | color: 0xfffbb03b, 14 | src: Utils.asset('images/background.png'), 15 | }, 16 | Logo: { 17 | mountX: 0.5, 18 | mountY: 1, 19 | x: 960, 20 | y: 600, 21 | src: Utils.asset('images/logo.png'), 22 | }, 23 | Text: { 24 | mount: 0.5, 25 | x: 960, 26 | y: 720, 27 | text: { 28 | text: "Let's start Building!", 29 | fontFace: 'Regular', 30 | fontSize: 64, 31 | textColor: 0xbbffffff, 32 | }, 33 | }, 34 | } 35 | } 36 | 37 | _init() { 38 | this.tag('Background') 39 | .animation({ 40 | duration: 15, 41 | repeat: -1, 42 | actions: [ 43 | { 44 | t: '', 45 | p: 'color', 46 | v: { 0: { v: 0xfffbb03b }, 0.5: { v: 0xfff46730 }, 0.8: { v: 0xfffbb03b } }, 47 | }, 48 | ], 49 | }) 50 | .start() 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /fixtures/js/lightning-app/src/index.js: -------------------------------------------------------------------------------- 1 | import { Launch } from '@lightningjs/sdk' 2 | import App from './App.js' 3 | 4 | export default function() { 5 | return Launch(App, ...arguments) 6 | } 7 | -------------------------------------------------------------------------------- /fixtures/ts/eslint/eslintrc.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | module.exports = { 3 | root: true, 4 | env: { 5 | browser: true, 6 | es6: true, 7 | }, 8 | parser: '@typescript-eslint/parser', 9 | plugins: ['@typescript-eslint', 'prettier'], 10 | extends: [ 11 | 'eslint:recommended', 12 | 'plugin:@typescript-eslint/eslint-recommended', 13 | 'plugin:@typescript-eslint/recommended', 14 | 'plugin:prettier/recommended', 15 | 'prettier', 16 | ], 17 | rules: { 18 | quotes: [2, 'single', 'avoid-escape'], 19 | semi: [2, 'never'], 20 | 'no-extra-boolean-cast': 'off', 21 | 'no-unused-vars': [ 22 | 1, 23 | { 24 | ignoreRestSiblings: true, 25 | argsIgnorePattern: 'res|next|^err', 26 | }, 27 | ], 28 | '@typescript-eslint/no-non-null-assertion': 'off', 29 | 'prettier/prettier': [ 30 | 'error', 31 | { 32 | trailingComma: 'all', 33 | singleQuote: true, 34 | tabWidth: 2, 35 | semi: false, 36 | printWidth: 100, 37 | }, 38 | ], 39 | }, 40 | parserOptions: { 41 | parser: 'babel-eslint', 42 | ecmaVersion: 2018, 43 | sourceType: 'module', 44 | }, 45 | } 46 | -------------------------------------------------------------------------------- /fixtures/ts/eslint/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lint-staged": { 3 | "*.js": [ 4 | "eslint --fix", 5 | "git add" 6 | ], 7 | "*.ts": [ 8 | "eslint --fix", 9 | "git add" 10 | ] 11 | }, 12 | "scripts": { 13 | "prepare": "husky install" 14 | }, 15 | "devDependencies": { 16 | "@babel/core": "^7.7.2", 17 | "@typescript-eslint/eslint-plugin": "^5.32.0", 18 | "@typescript-eslint/parser": "^5.32.0", 19 | "babel-eslint": "^10.0.3", 20 | "eslint": "^8.21.0", 21 | "eslint-config-prettier": "^8.5.0", 22 | "eslint-plugin-prettier": "^4.2.1", 23 | "husky": "^8.0.1", 24 | "lint-staged": "^13.0.3", 25 | "prettier": "^2.7.1" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /fixtures/ts/git/gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_store 3 | .idea 4 | dist 5 | build 6 | build-ts 7 | releases 8 | .tmp 9 | .env 10 | -------------------------------------------------------------------------------- /fixtures/ts/ide/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "editorconfig.editorconfig", 4 | "dbaeumer.vscode-eslint", 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /fixtures/ts/ide/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "editor.formatOnPaste": true, 4 | "eslint.validate": [ 5 | "javascript", 6 | "typescript" 7 | ], 8 | "editor.codeActionsOnSave": { 9 | "source.fixAll.eslint": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /fixtures/ts/lightning-app/README.md: -------------------------------------------------------------------------------- 1 | # {$appName} 2 | 3 | ## {$appId} 4 | 5 | ### Getting started 6 | 7 | > Before you follow the steps below, make sure you have the 8 | [Lightning-CLI](https://rdkcentral.github.io/Lightning-CLI/#/) installed _globally_ only your system 9 | 10 | ``` 11 | npm install -g @lightningjs/cli 12 | ``` 13 | 14 | #### Running the App 15 | 16 | 1. Install the NPM dependencies by running `npm install` 17 | 18 | 2. Build the App using the _Lightning-CLI_ by running `lng build` inside the root of your project 19 | 20 | 3. Fire up a local webserver and open the App in a browser by running `lng serve` inside the root of your project 21 | 22 | #### Developing the App 23 | 24 | During development you can use the **watcher** functionality of the _Lightning-CLI_. 25 | 26 | - use `lng watch` to automatically _rebuild_ your App whenever you make a change in the `src` or `static` folder 27 | - use `lng dev` to start the watcher and run a local webserver / open the App in a browser _at the same time_ 28 | 29 | #### Documentation 30 | 31 | Use `lng docs` to open up the Lightning-SDK documentation. 32 | -------------------------------------------------------------------------------- /fixtures/ts/lightning-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{$appId}", 3 | "description": "{$appName}", 4 | "scripts": { 5 | "tsc": "tsc" 6 | }, 7 | "dependencies": { 8 | "@lightningjs/sdk": "{$sdkVersion}" 9 | }, 10 | "devDependencies": { 11 | "typescript": "^4.7.4" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /fixtures/ts/lightning-app/src/App.ts: -------------------------------------------------------------------------------- 1 | import { Lightning, Utils } from '@lightningjs/sdk' 2 | 3 | interface AppTemplateSpec extends Lightning.Component.TemplateSpec { 4 | Background: { 5 | Logo: object 6 | Mystery: object 7 | Text: object 8 | } 9 | } 10 | 11 | export class App 12 | extends Lightning.Component 13 | implements Lightning.Component.ImplementTemplateSpec 14 | { 15 | /* 16 | * The following properties exist to make it more convenient to access elements 17 | * below in a type-safe way. They are optional. 18 | * 19 | * See https://lightningjs.io/docs/#/lightning-core-reference/TypeScript/Components/TemplateSpecs?id=using-a-template-spec 20 | * for more information. 21 | */ 22 | readonly Background = this.getByRef('Background')! 23 | readonly Logo = this.Background.getByRef('Logo')! 24 | readonly Text = this.Background.getByRef('Text')! 25 | readonly Mystery = this.Background.getByRef('Mystery')! 26 | 27 | static override _template(): Lightning.Component.Template { 28 | return { 29 | w: 1920, 30 | h: 1080, 31 | Background: { 32 | w: 1920, 33 | h: 1080, 34 | color: 0xfffbb03b, 35 | src: Utils.asset('images/background.png'), 36 | Logo: { 37 | mountX: 0.5, 38 | mountY: 1, 39 | x: 960, 40 | y: 600, 41 | src: Utils.asset('images/logo.png'), 42 | }, 43 | Mystery: { 44 | x: 930, 45 | y: 400, 46 | w: 150, 47 | h: 150, 48 | scale: 0, 49 | src: Utils.asset('images/mystery.png'), 50 | }, 51 | Text: { 52 | mount: 0.5, 53 | x: 960, 54 | y: 720, 55 | text: { 56 | text: "Let's start Building!", 57 | fontFace: 'Regular', 58 | fontSize: 64, 59 | textColor: 0xbbffffff, 60 | }, 61 | }, 62 | }, 63 | } 64 | } 65 | 66 | static getFonts() { 67 | return [ 68 | { 69 | family: 'Regular', 70 | url: Utils.asset('fonts/Roboto-Regular.ttf') as string, 71 | }, 72 | ] 73 | } 74 | 75 | override _handleEnter() { 76 | this.Logo.setSmooth('scale', 2, { 77 | duration: 2.5, 78 | }) 79 | this.Text.setSmooth('y', 800, { 80 | duration: 2.5, 81 | }) 82 | this.Text.setSmooth('alpha', 0, { 83 | duration: 2.5, 84 | timingFunction: 'ease-out', 85 | }) 86 | this.Mystery.smooth = { 87 | x: 1025, 88 | y: 550, 89 | scale: 1, 90 | } 91 | } 92 | 93 | override _init() { 94 | this.stage.transitions.defaultTransitionSettings.duration = 3 95 | this.Background.animation({ 96 | duration: 15, 97 | repeat: -1, 98 | delay: 1, 99 | actions: [ 100 | { 101 | p: 'color', 102 | v: { 103 | 0: { v: 0xfffbb03b }, 104 | 0.5: { v: 0xfff46730 }, 105 | 0.8: { v: 0xfffbb03b }, 106 | }, 107 | }, 108 | ], 109 | }).start() 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /fixtures/ts/lightning-app/src/augmentations.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-empty-interface */ 2 | /** 3 | * This file defines all of the existing interfaces from Lightning Core and SDK that 4 | * may be added to (aka. augmented) by your Application. 5 | * 6 | * @module 7 | */ 8 | import '@lightningjs/sdk' 9 | 10 | declare module '@lightningjs/sdk' { 11 | /** 12 | * Lightning Core Augmentations 13 | */ 14 | namespace Lightning { 15 | namespace Component { 16 | /** 17 | * These handlers augmented here will be _added_ to the existing default key handlers 18 | * Be sure to name and type them appropriately. There should be 4 forms of each: 19 | * - `_capture{KeyName}` 20 | * - `_capture{KeyName}Release` 21 | * - `_handle{KeyName}` 22 | * - `_handle{KeyName}Release` 23 | */ 24 | interface DefaultKeyHandlers { 25 | // Examples: 26 | // _captureHome?(e: KeyboardEvent): boolean | void; 27 | // _captureHomeRelease?(e: KeyboardEvent): boolean | void; 28 | // _handleHome?(e: KeyboardEvent): boolean | void; 29 | // _handleHomeRelease?(e: KeyboardEvent): boolean | void; 30 | } 31 | 32 | /** 33 | * If any handlers are augmented here, the will _replace_ the default key handlers 34 | * declared in {@link DefaultKeyHandlers}. 35 | * 36 | * Use this if you have a radically different set of keys you'd like to orient your app around. 37 | */ 38 | interface CustomKeyHandlers { 39 | // Examples: 40 | // _captureHome?(e: KeyboardEvent): boolean | void; 41 | // _captureHomeRelease?(e: KeyboardEvent): boolean | void; 42 | // _handleHome?(e: KeyboardEvent): boolean | void; 43 | // _handleHomeRelease?(e: KeyboardEvent): boolean | void; 44 | } 45 | 46 | /** 47 | * Fire Ancestor Definitions 48 | */ 49 | interface FireAncestorsMap { 50 | // Examples: 51 | // $itemCreated(): void; 52 | // $firstItemCreated(): void; 53 | // $selectItem(arg: {item: ContentItem}): void; 54 | } 55 | } 56 | 57 | namespace Application { 58 | /** 59 | * Application Event Definitions (emitted from/onto the Lightning.Application instance) 60 | * 61 | * @remarks 62 | * These appear in 63 | */ 64 | interface EventMap { 65 | // Examples: 66 | // titleLoaded(): void; 67 | // ratingColor(color: number): void; 68 | // setBackground(evt: { src: string }): void; 69 | // contentHeight(height: number): void; 70 | // backgroundLoaded(): void; 71 | // readyForBackground(): void; 72 | // itemAnimationEnded(): void; 73 | // setItem(evt: { item: ContentItem, direction?: -1 | 0 | 1 }): void; 74 | // contentHidden(): void; 75 | } 76 | } 77 | } 78 | 79 | /** 80 | * Lightning SDK Router Augmentations 81 | */ 82 | namespace Router { 83 | /** 84 | * App-specifc Widgets Definitions 85 | * 86 | * @remarks 87 | * These appear in: 88 | * ```ts 89 | * anyRouterPage.widgets.menu; 90 | * Router.focusWidget('Menu'); 91 | * ``` 92 | */ 93 | interface CustomWidgets { 94 | // Examples: 95 | // Menu: typeof Menu; 96 | // Overlay: typeof OverlayComponent; 97 | } 98 | } 99 | 100 | /** 101 | * Lightning SDK Application Augmentations 102 | */ 103 | namespace Application { 104 | /** 105 | * AppData (Application SDK) definitions 106 | */ 107 | export interface AppData { 108 | // Examples: 109 | // myAppDataParam1: string; 110 | // myAppDataParam2: number; 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /fixtures/ts/lightning-app/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Lightning, Launch, PlatformSettings, AppData } from '@lightningjs/sdk' 2 | import { App } from './App.js' 3 | 4 | export default function ( 5 | appSettings: Lightning.Application.Options, 6 | platformSettings: PlatformSettings, 7 | appData: AppData, 8 | ) { 9 | return Launch(App, appSettings, platformSettings, appData) 10 | } 11 | -------------------------------------------------------------------------------- /fixtures/ts/lightning-app/static/images/mystery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdkcentral/Lightning-CLI/78916017be66006c69f91a03fbdaab265f345090/fixtures/ts/lightning-app/static/images/mystery.png -------------------------------------------------------------------------------- /fixtures/ts/lightning-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "build-ts", 4 | "target": "ES2019", 5 | "lib": ["ES2019", "DOM"], 6 | "moduleResolution": "node", 7 | "strict": true, 8 | "noUncheckedIndexedAccess": true, 9 | "noImplicitOverride": true 10 | }, 11 | "include": ["src/**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Michiel van der Geest ", 3 | "license": "Apache-2", 4 | "name": "@lightningjs/cli", 5 | "version": "2.14.2", 6 | "description": "Lightning-CLI: Command Line Interface tool for a seamless Lightning App Development flow", 7 | "bin": { 8 | "lightning": "./bin/index.js", 9 | "lng": "./bin/index.js" 10 | }, 11 | "scripts": { 12 | "test": "jest --runInBand --detectOpenHandles", 13 | "release": "npm publish --access public" 14 | }, 15 | "lint-staged": { 16 | "*.js": [ 17 | "eslint --fix" 18 | ] 19 | }, 20 | "husky": { 21 | "hooks": { 22 | "pre-commit": "lint-staged" 23 | } 24 | }, 25 | "dependencies": { 26 | "@babel/core": "^7.11.6", 27 | "@babel/plugin-proposal-class-properties": "^7.12.13", 28 | "@babel/plugin-transform-parameters": "^7.10.5", 29 | "@babel/plugin-transform-spread": "^7.11.0", 30 | "@babel/preset-env": "^7.11.5", 31 | "@babel/preset-typescript": "^7.13.0", 32 | "@rollup/plugin-alias": "^3.1.1", 33 | "@rollup/plugin-babel": "^5.2.1", 34 | "@rollup/plugin-commonjs": "^21.0.1", 35 | "@rollup/plugin-image": "^2.0.5", 36 | "@rollup/plugin-inject": "^4.0.2", 37 | "@rollup/plugin-json": "^4.1.0", 38 | "@rollup/plugin-node-resolve": "^9.0.0", 39 | "@rollup/plugin-virtual": "^2.0.3", 40 | "@rollup/plugin-typescript": "^11.1.1", 41 | "babel-plugin-inline-json-import": "^0.3.2", 42 | "chalk": "^4.1.0", 43 | "commander": "^10.0.0 ", 44 | "concat": "^1.0.3", 45 | "core-js": "^3.6.5", 46 | "deepmerge": "^4.3.1", 47 | "didyoumean2": "^4.1.0", 48 | "dotenv": "^16.0.3", 49 | "esbuild": "^0.19.3", 50 | "execa": "^4.0.3", 51 | "fs-extra": "^9.0.1", 52 | "http-server": "^14.1.0", 53 | "inquirer": "^7.3.3", 54 | "is-online": "^8.4.0", 55 | "latest-version": "^5.1.0", 56 | "ora": "^5.1.0", 57 | "replace-in-file": "^6.1.0", 58 | "rollup": "^2.28.2", 59 | "rollup-plugin-license": "^2.2.0", 60 | "rollup-plugin-terser": "^7.0.2", 61 | "semver": "^7.3.2", 62 | "shelljs": "^0.8.4", 63 | "socket.io": "^4.5.2", 64 | "watch": "^1.0.2" 65 | }, 66 | "devDependencies": { 67 | "@types/jest": "^29.4.0", 68 | "babel-eslint": "^10.1.0", 69 | "eslint": "^7.10.0", 70 | "eslint-config-prettier": "^6.12.0", 71 | "eslint-plugin-prettier": "^3.1.4", 72 | "husky": "^4.3.0", 73 | "jest": "^29.5.0", 74 | "jest-html-reporters": "^3.1.4", 75 | "jest-image-snapshot": "^6.1.0", 76 | "lint-staged": "^10.4.0", 77 | "prettier": "^1.19.1", 78 | "puppeteer": "^19.7.5" 79 | }, 80 | "jest": { 81 | "setupFiles": [ 82 | "./tests/global_configs/globalMocks.js" 83 | ], 84 | "testSequencer": "./tests/global_configs/runSequence.js", 85 | "globalSetup": "./tests/global_configs/globalSetup.js", 86 | "globalTeardown": "./tests/global_configs/globalTeardown.js", 87 | "collectCoverage": false, 88 | "coverageDirectory": "./tests_coverage", 89 | "coverageReporters": [ 90 | "html" 91 | ], 92 | "collectCoverageFrom": [ 93 | "**/src/**", 94 | "!**/fixtures/**" 95 | ], 96 | "reporters": [ 97 | "default", 98 | [ 99 | "jest-html-reporters", 100 | { 101 | "pageTitle": "Lightning-CLI Test Result", 102 | "expand": true, 103 | "hideIcon": true, 104 | "publicPath": "./tests_report", 105 | "filename": "report.html", 106 | "inlineSource": true, 107 | "openReport": false, 108 | "includeConsoleLog": true 109 | } 110 | ] 111 | ] 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/actions/build.js: -------------------------------------------------------------------------------- 1 | /* 2 | * If not stated otherwise in this file or this component's LICENSE file the 3 | * following copyright and licenses apply: 4 | * 5 | * Copyright 2020 Metrological 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the License); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | const path = require('path') 21 | const sequence = require('../helpers/sequence') 22 | const buildHelpers = require('../helpers/build') 23 | 24 | module.exports = (clear = false, change = null, types = ['default'], bundlerOptions) => { 25 | const targetDir = path.join(process.cwd(), process.env.LNG_BUILD_FOLDER || 'build') 26 | 27 | let settingsFileName = buildHelpers.getSettingsFileName() 28 | let metadata 29 | let settings 30 | 31 | const buildES = (test, esEnv, types) => { 32 | return types.includes('default') && esEnv 33 | ? (clear || change === 'src') && esEnv === test 34 | : types.includes('default') 35 | ? test === 'es6' 36 | : types.includes(test) 37 | } 38 | 39 | return sequence([ 40 | () => buildHelpers.ensureLightningApp(), 41 | () => clear && buildHelpers.ensureCorrectGitIgnore(), 42 | () => clear && buildHelpers.ensureCorrectSdkDependency(), 43 | () => clear && buildHelpers.removeFolder(targetDir), 44 | () => buildHelpers.ensureFolderExists(targetDir), 45 | () => clear && buildHelpers.copySupportFiles(targetDir), 46 | () => (clear || change === 'static') && buildHelpers.copyStaticFolder(targetDir), 47 | () => 48 | (clear || change === 'settings') && buildHelpers.copySettings(settingsFileName, targetDir), 49 | () => (clear || change === 'metadata') && buildHelpers.copyMetadata(targetDir), 50 | () => buildHelpers.readMetadata().then(result => (metadata = result)), 51 | () => buildHelpers.readSettings(settingsFileName).then(result => (settings = result)), 52 | () => 53 | (clear || change === 'src') && 54 | buildES('es6', settings.platformSettings.esEnv, types) && 55 | buildHelpers.bundleEs6App(targetDir, metadata, bundlerOptions), 56 | () => 57 | (clear || change === 'src') && 58 | buildES('es5', settings.platformSettings.esEnv, types) && 59 | buildHelpers.bundleEs5App(targetDir, metadata, bundlerOptions), 60 | ]) 61 | } 62 | -------------------------------------------------------------------------------- /src/actions/create.js: -------------------------------------------------------------------------------- 1 | /* 2 | * If not stated otherwise in this file or this component's LICENSE file the 3 | * following copyright and licenses apply: 4 | * 5 | * Copyright 2020 Metrological 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the License); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | const path = require('path') 21 | const fs = require('fs-extra') 22 | const replaceInFile = require('replace-in-file') 23 | const execa = require('execa') 24 | const chalk = require('chalk') 25 | 26 | const sequence = require('../helpers/sequence') 27 | const ask = require('../helpers/ask') 28 | const exit = require('../helpers/exit') 29 | const spinner = require('../helpers/spinner') 30 | 31 | const commonFixturesPath = path.join(__dirname, '../../fixtures/common') 32 | /******* Questions *******/ 33 | 34 | const askAppName = () => 35 | sequence([ 36 | () => ask('What is the name of your Lightning App?', 'My Awesome App'), 37 | appName => validateAppName(appName), 38 | ]) 39 | 40 | const askAppId = appName => 41 | sequence([ 42 | () => 43 | ask( 44 | 'What is the App identifier? (reverse-DNS format)', 45 | `com.domain.app.${appName.replace(/[^A-Z0-9]/gi, '')}` 46 | ), 47 | appId => validateAppId(appId), 48 | ]) 49 | 50 | const askAppFolder = appId => 51 | sequence([ 52 | () => 53 | ask( 54 | 'In what (relative) folder do you want to create the new App? (leave empty to create in current working dir)', 55 | appId 56 | ), 57 | appFolder => validateAppFolder(appFolder), 58 | ]) 59 | 60 | const askTypeScript = () => 61 | ask('Do you want to write your App in TypeScript?', null, 'list', ['No', 'Yes']).then( 62 | // map yes to true and no to false 63 | val => val === 'Yes' 64 | ) 65 | 66 | const askESlint = () => 67 | ask('Do you want to enable ESlint?', null, 'list', ['Yes', 'No']).then( 68 | // map yes to true and no to false 69 | val => val === 'Yes' 70 | ) 71 | 72 | const askNpmInstall = () => 73 | ask('Do you want to install the NPM dependencies now?', null, 'list', ['Yes', 'No']).then( 74 | // map yes to true and no to false 75 | val => val === 'Yes' 76 | ) 77 | 78 | const askGitInit = () => 79 | ask('Do you want to initialize an empty GIT repository?', null, 'list', ['Yes', 'No']).then( 80 | // map yes to true and no to false 81 | val => val === 'Yes' 82 | ) 83 | 84 | const askConfig = async () => { 85 | const config = {} 86 | return sequence([ 87 | () => askAppName().then(appName => (config.appName = appName)), 88 | () => askAppId(config.appName).then(appId => (config.appId = appId)), 89 | () => askAppFolder(config.appId).then(folder => (config.appFolder = folder)), 90 | () => 91 | askTypeScript().then( 92 | useTypeScript => 93 | (config.fixturesBase = path.join( 94 | __dirname, 95 | '../../fixtures', 96 | useTypeScript ? 'ts' : 'js' 97 | )) 98 | ), 99 | () => askESlint().then(eslint => (config.eslint = eslint)), 100 | () => config, 101 | ]) 102 | } 103 | 104 | const askInstall = config => { 105 | return sequence([ 106 | () => askNpmInstall().then(npmInstall => (config.npmInstall = npmInstall)), 107 | () => askGitInit().then(gitInit => (config.gitInit = gitInit)), 108 | () => config, 109 | ]) 110 | } 111 | 112 | /******* validations *******/ 113 | 114 | const validateAppId = appId => { 115 | if (!appId) { 116 | exit('Please provide an app ID') 117 | } 118 | // todo: add possible pre-processing 119 | // todo: validate if appId matches the requirements 120 | // todo: validate if appId isn't taken yet (in backoffice) 121 | return appId 122 | } 123 | 124 | const validateAppName = appName => { 125 | if (!appName) { 126 | exit('Please provide an app Name') 127 | } 128 | // todo: add possible pre-processing 129 | return appName 130 | } 131 | 132 | const validateAppFolder = folder => { 133 | // todo: validate if folder is correct path / doesn't exist etc. 134 | return folder 135 | } 136 | 137 | /******* Actions *******/ 138 | 139 | const copyLightningFixtures = config => { 140 | return new Promise(resolve => { 141 | const targetDir = path.join(process.cwd(), config.appFolder || '') 142 | if (config.appFolder && fs.pathExistsSync(targetDir)) { 143 | exit('The target directory ' + targetDir + ' already exists') 144 | } 145 | fs.copySync(path.join(config.fixturesBase, 'lightning-app'), targetDir) 146 | fs.copySync(path.join(commonFixturesPath, 'lightning-app'), path.join(targetDir)) 147 | resolve(targetDir) 148 | }) 149 | } 150 | 151 | const setAppData = config => { 152 | replaceInFile.sync({ 153 | files: config.targetDir + '/*', 154 | from: /\{\$appId\}/g, 155 | to: config.appId, 156 | }) 157 | 158 | replaceInFile.sync({ 159 | files: config.targetDir + '/*', 160 | from: /\{\$appName\}/g, 161 | to: config.appName, 162 | }) 163 | } 164 | 165 | const setSdkVersion = config => { 166 | return new Promise((resolve, reject) => { 167 | execa('npm', ['view', '@lightningjs/sdk', 'version']) 168 | .then(({ stdout }) => { 169 | replaceInFile.sync({ 170 | files: config.targetDir + '/*', 171 | from: /\{\$sdkVersion\}/g, 172 | to: '^' + stdout, 173 | }) 174 | resolve() 175 | }) 176 | .catch(e => { 177 | spinner.fail(`Error occurred while setting sdk version\n\n${e}`) 178 | reject() 179 | }) 180 | }) 181 | } 182 | 183 | const addESlint = config => { 184 | // Make husky dir 185 | fs.mkdirSync(path.join(config.targetDir, '.husky'), { recursive: true }) 186 | 187 | // Copy husky hook 188 | fs.copyFileSync( 189 | path.join(commonFixturesPath, 'eslint/husky/pre-commit'), 190 | path.join(config.targetDir, '.husky/pre-commit') 191 | ) 192 | 193 | // Copy editor config from common 194 | fs.copyFileSync( 195 | path.join(commonFixturesPath, 'eslint/editorconfig'), 196 | path.join(config.targetDir, '.editorconfig') 197 | ) 198 | 199 | // Copy eslintignore from common 200 | fs.copyFileSync( 201 | path.join(commonFixturesPath, 'eslint/eslintignore'), 202 | path.join(config.targetDir, '.eslintignore') 203 | ) 204 | 205 | // Copy eslintrc.js from fixtured specfic directory 206 | fs.copyFileSync( 207 | path.join(config.fixturesBase, 'eslint/eslintrc.js'), 208 | path.join(config.targetDir, '.eslintrc.js') 209 | ) 210 | 211 | // Copy IDE stuff from fixture base 212 | fs.copySync(path.join(config.fixturesBase, 'ide'), path.join(config.targetDir)) 213 | 214 | // Copy and merge fixture specific package.json 215 | const origPackageJson = JSON.parse(fs.readFileSync(path.join(config.targetDir, 'package.json'))) 216 | const eslintPackageJson = JSON.parse( 217 | fs.readFileSync(path.join(config.fixturesBase, 'eslint/package.json')) 218 | ) 219 | fs.writeFileSync( 220 | path.join(config.targetDir, 'package.json'), 221 | JSON.stringify( 222 | { 223 | ...origPackageJson, 224 | ...eslintPackageJson, 225 | devDependencies: { 226 | ...(origPackageJson.devDependencies || {}), 227 | ...(eslintPackageJson.devDependencies || {}), 228 | }, 229 | }, 230 | null, 231 | 2 232 | ) 233 | ) 234 | 235 | return true 236 | } 237 | 238 | const createApp = config => { 239 | spinner.start('Creating Lightning App ' + config.appName) 240 | return sequence([ 241 | () => copyLightningFixtures(config).then(targetDir => (config.targetDir = targetDir)), 242 | () => setAppData(config), 243 | () => setSdkVersion(config), 244 | () => config.eslint && addESlint(config), 245 | () => 246 | new Promise(resolve => { 247 | setTimeout(() => { 248 | spinner.succeed() 249 | resolve() 250 | }, 2000) 251 | }), 252 | () => config, 253 | ]) 254 | } 255 | 256 | const npmInstall = cwd => { 257 | spinner.start('Installing NPM dependencies') 258 | return execa('npm', ['install'], { cwd }) 259 | .then(() => spinner.succeed('NPM dependencies installed')) 260 | .catch(e => spinner.fail(`Error occurred while installing npm dependencies\n\n${e}`)) 261 | } 262 | 263 | const gitInit = (cwd, fixturesBase) => { 264 | spinner.start('Initializing empty GIT repository') 265 | let msg 266 | return execa('git', ['init'], { cwd }) 267 | .then(({ stdout }) => (msg = stdout)) 268 | .then(() => { 269 | return fs.copyFileSync(path.join(fixturesBase, 'git/gitignore'), path.join(cwd, '.gitignore')) 270 | }) 271 | .then(() => spinner.succeed(msg)) 272 | .catch(e => spinner.fail(`Error occurred while creating git repository\n\n${e}`)) 273 | } 274 | 275 | const install = config => { 276 | return sequence([ 277 | () => config.gitInit && gitInit(config.targetDir, config.fixturesBase), 278 | () => config.npmInstall && npmInstall(config.targetDir), 279 | () => config, 280 | ]) 281 | } 282 | 283 | /******* Logs *******/ 284 | 285 | const done = config => { 286 | const label = '⚡️ "' + config.appName + '" successfully created! ⚡️' 287 | 288 | console.log(' ') 289 | console.log('='.repeat(label.length)) 290 | console.log(label) 291 | console.log('='.repeat(label.length)) 292 | console.log(' ') 293 | 294 | console.log('👉 Get started with the following commands:') 295 | console.log(' ') 296 | config.appFolder && 297 | console.log(' ' + chalk.grey('$') + chalk.yellow(' cd ' + chalk.underline(config.appFolder))) 298 | console.log(' ' + chalk.grey('$') + chalk.yellow(' lng build')) 299 | console.log(' ' + chalk.grey('$') + chalk.yellow(' lng serve')) 300 | console.log(' ') 301 | 302 | return config 303 | } 304 | 305 | module.exports = () => { 306 | return new Promise(resolve => { 307 | sequence([ 308 | askConfig, 309 | config => createApp(config), 310 | config => askInstall(config), 311 | config => install(config), 312 | config => done(config), 313 | config => resolve(config), 314 | ]) 315 | }) 316 | } 317 | -------------------------------------------------------------------------------- /src/actions/dev.js: -------------------------------------------------------------------------------- 1 | /* 2 | * If not stated otherwise in this file or this component's LICENSE file the 3 | * following copyright and licenses apply: 4 | * 5 | * Copyright 2020 Metrological 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the License); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | const watch = require('./watch') 21 | const serve = require('./serve') 22 | const sequence = require('../helpers/sequence') 23 | const buildHelpers = require('../helpers/build') 24 | 25 | module.exports = () => { 26 | return sequence([ 27 | () => buildHelpers.ensureLightningApp(), 28 | () => { 29 | return watch(serve, () => { 30 | console.log('') 31 | if (process.env.LNG_LIVE_RELOAD === 'true') { 32 | console.log('Navigate to web browser to see the changes') 33 | } else { 34 | console.log('Reload your web browser to see the changes') 35 | } 36 | console.log('') 37 | }) 38 | }, 39 | ]) 40 | } 41 | -------------------------------------------------------------------------------- /src/actions/dist.js: -------------------------------------------------------------------------------- 1 | /* 2 | * If not stated otherwise in this file or this component's LICENSE file the 3 | * following copyright and licenses apply: 4 | * 5 | * Copyright 2020 Metrological 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the License); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | const path = require('path') 21 | const fs = require('fs') 22 | const sequence = require('../helpers/sequence') 23 | const buildHelpers = require('../helpers/build') 24 | const distHelpers = require('../helpers/dist') 25 | const distWatch = require('../helpers/distWatch') 26 | 27 | module.exports = (options, bundlerConfig) => { 28 | const baseDistDir = path.join(process.cwd(), process.env.LNG_DIST_FOLDER || 'dist') 29 | 30 | let metadata 31 | let settingsFileName = buildHelpers.getSettingsFileName() 32 | let settings 33 | 34 | const buildES = (type, esEnv) => { 35 | return !!(type || esEnv) 36 | } 37 | 38 | const dist = (type, config) => { 39 | let distDir 40 | return sequence([ 41 | () => buildHelpers.ensureLightningApp(), 42 | () => distHelpers.moveOldDistFolderToBuildFolder(), 43 | () => buildHelpers.ensureCorrectGitIgnore(), 44 | () => buildHelpers.readMetadata().then(result => (metadata = result)), 45 | () => buildHelpers.readSettings(settingsFileName).then(result => (settings = result)), 46 | () => 47 | (type = !type.includes('defaults') 48 | ? type 49 | : settings.platformSettings.esEnv 50 | ? settings.platformSettings.esEnv 51 | : 'es6'), 52 | () => { 53 | distDir = path.join(baseDistDir, type) 54 | }, 55 | () => { 56 | if (!fs.existsSync(distDir)) { 57 | return sequence([ 58 | () => buildHelpers.ensureFolderExists(distDir), 59 | () => buildHelpers.ensureFolderExists(path.join(distDir, 'js')), 60 | () => distHelpers.setupDistFolder(distDir, type, metadata), 61 | ]) 62 | } 63 | return true 64 | }, 65 | () => buildHelpers.removeFolder(path.join(distDir, 'static')), 66 | () => buildHelpers.copyStaticFolder(distDir), 67 | () => 68 | type === 'es6' && 69 | buildES('es6', settings.platformSettings.esEnv) && 70 | buildHelpers.bundleEs6App(path.join(distDir, 'js'), metadata, bundlerConfig), 71 | () => 72 | type === 'es5' && 73 | buildES('es5', settings.platformSettings.esEnv) && 74 | buildHelpers.bundleEs5App(path.join(distDir, 'js'), metadata, bundlerConfig), 75 | () => type === 'es5' && buildHelpers.bundlePolyfills(path.join(distDir, 'js')), 76 | () => config.isWatchEnabled && distWatch(type), 77 | ]) 78 | } 79 | 80 | // execute the dist function for all types 81 | return options.types.reduce((promise, type) => { 82 | return promise 83 | .then(function() { 84 | return dist(type, options) 85 | }) 86 | .catch(e => Promise.reject(e)) 87 | }, Promise.resolve(null)) 88 | } 89 | -------------------------------------------------------------------------------- /src/actions/docs.js: -------------------------------------------------------------------------------- 1 | /* 2 | * If not stated otherwise in this file or this component's LICENSE file the 3 | * following copyright and licenses apply: 4 | * 5 | * Copyright 2020 Metrological 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the License); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | const execa = require('execa') 21 | const chalk = require('chalk') 22 | const path = require('path') 23 | const buildHelpers = require('../helpers/build') 24 | const isLocallyInstalled = require('../helpers/localinstallationcheck') 25 | const sequence = require('../helpers/sequence') 26 | 27 | module.exports = () => { 28 | return new Promise(resolve => { 29 | return sequence([ 30 | () => buildHelpers.ensureLightningApp(), 31 | () => { 32 | console.log(chalk.green('Serving the Lightning-SDK documentation\n\n')) 33 | const docFolderPath = buildHelpers.hasNewSDK() 34 | ? 'node_modules/@lightningjs/sdk/docs' 35 | : 'node_modules/wpe-lightning-sdk/docs' 36 | const documentsPath = buildHelpers.findFile(process.cwd(), docFolderPath) 37 | const args = [documentsPath, '-o', '-c-1'] 38 | 39 | const levelsDown = isLocallyInstalled() 40 | ? buildHelpers.findFile(process.cwd(), 'node_modules/.bin/http-server') 41 | : path.join(__dirname, '../..', 'node_modules/.bin/http-server') 42 | 43 | const subprocess = execa(levelsDown, args) 44 | subprocess.catch(e => console.log(chalk.red(e.stderr))) 45 | subprocess.stdout.pipe(process.stdout) 46 | 47 | subprocess.stdout.on('data', data => { 48 | if (/Open:/.test(data)) { 49 | const url = data 50 | .toString() 51 | .match(/https?:\/\/(?:[0-9]{1,3}\.){3}[0-9]{1,3}:[0-9]{1,5}(?=\s*$)/)[0] 52 | resolve({ 53 | process: subprocess, 54 | config: { 55 | url: url, 56 | LNG_BUILD_FOLDER: args[0], 57 | LNG_SERVE_OPEN: args[1], 58 | LNG_SERVE_CACHE_TIME: args[2], 59 | LNG_SERVE_PORT: args[3], 60 | LNG_SERVE_PROXY: args[4], 61 | }, 62 | }) 63 | } 64 | }) 65 | }, 66 | ]) 67 | }) 68 | } 69 | -------------------------------------------------------------------------------- /src/actions/serve.js: -------------------------------------------------------------------------------- 1 | /* 2 | * If not stated otherwise in this file or this component's LICENSE file the 3 | * following copyright and licenses apply: 4 | * 5 | * Copyright 2020 Metrological 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the License); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | const execa = require('execa') 21 | const path = require('path') 22 | const chalk = require('chalk') 23 | const os = require('os') 24 | const { exec, execSync } = require('child_process') 25 | const isLocallyInstalled = require('../helpers/localinstallationcheck') 26 | const buildHelpers = require('../helpers/build') 27 | const sequence = require('../helpers/sequence') 28 | 29 | module.exports = () => { 30 | return new Promise(resolve => { 31 | return sequence([ 32 | () => buildHelpers.ensureLightningApp(), 33 | () => { 34 | const args = [ 35 | process.env.LNG_BUILD_FOLDER ? `./${process.env.LNG_BUILD_FOLDER}` : './build', 36 | process.env.LNG_SERVE_OPEN === 'false' ? false : '-o', 37 | process.env.LNG_SERVE_CACHE_TIME ? '-c' + process.env.LNG_SERVE_CACHE_TIME : '-c-1', 38 | process.env.LNG_SERVE_PORT ? '-p' + process.env.LNG_SERVE_PORT : false, 39 | process.env.LNG_SERVE_PROXY ? '-P' + process.env.LNG_SERVE_PROXY : false, 40 | process.env.LNG_SERVE_CORS && process.env.LNG_SERVE_CORS !== 'false' 41 | ? process.env.LNG_SERVE_CORS === 'true' 42 | ? '--cors' 43 | : '--cors=' + process.env.LNG_SERVE_CORS 44 | : '', 45 | ].filter(val => val) 46 | 47 | const levelsDown = isLocallyInstalled() 48 | ? buildHelpers.findFile(process.cwd(), 'node_modules/.bin/http-server') 49 | : path.join(__dirname, '../..', 'node_modules/.bin/http-server') 50 | 51 | const subprocess = execa(levelsDown, args) 52 | 53 | subprocess.catch(e => console.log(chalk.red(e.stderr))) 54 | subprocess.stdout.pipe(process.stdout) 55 | 56 | // Hack for windows to prevent leaving orphan processes, resulting in multiple http-server running instances 57 | if (os.platform() === 'win32') { 58 | process.on('SIGINT', () => { 59 | const task = 'taskkill /pid ' + subprocess.pid + ' /t /f' 60 | if (process.env.LNG_LIVE_RELOAD) { 61 | execSync(task) 62 | } else { 63 | exec(task, () => { 64 | process.exit() 65 | }) 66 | } 67 | }) 68 | } 69 | 70 | subprocess.stdout.on('data', data => { 71 | if (/Hit CTRL-C to stop the server/.test(data)) { 72 | const url = data 73 | .toString() 74 | .match(/(http|https):\/\/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\d+)/)[0] 75 | resolve({ 76 | process: subprocess, 77 | config: { 78 | url: url, 79 | LNG_BUILD_FOLDER: args[0], 80 | LNG_SERVE_OPEN: args[1], 81 | LNG_SERVE_CACHE_TIME: args[2], 82 | LNG_SERVE_PORT: args[3], 83 | LNG_SERVE_PROXY: args[4], 84 | }, 85 | }) 86 | } 87 | }) 88 | 89 | return subprocess 90 | }, 91 | ]) 92 | }) 93 | } 94 | -------------------------------------------------------------------------------- /src/actions/watch.js: -------------------------------------------------------------------------------- 1 | /* 2 | * If not stated otherwise in this file or this component's LICENSE file the 3 | * following copyright and licenses apply: 4 | * 5 | * Copyright 2020 Metrological 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the License); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | const watch = require('watch') 21 | const build = require('./build') 22 | const chalk = require('chalk') 23 | const buildHelpers = require('../helpers/build') 24 | const { Server } = require('socket.io') 25 | 26 | const settingsFileName = buildHelpers.getSettingsFileName() // Get settings file name 27 | const regexp = new RegExp(`^(?!src|static|${settingsFileName}|metadata.json)(.+)$`) 28 | 29 | let wss 30 | 31 | const initWebSocketServer = () => { 32 | const port = process.env.LNG_LIVE_RELOAD_PORT || 8991 33 | const server = new Server(port, { 34 | cors: { 35 | origin: '*', 36 | }, 37 | }) 38 | server.on('error', e => { 39 | if (e.code === 'EADDRINUSE') { 40 | console.log(chalk.red(chalk.underline(`Process already running on port: ${port}`))) 41 | } 42 | }) 43 | 44 | process.on('SIGINT', () => { 45 | server.close() 46 | process.exit() 47 | }) 48 | 49 | return server 50 | } 51 | 52 | module.exports = (initCallback, watchCallback) => { 53 | let busy = false 54 | return new Promise((resolve, reject) => 55 | watch.watchTree( 56 | './', 57 | { 58 | interval: 1, 59 | filter(f) { 60 | return !!!regexp.test(f) 61 | }, 62 | ignoreDirectoryPattern: /node_modules|\.git|dist|build/, 63 | }, 64 | (f, curr, prev) => { 65 | // prevent initiating another build when already busy 66 | if (busy === true) { 67 | return 68 | } 69 | if (typeof f == 'object' && prev === null && curr === null) { 70 | build(true) 71 | .then(() => { 72 | initCallback && initCallback().catch(() => process.exit()) 73 | // if configured start WebSocket Server 74 | if (process.env.LNG_LIVE_RELOAD === 'true') { 75 | wss = initWebSocketServer() 76 | } 77 | 78 | resolve(watch) 79 | }) 80 | .catch(e => { 81 | reject(e) 82 | }) 83 | } else { 84 | busy = true 85 | 86 | // pass the 'type of change' based on the file that was changes 87 | let change 88 | if (/^src/g.test(f)) { 89 | change = 'src' 90 | } 91 | if (/^static/g.test(f)) { 92 | change = 'static' 93 | } 94 | if (f === 'metadata.json') { 95 | change = 'metadata' 96 | } 97 | if (f === settingsFileName) { 98 | change = 'settings' 99 | } 100 | 101 | build(false, change) 102 | .then(() => { 103 | busy = false 104 | 105 | watchCallback && watchCallback() 106 | // send reload signal over socket 107 | if (wss) { 108 | wss.sockets.emit('reload') 109 | } 110 | resolve(watch) 111 | }) 112 | .catch(e => { 113 | reject(e) 114 | }) 115 | } 116 | } 117 | ) 118 | ) 119 | } 120 | -------------------------------------------------------------------------------- /src/alias/lightningjs-core.js: -------------------------------------------------------------------------------- 1 | /* 2 | * If not stated otherwise in this file or this component's LICENSE file the 3 | * following copyright and licenses apply: 4 | * 5 | * Copyright 2020 Metrological 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the License); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | export default window.lng 21 | -------------------------------------------------------------------------------- /src/alias/wpe-lightning.js: -------------------------------------------------------------------------------- 1 | /* 2 | * If not stated otherwise in this file or this component's LICENSE file the 3 | * following copyright and licenses apply: 4 | * 5 | * Copyright 2020 Metrological 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the License); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | export default window.lng 21 | -------------------------------------------------------------------------------- /src/configs/esbuild.es5.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * If not stated otherwise in this file or this component's LICENSE file the 3 | * following copyright and licenses apply: 4 | * 5 | * Copyright 2020 Metrological 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the License); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | const buildHelpers = require('../helpers/build') 21 | const alias = require('../plugins/esbuild-alias') 22 | const babel = require('../helpers/esbuildbabel') 23 | const babelPresetTypescript = require('@babel/preset-typescript') 24 | const os = require('os') 25 | const path = require('path') 26 | const babelPresetEnv = require('@babel/preset-env') 27 | const babelPluginTransFormSpread = require('@babel/plugin-transform-spread') 28 | const babelPluginTransFormParameters = require('@babel/plugin-transform-parameters') 29 | const babelPluginClassProperties = require('@babel/plugin-proposal-class-properties') 30 | const babelPluginInlineJsonImport = require('babel-plugin-inline-json-import') 31 | const deepMerge = require('deepmerge') 32 | const fs = require('fs') 33 | const chalk = require('chalk') 34 | 35 | let customConfig 36 | 37 | if (process.env.LNG_CUSTOM_ESBUILD === 'true') { 38 | const customConfigPath = path.join(process.cwd(), 'esbuild.es5.config.js') 39 | if (fs.existsSync(customConfigPath)) { 40 | customConfig = require(customConfigPath) 41 | } else { 42 | console.warn( 43 | chalk.yellow('\nCustom esbuild config not found while LNG_CUSTOM_ESBUILD is set to true') 44 | ) 45 | } 46 | } 47 | 48 | module.exports = (folder, globalName) => { 49 | // Load .env config every time build is triggered 50 | const appVars = { 51 | NODE_ENV: process.env.NODE_ENV, 52 | ...buildHelpers.getEnvAppVars(process.env), 53 | } 54 | const keys = Object.keys(appVars) 55 | const defined = keys.reduce((acc, key) => { 56 | acc[`process.env.${key}`] = `"${appVars[key]}"` 57 | return acc 58 | }, {}) 59 | defined['process.env.NODE_ENV'] = `"${process.env.NODE_ENV}"` 60 | const minify = process.env.LNG_BUILD_MINIFY === 'true' || process.env.NODE_ENV === 'production' 61 | 62 | let defaultConfig = { 63 | plugins: [ 64 | alias([ 65 | { find: '@', filter: /@\//, replace: path.resolve(process.cwd(), 'src/') }, 66 | { find: '~', filter: /~\//, replace: path.resolve(process.cwd(), 'node_modules/') }, 67 | { 68 | find: '@lightningjs/core', 69 | filter: /^@lightningjs\/core$/, 70 | replace: path.join(__dirname, '../alias/lightningjs-core.js'), 71 | }, 72 | { 73 | find: 'wpe-lightning', 74 | filter: /^wpe-lightning$/, 75 | replace: path.join(__dirname, '../alias/wpe-lightning.js'), 76 | }, 77 | ]), 78 | babel({ 79 | config: { 80 | presets: [ 81 | [ 82 | babelPresetEnv, 83 | { 84 | targets: { 85 | chrome: '39', 86 | }, 87 | debug: false, 88 | useBuiltIns: 'entry', 89 | corejs: '^3.6.5', 90 | }, 91 | ], 92 | [babelPresetTypescript], 93 | ], 94 | plugins: [ 95 | babelPluginClassProperties, 96 | babelPluginTransFormSpread, 97 | babelPluginTransFormParameters, 98 | babelPluginInlineJsonImport, 99 | ], 100 | }, 101 | }), 102 | ], 103 | logLevel: 'silent', 104 | minifyWhitespace: minify, 105 | minifyIdentifiers: minify, 106 | minifySyntax: false, 107 | keepNames: minify, 108 | entryPoints: [`${process.cwd()}/src/index.js`], 109 | bundle: true, 110 | target: 'es5', 111 | mainFields: buildHelpers.getResolveConfigForBundlers(), 112 | outfile: `${folder}/appBundle.es5.js`, 113 | sourcemap: 114 | process.env.NODE_ENV === 'production' 115 | ? 'external' 116 | : process.env.LNG_BUILD_SOURCEMAP === 'inline' 117 | ? 'inline' 118 | : process.env.LNG_BUILD_SOURCEMAP === 'false' 119 | ? '' 120 | : 'external', 121 | format: 'iife', 122 | define: defined, 123 | globalName, 124 | banner: { 125 | js: [ 126 | '/*', 127 | ` App version: ${buildHelpers.getAppVersion()}`, 128 | ` SDK version: ${buildHelpers.getSdkVersion()}`, 129 | ` CLI version: ${buildHelpers.getCliVersion()}`, 130 | '', 131 | ` gmtDate: ${new Date().toGMTString()}`, 132 | '*/', 133 | ].join(os.EOL), 134 | }, 135 | } 136 | if (customConfig && 'entryPoints' in customConfig) { 137 | delete defaultConfig.entryPoints 138 | } 139 | const finalConfig = customConfig ? deepMerge(defaultConfig, customConfig) : defaultConfig 140 | return finalConfig 141 | } 142 | -------------------------------------------------------------------------------- /src/configs/esbuild.es6.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * If not stated otherwise in this file or this component's LICENSE file the 3 | * following copyright and licenses apply: 4 | * 5 | * Copyright 2020 Metrological 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the License); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | const buildHelpers = require('../helpers/build') 21 | const os = require('os') 22 | const alias = require('../plugins/esbuild-alias') 23 | const babel = require('../helpers/esbuildbabel') 24 | const babelPresetTypescript = require('@babel/preset-typescript') 25 | const babelPresetEnv = require('@babel/preset-env') 26 | const path = require('path') 27 | const babelPluginClassProperties = require('@babel/plugin-proposal-class-properties') 28 | const babelPluginInlineJsonImport = require('babel-plugin-inline-json-import') 29 | const deepMerge = require('deepmerge') 30 | const fs = require('fs') 31 | const chalk = require('chalk') 32 | 33 | let customConfig 34 | 35 | if (process.env.LNG_CUSTOM_ESBUILD === 'true') { 36 | const customConfigPath = path.join(process.cwd(), 'esbuild.es6.config.js') 37 | if (fs.existsSync(customConfigPath)) { 38 | customConfig = require(customConfigPath) 39 | } else { 40 | console.warn( 41 | chalk.yellow('\nCustom esbuild config not found while LNG_CUSTOM_ESBUILD is set to true') 42 | ) 43 | } 44 | } 45 | 46 | module.exports = (folder, globalName) => { 47 | //Load .env config every time build is triggered 48 | const appVars = { 49 | NODE_ENV: process.env.NODE_ENV, 50 | ...buildHelpers.getEnvAppVars(process.env), 51 | } 52 | const keys = Object.keys(appVars) 53 | const defined = keys.reduce((acc, key) => { 54 | acc[`process.env.${key}`] = `"${appVars[key]}"` 55 | return acc 56 | }, {}) 57 | defined['process.env.NODE_ENV'] = `"${process.env.NODE_ENV}"` 58 | const minify = process.env.LNG_BUILD_MINIFY === 'true' || process.env.NODE_ENV === 'production' 59 | let defaultConfig = { 60 | plugins: [ 61 | alias([ 62 | { 63 | find: 'wpe-lightning', 64 | filter: /^wpe-lightning$/, 65 | replace: path.join(__dirname, '../alias/wpe-lightning.js'), 66 | }, 67 | { 68 | find: '@lightningjs/core', 69 | filter: /^@lightningjs\/core$/, 70 | replace: path.join(__dirname, '../alias/lightningjs-core.js'), 71 | }, 72 | { find: '@', filter: /@\//, replace: path.resolve(process.cwd(), 'src/') }, 73 | { find: '~', filter: /~\//, replace: path.resolve(process.cwd(), 'node_modules/') }, 74 | ]), 75 | babel({ 76 | config: { 77 | presets: [ 78 | [ 79 | babelPresetEnv, 80 | { 81 | targets: { 82 | safari: '12.0', 83 | }, 84 | }, 85 | ], 86 | [babelPresetTypescript], 87 | ], 88 | plugins: [babelPluginClassProperties, babelPluginInlineJsonImport], 89 | }, 90 | }), 91 | ], 92 | logLevel: 'silent', 93 | minifyWhitespace: minify, 94 | minifyIdentifiers: minify, 95 | minifySyntax: false, 96 | keepNames: minify, 97 | entryPoints: [`${process.cwd()}/src/index.js`], 98 | bundle: true, 99 | outfile: `${folder}/appBundle.js`, 100 | mainFields: buildHelpers.getResolveConfigForBundlers(), 101 | sourcemap: 102 | process.env.NODE_ENV === 'production' 103 | ? 'external' 104 | : process.env.LNG_BUILD_SOURCEMAP === 'inline' 105 | ? 'inline' 106 | : process.env.LNG_BUILD_SOURCEMAP === 'false' 107 | ? '' 108 | : 'external', 109 | format: 'iife', 110 | define: defined, 111 | target: process.env.LNG_BUNDLER_TARGET || '', 112 | globalName, 113 | banner: { 114 | js: [ 115 | '/*', 116 | ` App version: ${buildHelpers.getAppVersion()}`, 117 | ` SDK version: ${buildHelpers.getSdkVersion()}`, 118 | ` CLI version: ${buildHelpers.getCliVersion()}`, 119 | '', 120 | ` gmtDate: ${new Date().toGMTString()}`, 121 | '*/', 122 | ].join(os.EOL), 123 | }, 124 | } 125 | if (customConfig && 'entryPoints' in customConfig) { 126 | delete defaultConfig.entryPoints 127 | } 128 | const finalConfig = customConfig ? deepMerge(defaultConfig, customConfig) : defaultConfig 129 | return finalConfig 130 | } 131 | -------------------------------------------------------------------------------- /src/configs/rollup.es5.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * If not stated otherwise in this file or this component's LICENSE file the 3 | * following copyright and licenses apply: 4 | * 5 | * Copyright 2020 Metrological 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the License); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | const path = require('path') 21 | const process = require('process') 22 | const fs = require('fs') 23 | const babel = require('@rollup/plugin-babel').babel 24 | const resolve = require('@rollup/plugin-node-resolve').nodeResolve 25 | const commonjs = require('@rollup/plugin-commonjs') 26 | const babelPresetEnv = require('@babel/preset-env') 27 | const babelPresetTypescript = require('@babel/preset-typescript') 28 | const babelPluginTransFormSpread = require('@babel/plugin-transform-spread') 29 | const babelPluginTransFormParameters = require('@babel/plugin-transform-parameters') 30 | const babelPluginClassProperties = require('@babel/plugin-proposal-class-properties') 31 | const alias = require('@rollup/plugin-alias') 32 | const json = require('@rollup/plugin-json') 33 | const virtual = require('@rollup/plugin-virtual') 34 | const inject = require('@rollup/plugin-inject') 35 | const image = require('@rollup/plugin-image') 36 | const typescript = require('@rollup/plugin-typescript') 37 | const buildHelpers = require(path.join(__dirname, '../helpers/build')) 38 | const minify = require('rollup-plugin-terser').terser 39 | const license = require('rollup-plugin-license') 40 | const os = require('os') 41 | const extensions = ['.js', '.ts', '.mjs'] 42 | const deepMerge = require('deepmerge') 43 | const chalk = require('chalk') 44 | 45 | let customConfig 46 | 47 | if (process.env.LNG_CUSTOM_ROLLUP === 'true') { 48 | const customConfigPath = path.join(process.cwd(), 'rollup.es5.config.js') 49 | if (fs.existsSync(customConfigPath)) { 50 | customConfig = require(customConfigPath) 51 | } else { 52 | console.warn( 53 | chalk.yellow('\nCustom rollup config not found while LNG_CUSTOM_ROLLUP is set to true') 54 | ) 55 | } 56 | } 57 | 58 | const defaultConfig = { 59 | onwarn(warning, warn) { 60 | if (warning.code !== 'CIRCULAR_DEPENDENCY') { 61 | warn(warning) 62 | } 63 | }, 64 | plugins: [ 65 | json(), 66 | image(), 67 | fs.existsSync(path.join(process.cwd(), 'tsconfig.json')) && typescript(), 68 | inject({ 69 | 'process.env': 'processEnv', 70 | }), 71 | virtual({ 72 | processEnv: `export default ${JSON.stringify({ 73 | NODE_ENV: process.env.NODE_ENV, 74 | ...buildHelpers.getEnvAppVars(process.env), 75 | })}`, 76 | }), 77 | alias({ 78 | entries: { 79 | 'wpe-lightning': path.join(__dirname, '../alias/wpe-lightning.js'), 80 | '@lightningjs/core': path.join(__dirname, '../alias/lightningjs-core.js'), 81 | '@': path.resolve(process.cwd(), 'src/'), 82 | '~': path.resolve(process.cwd(), 'node_modules/'), 83 | }, 84 | }), 85 | resolve({ extensions, mainFields: buildHelpers.getResolveConfigForBundlers() }), 86 | commonjs({ sourceMap: false }), 87 | babel({ 88 | presets: [ 89 | [ 90 | babelPresetEnv, 91 | { 92 | targets: { 93 | chrome: '39', 94 | }, 95 | spec: false, 96 | debug: false, 97 | useBuiltIns: 'entry', 98 | corejs: '^3.6.5', 99 | }, 100 | ], 101 | [babelPresetTypescript], 102 | ], 103 | extensions, 104 | babelHelpers: 'bundled', 105 | plugins: [ 106 | babelPluginTransFormSpread, 107 | babelPluginTransFormParameters, 108 | babelPluginClassProperties, 109 | ], 110 | }), 111 | (process.env.LNG_BUILD_MINIFY === 'true' || process.env.NODE_ENV === 'production') && 112 | minify({ keep_fnames: true }), 113 | license({ 114 | banner: { 115 | content: [ 116 | 'App version: <%= data.appVersion %>', 117 | 'SDK version: <%= data.sdkVersion %>', 118 | 'CLI version: <%= data.cliVersion %>', 119 | '', 120 | 'Generated: <%= data.gmtDate %>', 121 | ].join(os.EOL), 122 | data() { 123 | const date = new Date() 124 | return { 125 | appVersion: buildHelpers.getAppVersion(), 126 | sdkVersion: buildHelpers.getSdkVersion(), 127 | cliVersion: buildHelpers.getCliVersion(), 128 | gmtDate: date.toGMTString(), 129 | } 130 | }, 131 | }, 132 | }), 133 | ], 134 | output: { 135 | format: 'iife', 136 | inlineDynamicImports: true, 137 | sourcemap: 138 | process.env.NODE_ENV === 'production' 139 | ? true 140 | : process.env.LNG_BUILD_SOURCEMAP === undefined 141 | ? true 142 | : process.env.LNG_BUILD_SOURCEMAP === 'false' 143 | ? false 144 | : process.env.LNG_BUILD_SOURCEMAP, 145 | }, 146 | } 147 | 148 | const finalConfig = customConfig ? deepMerge(defaultConfig, customConfig) : defaultConfig 149 | module.exports = finalConfig 150 | -------------------------------------------------------------------------------- /src/configs/rollup.es6.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * If not stated otherwise in this file or this component's LICENSE file the 3 | * following copyright and licenses apply: 4 | * 5 | * Copyright 2020 Metrological 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the License); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | const path = require('path') 21 | const process = require('process') 22 | const fs = require('fs') 23 | const babel = require('@rollup/plugin-babel').babel 24 | const babelPluginClassProperties = require('@babel/plugin-proposal-class-properties') 25 | const babelPresetTypescript = require('@babel/preset-typescript') 26 | const babelPresetEnv = require('@babel/preset-env') 27 | const resolve = require('@rollup/plugin-node-resolve').nodeResolve 28 | const commonjs = require('@rollup/plugin-commonjs') 29 | const alias = require('@rollup/plugin-alias') 30 | const json = require('@rollup/plugin-json') 31 | const virtual = require('@rollup/plugin-virtual') 32 | const inject = require('@rollup/plugin-inject') 33 | const image = require('@rollup/plugin-image') 34 | const typescript = require('@rollup/plugin-typescript') 35 | const buildHelpers = require(path.join(__dirname, '../helpers/build')) 36 | const minify = require('rollup-plugin-terser').terser 37 | const license = require('rollup-plugin-license') 38 | const os = require('os') 39 | const extensions = ['.js', '.ts'] 40 | const deepMerge = require('deepmerge') 41 | const chalk = require('chalk') 42 | 43 | let customConfig 44 | 45 | if (process.env.LNG_CUSTOM_ROLLUP === 'true') { 46 | const customConfigPath = path.join(process.cwd(), 'rollup.es6.config.js') 47 | if (fs.existsSync(customConfigPath)) { 48 | customConfig = require(customConfigPath) 49 | } else { 50 | console.warn( 51 | chalk.yellow('\nCustom rollup config not found while LNG_CUSTOM_ROLLUP is set to true') 52 | ) 53 | } 54 | } 55 | 56 | const defaultConfig = { 57 | onwarn(warning, warn) { 58 | if (warning.code !== 'CIRCULAR_DEPENDENCY') { 59 | warn(warning) 60 | } 61 | }, 62 | plugins: [ 63 | json(), 64 | image(), 65 | fs.existsSync(path.join(process.cwd(), 'tsconfig.json')) && typescript(), 66 | inject({ 67 | 'process.env': 'processEnv', 68 | }), 69 | virtual({ 70 | processEnv: `export default ${JSON.stringify({ 71 | NODE_ENV: process.env.NODE_ENV, 72 | ...buildHelpers.getEnvAppVars(process.env), 73 | })}`, 74 | }), 75 | alias({ 76 | entries: { 77 | 'wpe-lightning': path.join(__dirname, '../alias/wpe-lightning.js'), 78 | '@lightningjs/core': path.join(__dirname, '../alias/lightningjs-core.js'), 79 | '@': path.resolve(process.cwd(), 'src/'), 80 | '~': path.resolve(process.cwd(), 'node_modules/'), 81 | }, 82 | }), 83 | resolve({ extensions, mainFields: buildHelpers.getResolveConfigForBundlers() }), 84 | commonjs({ sourceMap: false }), 85 | babel({ 86 | presets: [ 87 | [ 88 | babelPresetEnv, 89 | { 90 | targets: { 91 | safari: '12.0', 92 | }, 93 | }, 94 | ], 95 | [babelPresetTypescript], 96 | ], 97 | extensions, 98 | babelHelpers: 'bundled', 99 | plugins: [babelPluginClassProperties], 100 | }), 101 | (process.env.LNG_BUILD_MINIFY === 'true' || process.env.NODE_ENV === 'production') && 102 | minify({ keep_fnames: true }), 103 | license({ 104 | banner: { 105 | content: [ 106 | 'App version: <%= data.appVersion %>', 107 | 'SDK version: <%= data.sdkVersion %>', 108 | 'CLI version: <%= data.cliVersion %>', 109 | '', 110 | 'Generated: <%= data.gmtDate %>', 111 | ].join(os.EOL), 112 | data() { 113 | const date = new Date() 114 | return { 115 | appVersion: buildHelpers.getAppVersion(), 116 | sdkVersion: buildHelpers.getSdkVersion(), 117 | cliVersion: buildHelpers.getCliVersion(), 118 | gmtDate: date.toGMTString(), 119 | } 120 | }, 121 | }, 122 | }), 123 | ], 124 | output: { 125 | format: 'iife', 126 | inlineDynamicImports: true, 127 | sourcemap: 128 | process.env.NODE_ENV === 'production' 129 | ? true 130 | : process.env.LNG_BUILD_SOURCEMAP === undefined 131 | ? true 132 | : process.env.LNG_BUILD_SOURCEMAP === 'false' 133 | ? false 134 | : process.env.LNG_BUILD_SOURCEMAP, 135 | }, 136 | } 137 | 138 | const finalConfig = customConfig ? deepMerge(defaultConfig, customConfig) : defaultConfig 139 | module.exports = finalConfig 140 | -------------------------------------------------------------------------------- /src/helpers/ask.js: -------------------------------------------------------------------------------- 1 | /* 2 | * If not stated otherwise in this file or this component's LICENSE file the 3 | * following copyright and licenses apply: 4 | * 5 | * Copyright 2020 Metrological 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the License); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | const inquirer = require('inquirer') 21 | 22 | // type = null, choices = null 23 | const ask = (question, defaultAnswer = null, type = null, choices = []) => { 24 | return inquirer 25 | .prompt([{ name: 'q', message: question, default: defaultAnswer, type, choices }]) 26 | .then(answers => answers.q) 27 | } 28 | 29 | module.exports = ask 30 | -------------------------------------------------------------------------------- /src/helpers/build.js: -------------------------------------------------------------------------------- 1 | /* 2 | * If not stated otherwise in this file or this component's LICENSE file the 3 | * following copyright and licenses apply: 4 | * 5 | * Copyright 2020 Metrological 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the License); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | const shell = require('shelljs') 21 | const fs = require('fs') 22 | const execa = require('execa') 23 | const path = require('path') 24 | const chalk = require('chalk') 25 | const concat = require('concat') 26 | const os = require('os') 27 | const esbuild = require('esbuild') 28 | const spinner = require('./spinner') 29 | const isLocallyInstalled = require('./localinstallationcheck') 30 | const exit = require('./exit') 31 | const depth = 3 32 | 33 | const findFile = (parent, filePath, depthCount = 0) => { 34 | if (depthCount >= depth) throw new Error('Required files not found at the given path') 35 | 36 | const fullPath = path.join(parent, filePath) 37 | if (fs.existsSync(fullPath)) { 38 | return fullPath 39 | } 40 | return findFile(path.join(parent, '..'), filePath, ++depthCount) 41 | } 42 | 43 | const findBinary = binary => { 44 | const binaryPath = path.join(__dirname, '../..', `node_modules/.bin/${binary}`) 45 | const npxPath = path.join(__dirname, '../../../..', `.bin/${binary}`) 46 | return fs.existsSync(binaryPath) 47 | ? binaryPath 48 | : fs.existsSync(npxPath) 49 | ? npxPath 50 | : (() => { 51 | throw new Error(`Required binary (${binary}) not found`) 52 | })() 53 | } 54 | 55 | const removeFolder = folder => { 56 | spinner.start('Removing "' + folder.split('/').pop() + '" folder') 57 | shell.rm('-rf', folder) 58 | spinner.succeed() 59 | } 60 | 61 | const ensureFolderExists = folder => { 62 | spinner.start('Ensuring "' + folder.split('/').pop() + '" folder exists') 63 | shell.mkdir('-p', folder) 64 | spinner.succeed() 65 | } 66 | 67 | const copySupportFiles = folder => { 68 | spinner.start('Copying support files to "' + folder.split('/').pop() + '"') 69 | 70 | const nodeModulesPath = hasNewSDK() 71 | ? 'node_modules/@lightningjs/sdk' 72 | : 'node_modules/wpe-lightning-sdk' 73 | 74 | const lightningSDKfolder = findFile(process.cwd(), nodeModulesPath) 75 | 76 | shell.cp('-r', path.join(lightningSDKfolder, 'support/*'), folder) 77 | 78 | const command = process.argv.pop() 79 | 80 | // if live reload is enabled we write the client WebSocket logic 81 | // to index.html 82 | if (process.env.LNG_LIVE_RELOAD === 'true' && command === 'dev') { 83 | const host = process.env.LNG_LIVE_RELOAD_HOST || 'localhost' 84 | const port = process.env.LNG_LIVE_RELOAD_PORT || 8991 85 | const file = path.join(folder, 'index.html') 86 | const data = fs.readFileSync(file, { encoding: 'utf8' }) 87 | const wsData = ` 88 | 89 | 102 | ` 103 | fs.writeFileSync(file, data.replace(/<\/body>/gi, wsData)) 104 | } 105 | 106 | spinner.succeed() 107 | } 108 | 109 | const copyStaticFolder = folder => { 110 | spinner.start('Copying static assets to "' + folder.split('/').pop() + '"') 111 | shell.cp('-r', './static', folder) 112 | spinner.succeed() 113 | } 114 | 115 | const copySrcFolder = folder => { 116 | shell.cp('-r', './src', folder) 117 | } 118 | 119 | const copySettings = (settingsFile = 'settings.json', folder) => { 120 | const file = path.join(process.cwd(), settingsFile) 121 | if (fs.existsSync(file)) { 122 | spinner.start(`Copying ${settingsFile} to "${folder.split('/').pop()}"`) 123 | shell.cp(file, folder + '/settings.json') 124 | spinner.succeed() 125 | } else { 126 | spinner.warn( 127 | `Settings file not found at the ${process.cwd()}, so switching to default settings file` 128 | ) 129 | } 130 | } 131 | 132 | const copyMetadata = folder => { 133 | const file = path.join(process.cwd(), 'metadata.json') 134 | if (fs.existsSync(file)) { 135 | spinner.start('Copying metadata.json to "' + folder.split('/').pop() + '"') 136 | shell.cp(file, folder) 137 | spinner.succeed() 138 | } else { 139 | spinner.warn(`Metadata file not found at the ${process.cwd()}`) 140 | } 141 | } 142 | 143 | const readMetadata = () => { 144 | return readJson('metadata.json') 145 | } 146 | 147 | const readSettings = (settingsFileName = 'settings.json') => { 148 | return readJson(settingsFileName) 149 | } 150 | 151 | const readJson = fileName => { 152 | return new Promise((resolve, reject) => { 153 | const file = path.join(process.cwd(), fileName) 154 | if (fs.existsSync(file)) { 155 | try { 156 | resolve(JSON.parse(fs.readFileSync(file, 'utf8'))) 157 | } catch (e) { 158 | spinner.fail(`Error occurred while reading ${file} file\n\n${e}`) 159 | reject(e) 160 | } 161 | } else { 162 | spinner.fail(`File not found error occurred while reading ${file} file`) 163 | reject('"' + fileName + '" not found') 164 | } 165 | }) 166 | } 167 | 168 | const bundleEs6App = (folder, metadata, options = {}) => { 169 | if (process.env.LNG_BUNDLER === 'esbuild') { 170 | return buildAppEsBuild(folder, metadata, 'es6', options) 171 | } else { 172 | return bundleAppRollup(folder, metadata, 'es6', options) 173 | } 174 | } 175 | 176 | const bundleEs5App = (folder, metadata, options = {}) => { 177 | if (process.env.LNG_BUNDLER === 'esbuild') { 178 | return buildAppEsBuild(folder, metadata, 'es5', options) 179 | } else { 180 | return bundleAppRollup(folder, metadata, 'es5', options) 181 | } 182 | } 183 | 184 | const buildAppEsBuild = async (folder, metadata, type, options) => { 185 | spinner.start( 186 | `Building ${type.toUpperCase()} appBundle using [esbuild] and saving to ${folder 187 | .split('/') 188 | .pop()}` 189 | ) 190 | try { 191 | const getConfig = require(`../configs/esbuild.${type}.config`) 192 | let defaultOptions = getConfig(folder, makeSafeAppId(metadata)) 193 | let finalConfig = Object.assign(defaultOptions, convertToCamelCaseOrKeepOriginal(options)) 194 | //As code splitting only supports outdir so removing outfile 195 | if (finalConfig.splitting) { 196 | delete finalConfig.outfile 197 | } 198 | await esbuild.build(finalConfig) 199 | spinner.succeed() 200 | return metadata 201 | } catch (e) { 202 | spinner.fail(`Error while creating ${type.toUpperCase()} bundle using [esbuild] (see log)`) 203 | console.log(chalk.red('--------------------------------------------------------------')) 204 | console.log(chalk.italic(e.message)) 205 | console.log(chalk.red('--------------------------------------------------------------')) 206 | process.env.LNG_BUILD_EXIT_ON_FAIL === 'true' && process.exit(1) 207 | } 208 | } 209 | 210 | const bundleAppRollup = (folder, metadata, type, options) => { 211 | spinner.start(`Building ${type.toUpperCase()} appBundle and saving to ${folder.split('/').pop()}`) 212 | 213 | const enterFile = fs.existsSync(path.join(process.cwd(), 'src/index.ts')) 214 | ? 'src/index.ts' 215 | : 'src/index.js' 216 | 217 | const args = [ 218 | '-c', 219 | path.join(__dirname, `../configs/rollup.${type}.config.js`), 220 | '--file', 221 | path.join(folder, type === 'es6' ? 'appBundle.js' : 'appBundle.es5.js'), 222 | '--name', 223 | makeSafeAppId(metadata), 224 | ] 225 | 226 | const rollupConfig = require(path.join(__dirname, `../configs/rollup.${type}.config.js`)) 227 | // Check if 'input' property is not present in the rollupConfig object 228 | if (!('input' in rollupConfig)) { 229 | //if 'input' is not present, push the input option and location of source file to the args 230 | args.push('--input', path.join(process.cwd(), enterFile)) 231 | } 232 | 233 | // Check if 'preserveSymlinks' property is not present in the rollupConfig object 234 | if (!('preserveSymlinks' in rollupConfig)) { 235 | // If 'preserveSymlinks' property is not present, push preserveSymLinks to the args 236 | args.push('--preserveSymlinks') 237 | } 238 | 239 | if (options.sourcemaps === false) args.push('--no-sourcemap') 240 | 241 | const levelsDown = isLocallyInstalled() 242 | ? findFile(process.cwd(), 'node_modules/.bin/rollup') 243 | : findBinary('rollup') 244 | 245 | args.push.apply(args, addRollupOptions(options)) 246 | 247 | process.env.LNG_BUILD_FAIL_ON_WARNINGS === 'true' ? args.push('--failAfterWarnings') : '' 248 | return execa(levelsDown, args) 249 | .then(() => { 250 | spinner.succeed() 251 | return metadata 252 | }) 253 | .catch(e => { 254 | spinner.fail(`Error while creating ${type.toUpperCase()} bundle (see log)`) 255 | console.log(chalk.red('--------------------------------------------------------------')) 256 | console.log(chalk.italic(e.stderr)) 257 | console.log(chalk.red('--------------------------------------------------------------')) 258 | process.env.LNG_BUILD_EXIT_ON_FAIL === 'true' && process.exit(1) 259 | }) 260 | } 261 | 262 | const getEnvAppVars = (parsed = {}) => 263 | Object.keys(parsed) 264 | .filter(key => key.startsWith('APP_')) 265 | .reduce((env, key) => { 266 | env[key] = parsed[key] 267 | return env 268 | }, {}) 269 | 270 | const bundlePolyfills = folder => { 271 | spinner.start('Bundling ES5 polyfills and saving to "' + folder.split('/').pop() + '"') 272 | 273 | const nodeModulesPath = hasNewSDK() 274 | ? 'node_modules/@lightningjs/sdk' 275 | : 'node_modules/wpe-lightning-sdk' 276 | 277 | const lightningSDKfolder = findFile(process.cwd(), nodeModulesPath) 278 | 279 | const pathToPolyfills = path.join(lightningSDKfolder, 'support/polyfills') 280 | 281 | const polyfills = fs.readdirSync(pathToPolyfills).map(file => path.join(pathToPolyfills, file)) 282 | 283 | return concat(polyfills, path.join(folder, 'polyfills.js')).then(() => { 284 | spinner.succeed() 285 | }) 286 | } 287 | 288 | const ensureCorrectGitIgnore = () => { 289 | return new Promise(resolve => { 290 | const filename = path.join(process.cwd(), '.gitignore') 291 | try { 292 | const gitIgnoreEntries = fs 293 | .readFileSync(filename, 'utf8') 294 | .replace(/\r/g, '') 295 | .split('\n') 296 | const missingEntries = [ 297 | process.env.LNG_BUILD_FOLDER || 'dist', 298 | 'releases', 299 | '.tmp', 300 | process.env.LNG_BUILD_FOLDER || 'build', 301 | ].filter(entry => gitIgnoreEntries.indexOf(entry) === -1) 302 | 303 | if (missingEntries.length) { 304 | fs.appendFileSync(filename, os.EOL + missingEntries.join(os.EOL) + os.EOL) 305 | } 306 | 307 | resolve() 308 | } catch (e) { 309 | // no .gitignore file, so let's just move on 310 | resolve() 311 | } 312 | }) 313 | } 314 | 315 | const ensureCorrectSdkDependency = () => { 316 | const packageJsonPath = path.join(process.cwd(), 'package.json') 317 | const packageJson = require(packageJsonPath) 318 | // check if package.json has old WebPlatformForEmbedded sdk dependency 319 | if ( 320 | packageJson && 321 | packageJson.dependencies && 322 | Object.keys(packageJson.dependencies).indexOf('wpe-lightning-sdk') > -1 && 323 | packageJson.dependencies['wpe-lightning-sdk'] 324 | .toLowerCase() 325 | .indexOf('webplatformforembedded/lightning-sdk') > -1 326 | ) { 327 | let lockedDependency 328 | // if already has a hash, use that one (e.g. from a specific branch) 329 | if (packageJson.dependencies['wpe-lightning-sdk'].indexOf('#') > -1) { 330 | lockedDependency = packageJson.dependencies['wpe-lightning-sdk'] 331 | } 332 | // otherwise attempt to get the locked dependency from package-lock 333 | else { 334 | const packageLockJsonPath = path.join(process.cwd(), 'package-lock.json') 335 | if (!fs.existsSync(packageLockJsonPath)) return true 336 | const packageLockJson = require(packageLockJsonPath) 337 | // get the locked version from package-lock 338 | if ( 339 | packageLockJson && 340 | packageLockJson.dependencies && 341 | Object.keys(packageLockJson.dependencies).indexOf('wpe-lightning-sdk') > -1 342 | ) { 343 | lockedDependency = packageLockJson.dependencies['wpe-lightning-sdk'].version 344 | } 345 | } 346 | 347 | if (lockedDependency) { 348 | // replace WebPlatformForEmbedded organization with rdkcentral organization (and keep locked hash) 349 | lockedDependency = lockedDependency.replace(/WebPlatformForEmbedded/gi, 'rdkcentral') 350 | if (lockedDependency) { 351 | spinner.start( 352 | 'Moving SDK dependency from WebPlatformForEmbedded organization to RDKcentral organization' 353 | ) 354 | // install the new dependency 355 | return execa('npm', ['install', lockedDependency]) 356 | .then(() => { 357 | spinner.succeed() 358 | }) 359 | .catch(e => { 360 | spinner.fail() 361 | console.log(chalk.red('Unable to automatically move the SDK dependency')) 362 | console.log( 363 | 'Please run ' + 364 | chalk.yellow('npm install ' + lockedDependency) + 365 | ' manually to continue' 366 | ) 367 | console.log(' ') 368 | throw Error(e) 369 | }) 370 | } 371 | } 372 | } 373 | } 374 | 375 | const getAppVersion = () => { 376 | return require(path.join(process.cwd(), 'metadata.json')).version 377 | } 378 | 379 | const getSdkVersion = () => { 380 | const packagePath = hasNewSDK() 381 | ? 'node_modules/@lightningjs/sdk' 382 | : 'node_modules/wpe-lightning-sdk' 383 | const packageJsonPath = findFile(process.cwd(), packagePath) 384 | return require(path.join(packageJsonPath, 'package.json')).version 385 | } 386 | 387 | const getCliVersion = () => { 388 | return require(path.join(__dirname, '../../package.json')).version 389 | } 390 | const makeSafeAppId = metadata => 391 | ['APP', metadata.identifier && metadata.identifier.replace(/\./g, '_').replace(/-/g, '_')] 392 | .filter(val => val) 393 | .join('_') 394 | 395 | const hasNewSDK = () => { 396 | const dependencies = Object.keys(require(path.join(process.cwd(), 'package.json')).dependencies) 397 | return dependencies.indexOf('@lightningjs/sdk') > -1 398 | } 399 | const ensureLightningApp = () => { 400 | return new Promise(resolve => { 401 | const packageJsonPath = path.join(process.cwd(), 'package.json') 402 | if (!fs.existsSync(packageJsonPath)) { 403 | exit(`Package.json is not available at ${process.cwd()}. Build process cannot be proceeded`) 404 | } 405 | const packageJson = require(packageJsonPath) 406 | if ( 407 | packageJson.dependencies && 408 | (Object.keys(packageJson.dependencies).indexOf('wpe-lightning-sdk') > -1 || 409 | Object.keys(packageJson.dependencies).indexOf('@lightningjs/sdk') > -1) 410 | ) { 411 | resolve() 412 | } else { 413 | exit('Please make sure you are running the command in the Application directory') 414 | } 415 | }) 416 | } 417 | 418 | /** 419 | * Function to get the config for node-resolve plugin 420 | * @returns Object 421 | */ 422 | const getResolveConfigForBundlers = () => { 423 | return process.env.LNG_BROWSER_BUILD === 'true' 424 | ? ['module', 'browser', 'main'] 425 | : ['module', 'main', 'browser'] 426 | } 427 | 428 | const getSettingsFileName = () => { 429 | let settingsFileName = 'settings.json' 430 | if (process.env.LNG_SETTINGS_ENV) { 431 | const envSettingsFileName = `settings.${process.env.LNG_SETTINGS_ENV}.json` 432 | if (fs.existsSync(path.join(process.cwd(), envSettingsFileName))) { 433 | settingsFileName = envSettingsFileName 434 | } else { 435 | spinner.fail( 436 | chalk.red( 437 | `Settings file ${envSettingsFileName} not available in project home, hence switching to default settings` 438 | ) 439 | ) 440 | } 441 | } 442 | return settingsFileName 443 | } 444 | 445 | /** 446 | * Converts object keys from hyphen-separated format to camelCase, if not returns original keys. 447 | */ 448 | function convertToCamelCaseOrKeepOriginal(inputObject) { 449 | return Object.keys(inputObject).reduce((resultObject, originalKey) => { 450 | if (originalKey.includes('-')) { 451 | const words = originalKey.split('-') 452 | 453 | // Capitalize the first letter of each word except the first one 454 | for (let i = 1; i < words.length; i++) { 455 | words[i] = words[i].charAt(0).toUpperCase() + words[i].slice(1) 456 | } 457 | const camelCaseKey = words.join('') 458 | resultObject[camelCaseKey] = inputObject[originalKey] 459 | } else { 460 | resultObject[originalKey] = inputObject[originalKey] 461 | } 462 | return resultObject 463 | }, {}) 464 | } 465 | 466 | /** 467 | * Converts an options object into an array of command-line arguments for Rollup. 468 | */ 469 | const addRollupOptions = options => { 470 | return Object.keys(options).reduce((optionsList, key) => { 471 | const value = options[key] 472 | 473 | if (Array.isArray(value)) { 474 | value.forEach(element => { 475 | optionsList.push('--' + key, element) 476 | }) 477 | } else if (typeof value === 'object' && value !== null) { 478 | // If the value is an object, add a single argument with key-value pairs 479 | const keyValuePairs = Object.entries(value) 480 | .map(([innerKey, innerValue]) => `${innerKey}:${innerValue}`) 481 | .join(',') 482 | optionsList.push('--' + key, keyValuePairs) 483 | } else { 484 | // If the value is 'true', add only the key without a value or else add a single argument 485 | value !== true ? optionsList.push('--' + key, value) : optionsList.push('--' + key) 486 | } 487 | return optionsList 488 | }, []) 489 | } 490 | 491 | module.exports = { 492 | removeFolder, 493 | ensureFolderExists, 494 | copySupportFiles, 495 | copyStaticFolder, 496 | copySrcFolder, 497 | copySettings, 498 | copyMetadata, 499 | readMetadata, 500 | readSettings, 501 | bundleEs6App, 502 | bundleEs5App, 503 | getEnvAppVars, 504 | ensureCorrectGitIgnore, 505 | ensureCorrectSdkDependency, 506 | getAppVersion, 507 | getSdkVersion, 508 | getCliVersion, 509 | bundlePolyfills, 510 | makeSafeAppId, 511 | hasNewSDK, 512 | ensureLightningApp, 513 | getSettingsFileName, 514 | findFile, 515 | getResolveConfigForBundlers, 516 | } 517 | -------------------------------------------------------------------------------- /src/helpers/dist.js: -------------------------------------------------------------------------------- 1 | /* 2 | * If not stated otherwise in this file or this component's LICENSE file the 3 | * following copyright and licenses apply: 4 | * 5 | * Copyright 2020 Metrological 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the License); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | const fs = require('fs') 21 | const path = require('path') 22 | const shell = require('shelljs') 23 | const replaceInFile = require('replace-in-file') 24 | const buildHelpers = require('./build') 25 | 26 | const setupDistFolder = (folder, type, metadata) => { 27 | const corePath = buildHelpers.hasNewSDK() 28 | ? 'node_modules/@lightningjs/core' 29 | : 'node_modules/wpe-lightning/' 30 | 31 | const nodeModulesPath = buildHelpers.findFile(process.cwd(), corePath) 32 | 33 | const settingsFileName = buildHelpers.getSettingsFileName() 34 | 35 | if (type === 'es6') { 36 | shell.cp( 37 | path.join(nodeModulesPath, 'dist/lightning.min.js'), 38 | path.join(folder, 'js', 'lightning.min.js') 39 | ) 40 | 41 | shell.cp( 42 | path.join(__dirname, '../../fixtures/dist/index.es6.html'), 43 | path.join(folder, 'index.html') 44 | ) 45 | } 46 | if (type === 'es5') { 47 | const lightningFile = path.join(nodeModulesPath, 'dist/lightning.es5.min.js') 48 | // lightning es5 bundle in dist didn't exist in earlier versions (< 1.3.1) 49 | if (fs.existsSync(lightningFile)) { 50 | shell.cp(lightningFile, path.join(folder, 'js', 'lightning.es5.min.js')) 51 | } 52 | 53 | shell.cp( 54 | path.join(__dirname, '../../fixtures/dist/index.es5.html'), 55 | path.join(folder, 'index.html') 56 | ) 57 | } 58 | 59 | const settingsJsonFile = path.join(process.cwd(), settingsFileName) 60 | 61 | const settings = fs.existsSync(settingsJsonFile) 62 | ? JSON.parse(fs.readFileSync(settingsJsonFile, 'utf8')) 63 | : { 64 | appSettings: { 65 | stage: { 66 | clearColor: '0x00000000', 67 | useImageWorker: true, 68 | }, 69 | }, 70 | platformSettings: { 71 | path: './static', 72 | }, 73 | } 74 | 75 | //Adding complete metadata info to app settings 76 | Object.assign(settings.appSettings, metadata) 77 | 78 | //To align with the production response, adding the 'identifier' as 'id' 79 | settings.appSettings.id = metadata.identifier 80 | 81 | replaceInFile.sync({ 82 | files: folder + '/*', 83 | from: /\{\$APPSETTINGS\}/g, 84 | to: JSON.stringify(settings.appSettings, null, 2), 85 | }) 86 | 87 | replaceInFile.sync({ 88 | files: folder + '/*', 89 | from: /\{\$PLATFORMSETTINGS\}/g, 90 | to: JSON.stringify(settings.platformSettings, null, 2), 91 | }) 92 | 93 | replaceInFile.sync({ 94 | files: folder + '/*', 95 | from: /\{\$APP_ID\}/g, 96 | to: buildHelpers.makeSafeAppId(metadata), 97 | }) 98 | } 99 | 100 | const moveOldDistFolderToBuildFolder = () => { 101 | const distFolder = path.join(process.cwd(), process.env.LNG_DIST_FOLDER || 'dist') 102 | 103 | // when dist folder has a metadata.json file we assume it's an old 'built' app 104 | if (fs.existsSync(path.join(distFolder, 'metadata.json'))) { 105 | const buildFolder = path.join(process.cwd(), process.env.LNG_BUILD_FOLDER || 'build') 106 | // move to build folder if it doesn't exist yet 107 | if (!fs.existsSync(buildFolder)) { 108 | shell.mv(distFolder, buildFolder) 109 | } else { 110 | // otherwise just remove the old style dist folder 111 | shell.rm('-rf', distFolder) 112 | } 113 | } 114 | } 115 | 116 | module.exports = { 117 | setupDistFolder, 118 | moveOldDistFolderToBuildFolder, 119 | } 120 | -------------------------------------------------------------------------------- /src/helpers/distWatch.js: -------------------------------------------------------------------------------- 1 | const buildHelpers = require('./build') 2 | const watch = require('watch') 3 | const sequence = require('../helpers/sequence') 4 | const path = require('path') 5 | 6 | const regexp = /^(?!src|static)(.+)$/ 7 | 8 | let metadata 9 | 10 | const distWatch = type => { 11 | let busy = false 12 | return watch.watchTree( 13 | './', 14 | { 15 | interval: 1, 16 | filter(f) { 17 | return !!!regexp.test(f) 18 | }, 19 | ignoreDirectoryPattern: /node_modules|\.git|dist|build/, 20 | }, 21 | (f, curr, prev) => { 22 | // prevent initiating another dist when already busy 23 | if (busy === true) { 24 | return 25 | } 26 | //Ignore the dist for the first time as there will be no changes 27 | if (typeof f == 'object' && prev === null && curr === null) { 28 | console.log('') 29 | } else { 30 | busy = true 31 | // pass the 'type of change' based on the file that was changes 32 | let change 33 | if (/^src/g.test(f)) { 34 | change = 'src' 35 | } 36 | if (/^static/g.test(f)) { 37 | change = 'static' 38 | } 39 | updateDistFolder(change, type) 40 | busy = false 41 | } 42 | } 43 | ) 44 | } 45 | 46 | /** 47 | * Updates the dist folder with latest bundles based on the changes in the src folders 48 | * @param change 49 | * @param type 50 | */ 51 | const updateDistFolder = (change = null, type) => { 52 | const baseDistDir = path.join(process.cwd(), process.env.LNG_DIST_FOLDER || 'dist') 53 | const distDir = path.join(baseDistDir, type) 54 | sequence([ 55 | () => buildHelpers.readMetadata().then(result => (metadata = result)), 56 | () => change === 'static' && buildHelpers.copyStaticFolder(distDir), 57 | () => 58 | change === 'src' && 59 | type === 'es6' && 60 | buildHelpers.bundleEs6App(path.join(distDir, 'js'), metadata, { sourcemaps: false }), 61 | () => 62 | change === 'src' && 63 | type === 'es5' && 64 | buildHelpers.bundleEs5App(path.join(distDir, 'js'), metadata, { sourcemaps: false }), 65 | ]) 66 | } 67 | 68 | module.exports = distWatch 69 | -------------------------------------------------------------------------------- /src/helpers/esbuildbabel.js: -------------------------------------------------------------------------------- 1 | /* 2 | * If not stated otherwise in this file or this component's LICENSE file the 3 | * following copyright and licenses apply: 4 | * 5 | * Copyright 2020 Metrological 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the License); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | */ 20 | 21 | /** 22 | * Copyright (c) Antoine Boulanger (https://github.com/antoineboulanger) 23 | * Licensed under the ISC License 24 | */ 25 | const babel = require('@babel/core') 26 | const fs = require('fs') 27 | const path = require('path') 28 | 29 | const pluginBabel = (options = {}) => ({ 30 | name: 'babel', 31 | setup(build, { transform } = {}) { 32 | const { filter = /.*/, namespace = '', config = {} } = options 33 | 34 | const transformContents = ({ args, contents }) => { 35 | const babelOptions = babel.loadOptions({ 36 | ...config, 37 | filename: args.path, 38 | caller: { 39 | name: 'esbuild-plugin-babel', 40 | supportsStaticESM: true, 41 | }, 42 | }) 43 | 44 | if (!babelOptions) { 45 | return 46 | } 47 | 48 | if (babelOptions.sourceMaps) { 49 | const filename = path.relative(process.cwd(), args.path) 50 | 51 | babelOptions.sourceFileName = filename 52 | } 53 | 54 | return new Promise((resolve, reject) => { 55 | babel.transform(contents, babelOptions, (error, result) => { 56 | error ? reject(error) : resolve({ contents: result.code }) 57 | }) 58 | }) 59 | } 60 | 61 | if (transform) return transformContents(transform) 62 | 63 | build.onLoad({ filter, namespace }, async args => { 64 | if (/util.inspect/.test(args.path)) { 65 | args.path += '.js' 66 | } 67 | const contents = await fs.promises.readFile(args.path, 'utf8') 68 | 69 | return transformContents({ args, contents }) 70 | }) 71 | }, 72 | }) 73 | 74 | module.exports = pluginBabel 75 | -------------------------------------------------------------------------------- /src/helpers/exit.js: -------------------------------------------------------------------------------- 1 | /* 2 | * If not stated otherwise in this file or this component's LICENSE file the 3 | * following copyright and licenses apply: 4 | * 5 | * Copyright 2020 Metrological 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the License); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | const spinner = require('./spinner') 21 | 22 | const exit = msg => { 23 | spinner.fail(msg) 24 | process.exit() 25 | } 26 | 27 | module.exports = exit 28 | -------------------------------------------------------------------------------- /src/helpers/generateObject.js: -------------------------------------------------------------------------------- 1 | // Regular expressions to validate Windows file paths and URLs 2 | const windowsFilePathRegex = /^(?:[a-zA-Z]:)?(\\[^\\:*?"<>|]*)*\\?$/ 3 | const urlRegex = /^(https?|ftp?|localhost):(\/\/?|\d{4}).*$/ 4 | 5 | /** 6 | * This function generates an object from an array of strings in the format "key=value" 7 | */ 8 | const generateObject = arr => { 9 | return arr.reduce((generatedObject, element) => { 10 | const [key, val] = element.split('=') 11 | 12 | if (urlRegex.test(val) || windowsFilePathRegex.test(val)) { 13 | generatedObject[key] = val 14 | } else if (element.includes('[') && element.includes(']')) { 15 | // Check if the element contains square brackets '[' and ']' 16 | // If yes, split the value by ',' and remove any '[' or ']' characters, 17 | // then assign it to the generated object as an array 18 | generatedObject[key] = val.split(',').map(item => item.replace(/\[|\]/g, '')) 19 | } else if (element.includes(':')) { 20 | const [objKey, objVal] = element.split(':') 21 | // Create or update the object in the generated object and add the key-value pair 22 | generatedObject[objKey] = { 23 | ...generatedObject[objKey], 24 | [objVal.split('=')[0]]: objVal.split('=')[1], 25 | } 26 | } else if (element.includes('=')) { 27 | // Check if the value is a number or a boolean, and convert it accordingly 28 | generatedObject[key] = isNaN(val) 29 | ? val === 'true' 30 | ? true 31 | : val === 'false' 32 | ? false 33 | : val 34 | : parseFloat(val) 35 | } else { 36 | generatedObject[key] = true 37 | } 38 | return generatedObject 39 | }, {}) 40 | } 41 | 42 | module.exports = generateObject 43 | -------------------------------------------------------------------------------- /src/helpers/localinstallationcheck.js: -------------------------------------------------------------------------------- 1 | /* 2 | * If not stated otherwise in this file or this component's LICENSE file the 3 | * following copyright and licenses apply: 4 | * 5 | * Copyright 2020 Metrological 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the License); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | const path = require('path') 20 | const os = require('os') 21 | 22 | const isLocallyInstalled = () => { 23 | let currentWorkingDir = process.cwd() 24 | const localScriptExecutionPath = path.join( 25 | currentWorkingDir, 26 | `node_modules/${__dirname.split('node_modules')[1]}` 27 | ) 28 | if (os.platform() == 'linux') return __dirname.indexOf(currentWorkingDir) > -1 29 | else return __dirname.indexOf(localScriptExecutionPath) > -1 30 | } 31 | 32 | module.exports = isLocallyInstalled 33 | -------------------------------------------------------------------------------- /src/helpers/packageVersion.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = packageName => { 4 | return new Promise(resolve => { 5 | try { 6 | const version = require(path.join(process.cwd(), 'node_modules', packageName, 'package.json')) 7 | .version 8 | 9 | resolve(version) 10 | } catch (e) { 11 | console.log(`Error occurred while getting package version\n\n${e}`) 12 | resolve(false) 13 | } 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /src/helpers/sequence.js: -------------------------------------------------------------------------------- 1 | /* 2 | * If not stated otherwise in this file or this component's LICENSE file the 3 | * following copyright and licenses apply: 4 | * 5 | * Copyright 2020 Metrological 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the License); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | module.exports = steps => { 21 | return steps.reduce((promise, method) => { 22 | return promise 23 | .then(function() { 24 | return method(...arguments) 25 | }) 26 | .catch(e => Promise.reject(e)) 27 | }, Promise.resolve(null)) 28 | } 29 | -------------------------------------------------------------------------------- /src/helpers/spinner.js: -------------------------------------------------------------------------------- 1 | /* 2 | * If not stated otherwise in this file or this component's LICENSE file the 3 | * following copyright and licenses apply: 4 | * 5 | * Copyright 2020 Metrological 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the License); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | const spinner = require('ora')() 21 | 22 | module.exports = { 23 | start(msg) { 24 | console.log(' ') 25 | spinner.start(msg) 26 | }, 27 | stop() { 28 | spinner.stop() 29 | }, 30 | succeed(msg) { 31 | spinner.succeed(msg) 32 | }, 33 | fail(msg) { 34 | spinner.fail(msg) 35 | console.log(' ') 36 | }, 37 | warn(msg) { 38 | spinner.warn(msg) 39 | }, 40 | } 41 | -------------------------------------------------------------------------------- /src/helpers/uptodate.js: -------------------------------------------------------------------------------- 1 | /* 2 | * If not stated otherwise in this file or this component's LICENSE file the 3 | * following copyright and licenses apply: 4 | * 5 | * Copyright 2020 Metrological 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the License); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | const fs = require('fs') 21 | const path = require('path') 22 | const semver = require('semver') 23 | const execa = require('execa') 24 | const isOnline = require('is-online') 25 | const latestVersion = require('latest-version') 26 | 27 | const spinner = require('./spinner.js') 28 | const exit = require('./exit.js') 29 | const packageJson = require('../../package.json') 30 | const ask = require('../helpers/ask') 31 | 32 | const fetchLatestVersion = () => { 33 | return new Promise((resolve, reject) => { 34 | // if a git folder exists, we are probably working of a clone, 35 | // so likely we don't want to do any auto updates 36 | const gitFolder = path.join(__dirname, '../../.git') 37 | if (fs.existsSync(gitFolder)) { 38 | resolve(false) 39 | } else { 40 | return latestVersion(packageJson.name) 41 | .then(resolve) 42 | .catch(reject) 43 | } 44 | }) 45 | } 46 | 47 | const upToDate = async (skip = false) => { 48 | if (process.env.LNG_AUTO_UPDATE !== undefined) { 49 | skip = process.env.LNG_AUTO_UPDATE === 'false' ? true : false 50 | } 51 | if (skip === true) { 52 | return true 53 | } 54 | spinner.start('Testing internet connection..') 55 | 56 | const isOnline = await testConnection() 57 | if (!isOnline) { 58 | spinner.fail() 59 | console.log('Unable to check for CLI update due to no internet connection') 60 | console.log(' ') 61 | 62 | const answer = await ask('Do you want to continue', null, 'list', ['Yes', 'No']) 63 | if (answer === 'Yes') { 64 | return true 65 | } 66 | 67 | exit() 68 | } else { 69 | spinner.succeed() 70 | return checkForUpdate() 71 | } 72 | } 73 | 74 | const testConnection = async () => { 75 | return await isOnline() 76 | } 77 | const checkForUpdate = () => { 78 | spinner.start('Verifying if your installation of Lightning-CLI is up to date.') 79 | return fetchLatestVersion() 80 | .then(version => { 81 | if (version === false) { 82 | spinner.succeed() 83 | return Promise.resolve('a git folder exists, we are probably working of a clone') 84 | } 85 | if ( 86 | semver.lt(packageJson.version, version) || 87 | packageJson.name === 'wpe-lightning-cli' // always update when old package name 88 | ) { 89 | spinner.fail() 90 | spinner.start('Attempting to update Lightning-CLI to the latest version (' + version + ')') 91 | 92 | const options = ['install', '-g', '@lightningjs/cli'] 93 | 94 | // force update when old package name 95 | if (packageJson.name === 'wpe-lightning-cli') { 96 | options.splice(2, 0, '-f') 97 | } 98 | 99 | return execa('npm', options) 100 | .then(() => { 101 | spinner.succeed() 102 | console.log(' ') 103 | }) 104 | .catch(e => { 105 | spinner.fail('Error occurred while updating cli') 106 | console.log(e) 107 | console.log(' ') 108 | console.log(' ') 109 | console.log( 110 | 'Please update Lightning-CLI manually by running: npm install -g -f @lightningjs/cli' 111 | ) 112 | console.log(' ') 113 | exit() 114 | }) 115 | } else { 116 | spinner.succeed() 117 | return Promise.resolve() 118 | } 119 | }) 120 | .catch(error => { 121 | spinner.fail('Error occurred while checking for cli update') 122 | console.log(error) 123 | console.log(' ') 124 | }) 125 | } 126 | 127 | module.exports = upToDate 128 | -------------------------------------------------------------------------------- /src/plugins/esbuild-alias.js: -------------------------------------------------------------------------------- 1 | /* 2 | * If not stated otherwise in this file or this component's LICENSE file the 3 | * following copyright and licenses apply: 4 | * 5 | * Copyright 2020 Metrological 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the License); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | const fs = require('fs') 21 | 22 | const isFile = path => { 23 | try { 24 | return fs.lstatSync(path).isFile() 25 | } catch (e) { 26 | return false 27 | } 28 | } 29 | 30 | const getAsFile = path => { 31 | const isDir = fs.existsSync(path) && fs.lstatSync(path).isDirectory() 32 | const extensions = ['js', 'mjs', 'ts'] 33 | const file = extensions 34 | .reduce((acc, ext) => { 35 | acc.push(isDir ? `${path}/index.${ext}` : `${path}.${ext}`) 36 | return acc 37 | }, []) 38 | .filter(f => isFile(f)) 39 | 40 | return file.length ? file[0] : false 41 | } 42 | 43 | module.exports = (entries = []) => { 44 | return { 45 | name: 'alias', 46 | setup(build) { 47 | entries.forEach(entry => { 48 | build.onResolve({ filter: entry.filter }, args => { 49 | let importPath = args.path.replace(entry.find, entry.replace) 50 | if (!isFile(importPath)) { 51 | importPath = getAsFile(importPath) 52 | } 53 | if (!importPath) { 54 | throw new Error( 55 | `Unable to import: ${args.path} 56 | importer: ${args.importer}` 57 | ) 58 | } 59 | return { 60 | path: importPath, 61 | } 62 | }) 63 | }) 64 | }, 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /tests/01.lng_create.test.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs-extra') 2 | const { addMsg } = require('jest-html-reporters/helper') 3 | 4 | const upToDate = require('../src/helpers/uptodate') 5 | const createApp = require('../src/actions/create') 6 | const ask = require('../src/helpers/ask') 7 | 8 | jest.mock('is-online', () => jest.fn()) 9 | 10 | jest.mock('inquirer', () => ({ 11 | prompt: jest.fn(), 12 | })) 13 | const inquirer = require('inquirer') 14 | 15 | jest.mock('../src/helpers/spinner') 16 | 17 | describe('create app without internet', () => { 18 | let originalExit = process.exit 19 | 20 | beforeAll(async () => { 21 | process.exit = jest.fn() 22 | }) 23 | 24 | afterAll(async () => { 25 | process.exit = originalExit 26 | process.chdir(global.originalCWD) 27 | fs.removeSync(`${global.appConfig.appPath}`) 28 | }) 29 | beforeEach(() => { 30 | // Reset the mock Inquirer prompt's resolved value between tests 31 | inquirer.prompt.mockReset() 32 | }) 33 | 34 | it('should create app without internet', async () => { 35 | // Clean up the test by deleting the app folder 36 | fs.removeSync(`${global.appConfig.appPath}`) 37 | 38 | jest.spyOn(console, 'log').mockImplementation(() => {}) 39 | //Return is-online result to be false 40 | const mockIsOnline = require('is-online') 41 | mockIsOnline.mockResolvedValue(false) 42 | 43 | //Test for internet 44 | inquirer.prompt.mockResolvedValueOnce({ q: 'Yes' }) 45 | const result = await upToDate() 46 | 47 | expect(result).toBe(true) 48 | expect(inquirer.prompt.mock.calls).toHaveLength(1) 49 | 50 | //Create the app without internet 51 | //Mock the questions asked 52 | inquirer.prompt.mockReset() 53 | inquirer.prompt 54 | .mockResolvedValueOnce({ q: global.appConfig.name }) //What is the name of your Lightning App? 55 | .mockResolvedValueOnce({ q: global.appConfig.id }) //What is the App identifier? 56 | .mockResolvedValueOnce({ q: global.appConfig.id }) //In what (relative) folder do you want to create the new App? 57 | .mockResolvedValueOnce({ q: 'No' }) //Do you want to write your App in TypeScript? 58 | .mockResolvedValueOnce({ q: 'No' }) //Do you want to enable ESlint? 59 | .mockResolvedValueOnce({ q: 'No' }) //Do you want to install the NPM dependencies now? 60 | .mockResolvedValue({ q: 'No' }) //Do you want to initialize an empty GIT repository? (And all questions after this) 61 | const resultCreate = await createApp() 62 | await addMsg({ message: JSON.stringify(resultCreate, null, 2) }) 63 | 64 | expect(inquirer.prompt.mock.calls).toHaveLength(7) 65 | expect(resultCreate.appName).toBe(global.appConfig.name) 66 | expect(resultCreate.appId).toBe(global.appConfig.id) 67 | expect(resultCreate.appFolder).toBe(global.appConfig.id) 68 | expect(resultCreate.eslint).toBe(false) 69 | expect(resultCreate.targetDir).toContain(global.appConfig.id) 70 | expect(resultCreate.npmInstall).toBe(false) 71 | expect(resultCreate.gitInit).toBe(false) 72 | 73 | // Assert that the function created the app in the correct folder 74 | expect(fs.pathExistsSync(`${resultCreate.targetDir}`)).toBe(true) 75 | 76 | // Assert that the function set the correct app data in the files 77 | expect(fs.existsSync(`${resultCreate.targetDir}/metadata.json`)).toBe(true) 78 | expect(fs.existsSync(`${resultCreate.targetDir}/package.json`)).toBe(true) 79 | expect(fs.existsSync(`${resultCreate.targetDir}/settings.json`)).toBe(true) 80 | expect(fs.pathExistsSync(`${resultCreate.targetDir}/static`)).toBe(true) 81 | expect(fs.pathExistsSync(`${resultCreate.targetDir}/src`)).toBe(true) 82 | }, 30000) 83 | 84 | it('should create app without name', async () => { 85 | // Clean up the test by deleting the app folder 86 | fs.removeSync(`${global.appConfig.appPath}`) 87 | 88 | jest.spyOn(console, 'log').mockImplementation(() => {}) 89 | 90 | inquirer.prompt.mockResolvedValueOnce({ q: '' }) //What is the name of your Lightning App? 91 | 92 | let resultAskName = await ask('What is the name of your Lightning App?', 'My Awesome App') 93 | expect(resultAskName).toBeFalsy() 94 | 95 | const defaultName = inquirer.prompt.mock.calls[0][0][0].default || '' 96 | 97 | inquirer.prompt.mockResolvedValueOnce({ 98 | q: `com.domain.app.${defaultName.replace(/[^A-Z0-9]/gi, '')}`, 99 | }) 100 | const resultAskId = await ask( 101 | 'What is the App identifier?', 102 | `com.domain.app.${defaultName.replace(/[^A-Z0-9]/gi, '')}` 103 | ) 104 | 105 | //Reset question responses and create App 106 | inquirer.prompt.mockReset() 107 | inquirer.prompt 108 | .mockResolvedValueOnce({ q: defaultName }) //What is the name of your Lightning App? 109 | .mockResolvedValueOnce({ q: resultAskId }) //What is the App identifier? 110 | .mockResolvedValueOnce({ q: resultAskId }) //In what (relative) folder do you want to create the new App? 111 | .mockResolvedValueOnce({ q: 'No' }) //Do you want to write your App in TypeScript? 112 | .mockResolvedValueOnce({ q: 'No' }) //Do you want to enable ESlint? 113 | .mockResolvedValueOnce({ q: 'No' }) //Do you want to install the NPM dependencies now? 114 | .mockResolvedValue({ q: 'No' }) //Do you want to initialize an empty GIT repository? (And all questions after this) 115 | const resultCreate = await createApp() 116 | await addMsg({ message: JSON.stringify(resultCreate, null, 2) }) 117 | 118 | expect(inquirer.prompt.mock.calls).toHaveLength(7) 119 | expect(resultCreate.appName).toBe(defaultName) 120 | expect(resultCreate.appId).toBe(global.appConfig.id) 121 | expect(resultCreate.appFolder).toBe(global.appConfig.id) 122 | expect(resultCreate.eslint).toBe(false) 123 | expect(resultCreate.targetDir).toContain(global.appConfig.id) 124 | expect(resultCreate.npmInstall).toBe(false) 125 | expect(resultCreate.gitInit).toBe(false) 126 | 127 | // Assert that the function created the app in the correct folder 128 | expect(fs.pathExistsSync(`${resultCreate.targetDir}`)).toBe(true) 129 | 130 | // Assert that the function set the correct app data in the files 131 | expect(fs.existsSync(`${resultCreate.targetDir}/metadata.json`)).toBe(true) 132 | expect(fs.existsSync(`${resultCreate.targetDir}/package.json`)).toBe(true) 133 | expect(fs.existsSync(`${resultCreate.targetDir}/settings.json`)).toBe(true) 134 | expect(fs.pathExistsSync(`${resultCreate.targetDir}/static`)).toBe(true) 135 | expect(fs.pathExistsSync(`${resultCreate.targetDir}/src`)).toBe(true) 136 | }, 30000) 137 | 138 | it('should create app with invalid name', async () => { 139 | jest.spyOn(console, 'log').mockImplementation(() => {}) 140 | 141 | inquirer.prompt.mockResolvedValueOnce({ q: '/invalid|@41' }) //What is the name of your Lightning App? 142 | 143 | let resultAskName = await ask('What is the name of your Lightning App?', 'My Awesome App') 144 | resultAskName = `${resultAskName.replace(/[^A-Z0-9]/gi, '')}` 145 | inquirer.prompt.mockResolvedValueOnce({ 146 | q: `com.domain.app.${resultAskName}`, 147 | }) 148 | const resultAskId = await ask('What is the App identifier?', `com.domain.app.${resultAskName}`) 149 | 150 | //Reset question responses 151 | inquirer.prompt.mockReset() 152 | inquirer.prompt 153 | .mockResolvedValueOnce({ q: '/invalid|@41' }) //What is the name of your Lightning App? 154 | .mockResolvedValueOnce({ q: resultAskId }) //What is the App identifier? 155 | .mockResolvedValueOnce({ q: resultAskId }) //In what (relative) folder do you want to create the new App? 156 | .mockResolvedValueOnce({ q: 'No' }) //Do you want to write your App in TypeScript? 157 | .mockResolvedValueOnce({ q: 'No' }) //Do you want to enable ESlint? 158 | .mockResolvedValueOnce({ q: 'No' }) //Do you want to install the NPM dependencies now? 159 | .mockResolvedValue({ q: 'No' }) //Do you want to initialize an empty GIT repository? (And all questions after this) 160 | const resultCreate = await createApp() 161 | await addMsg({ message: JSON.stringify(resultCreate, null, 2) }) 162 | 163 | expect(inquirer.prompt.mock.calls).toHaveLength(7) 164 | expect(resultCreate.appName).toBe('/invalid|@41') 165 | expect(resultCreate.appId).toBe(resultAskId) 166 | expect(resultCreate.appFolder).toBe(resultAskId) 167 | expect(resultCreate.eslint).toBe(false) 168 | expect(resultCreate.targetDir).toContain(resultAskId) 169 | expect(resultCreate.npmInstall).toBe(false) 170 | expect(resultCreate.gitInit).toBe(false) 171 | 172 | // Assert that the function created the app in the correct folder 173 | expect(fs.pathExistsSync(`${resultCreate.targetDir}`)).toBe(true) 174 | 175 | // Assert that the function set the correct app data in the files 176 | expect(fs.existsSync(`${resultCreate.targetDir}/metadata.json`)).toBe(true) 177 | expect(fs.existsSync(`${resultCreate.targetDir}/package.json`)).toBe(true) 178 | expect(fs.existsSync(`${resultCreate.targetDir}/settings.json`)).toBe(true) 179 | expect(fs.pathExistsSync(`${resultCreate.targetDir}/static`)).toBe(true) 180 | expect(fs.pathExistsSync(`${resultCreate.targetDir}/src`)).toBe(true) 181 | 182 | // Clean up the test by deleting the app folder 183 | fs.removeSync(resultCreate.targetDir) 184 | }, 30000) 185 | 186 | it('should create app', async () => { 187 | // Clean up the test by deleting the app folder 188 | fs.removeSync(`${global.appConfig.appPath}`) 189 | 190 | jest.spyOn(console, 'log').mockImplementation(() => {}) 191 | 192 | inquirer.prompt 193 | .mockResolvedValueOnce({ q: global.appConfig.name }) //What is the name of your Lightning App? 194 | .mockResolvedValueOnce({ q: global.appConfig.id }) //What is the App identifier? 195 | .mockResolvedValueOnce({ q: global.appConfig.id }) //In what (relative) folder do you want to create the new App? 196 | .mockResolvedValueOnce({ q: 'No' }) //Do you want to write your App in TypeScript? 197 | .mockResolvedValueOnce({ q: 'No' }) //Do you want to enable ESlint? 198 | .mockResolvedValueOnce({ q: 'Yes' }) //Do you want to install the NPM dependencies now? 199 | .mockResolvedValue({ q: 'No' }) //Do you want to initialize an empty GIT repository? (And all questions after this) 200 | const resultCreate = await createApp() 201 | await addMsg({ message: JSON.stringify(resultCreate, null, 2) }) 202 | 203 | expect(inquirer.prompt.mock.calls).toHaveLength(7) 204 | expect(resultCreate.appName).toBe(global.appConfig.name) 205 | expect(resultCreate.appId).toBe(global.appConfig.id) 206 | expect(resultCreate.appFolder).toBe(global.appConfig.id) 207 | expect(resultCreate.eslint).toBe(false) 208 | expect(resultCreate.targetDir).toContain(global.appConfig.id) 209 | expect(resultCreate.npmInstall).toBe(true) 210 | expect(resultCreate.gitInit).toBe(false) 211 | 212 | // Assert that the function created the app in the correct folder 213 | expect(fs.pathExistsSync(`${resultCreate.targetDir}`)).toBe(true) 214 | 215 | // Assert that the function set the correct app data in the files 216 | expect(fs.existsSync(`${resultCreate.targetDir}/metadata.json`)).toBe(true) 217 | expect(fs.existsSync(`${resultCreate.targetDir}/package.json`)).toBe(true) 218 | expect(fs.existsSync(`${resultCreate.targetDir}/settings.json`)).toBe(true) 219 | expect(fs.pathExistsSync(`${resultCreate.targetDir}/static`)).toBe(true) 220 | expect(fs.pathExistsSync(`${resultCreate.targetDir}/src`)).toBe(true) 221 | }, 30000) 222 | }) 223 | -------------------------------------------------------------------------------- /tests/02.lng_build.test.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs-extra') 2 | const { addMsg } = require('jest-html-reporters/helper') 3 | const createApp = require('../src/actions/create') 4 | 5 | require('dotenv').config() 6 | 7 | const buildApp = require('../src/actions/build') 8 | const buildHelpers = require('../src/helpers/build') 9 | const inquirer = require('inquirer') 10 | 11 | jest.mock('is-online', () => jest.fn()) 12 | 13 | jest.mock('inquirer', () => ({ 14 | prompt: jest.fn(), 15 | })) 16 | 17 | const spinner = require('../src/helpers/spinner') 18 | 19 | describe('lng build', () => { 20 | let originalExit = process.exit 21 | let buildFolder 22 | 23 | beforeEach(async () => { 24 | spinner.start.mockReset() 25 | inquirer.prompt 26 | .mockResolvedValueOnce({ q: global.appConfig.name }) //What is the name of your Lightning App? 27 | .mockResolvedValueOnce({ q: global.appConfig.id }) //What is the App identifier? 28 | .mockResolvedValueOnce({ q: global.appConfig.id }) //In what (relative) folder do you want to create the new App? 29 | .mockResolvedValueOnce({ q: 'No' }) //Do you want to write your App in TypeScript? 30 | .mockResolvedValueOnce({ q: 'No' }) //Do you want to enable ESlint? 31 | .mockResolvedValueOnce({ q: 'Yes' }) //Do you want to install the NPM dependencies now? 32 | .mockResolvedValue({ q: 'No' }) //Do you want to initialize an empty GIT repository? (And all questions after this) 33 | await createApp() 34 | process.chdir(global.appConfig.appPath) 35 | buildFolder = `${process.cwd()}/build` 36 | process.exit = jest.fn() 37 | }, 30000) 38 | 39 | afterEach(() => { 40 | process.chdir(global.originalCWD) 41 | fs.removeSync(`${global.appConfig.appPath}`) 42 | }) 43 | 44 | afterAll(() => { 45 | fs.removeSync(`${global.appConfig.appPath}`) 46 | }) 47 | 48 | afterAll(async () => { 49 | process.exit = originalExit 50 | process.chdir(global.originalCWD) 51 | }) 52 | it('settings.json should have the correct data structure', async () => { 53 | const settings = await buildHelpers.readSettings() 54 | 55 | expect(settings).toEqual({ 56 | // Define the expected structure of the JSON file 57 | appSettings: expect.objectContaining({ 58 | stage: expect.objectContaining({ 59 | clearColor: expect.any(String), 60 | useImageWorker: expect.any(Boolean), 61 | }), 62 | debug: expect.any(Boolean), 63 | }), 64 | platformSettings: expect.objectContaining({ 65 | path: expect.any(String), 66 | log: expect.any(Boolean), 67 | showVersion: expect.any(Boolean), 68 | }), 69 | }) 70 | }, 30000) 71 | 72 | it('settings.json should have the correct data', async () => { 73 | const settings = await buildHelpers.readSettings() 74 | expect(settings.platformSettings.path).toBe('./static') 75 | }) 76 | 77 | //Not very usefull error message when npm install is not run 78 | it('Should build app with esbuild and es5', async () => { 79 | // Clean up the test by deleting the app folder 80 | fs.removeSync(`${global.appConfig.appPath}/build`) 81 | 82 | jest.spyOn(console, 'log').mockImplementation(() => {}) 83 | jest.spyOn(console, 'warn').mockImplementation(() => {}) 84 | global.setEnvironmentValue('LNG_BUNDLER', 'esbuild') 85 | // process.env.LNG_BUNDLER = 'esbuild' 86 | global.changeEsEnv('es5') 87 | 88 | const buildResult = await buildApp(true) 89 | await addMsg({ message: JSON.stringify(buildResult, null, 2) }) 90 | //TODO inconsistent return value -> es5 returns object with metadata.json content, es6 returns Boolean false 91 | 92 | await addMsg({ message: spinner.start.mock.calls.join('\n') }) 93 | 94 | //Check if build folder exists 95 | expect(fs.pathExistsSync(buildFolder)).toBe(true) 96 | //Check for files and directories 97 | expect(fs.existsSync(`${buildFolder}/settings.json`)).toBe(true) 98 | expect(fs.existsSync(`${buildFolder}/metadata.json`)).toBe(true) 99 | expect(fs.existsSync(`${buildFolder}/startApp.js`)).toBe(true) 100 | expect(fs.existsSync(`${buildFolder}/index.html`)).toBe(true) 101 | expect(fs.existsSync(`${buildFolder}/appBundle.es5.js`)).toBe(true) 102 | expect(fs.existsSync(`${buildFolder}/appBundle.es5.js.map`)).toBe(true) 103 | }, 30000) 104 | 105 | it('Should build app with esbuild and es6', async () => { 106 | // Clean up the test by deleting the app folder 107 | fs.removeSync(`${global.appConfig.appPath}/build`) 108 | jest.spyOn(console, 'log').mockImplementation(() => {}) 109 | global.setEnvironmentValue('LNG_BUNDLER', 'esbuild') 110 | // process.env.LNG_BUNDLER = 'esbuild' 111 | global.changeEsEnv('es6') 112 | 113 | const buildResult = await buildApp(true) 114 | await addMsg({ message: JSON.stringify(buildResult, null, 2) }) 115 | 116 | //TODO inconsistent return value -> es5 returns object with metadata.json content, es6 returns Boolean false 117 | 118 | await addMsg({ message: spinner.start.mock.calls.join('\n') }) 119 | 120 | //Check if build folder exists 121 | expect(fs.pathExistsSync(buildFolder)).toBe(true) 122 | //Check for files and directories 123 | expect(fs.existsSync(`${buildFolder}/settings.json`)).toBe(true) 124 | expect(fs.existsSync(`${buildFolder}/metadata.json`)).toBe(true) 125 | expect(fs.existsSync(`${buildFolder}/startApp.js`)).toBe(true) 126 | expect(fs.existsSync(`${buildFolder}/index.html`)).toBe(true) 127 | expect(fs.existsSync(`${buildFolder}/appBundle.js`)).toBe(true) 128 | expect(fs.existsSync(`${buildFolder}/appBundle.js.map`)).toBe(true) 129 | }) 130 | 131 | it('should build app with rollup and es5', async () => { 132 | // Clean up the test by deleting the app folder 133 | fs.removeSync(`${global.appConfig.appPath}/build`) 134 | jest.spyOn(console, 'log').mockImplementation(() => {}) 135 | // global.setEnvironmentValue('LNG_BUNDLER', 'rollup') 136 | process.env.LNG_BUNDLER = 'rollup' 137 | global.changeEsEnv('es5') 138 | 139 | const buildResult = await buildApp(true) 140 | await addMsg({ message: JSON.stringify(buildResult, null, 2) }) 141 | //TODO inconsistent return value -> es5 returns object with metadata.json content, es6 returns Boolean false 142 | 143 | await addMsg({ message: spinner.start.mock.calls.join('\n') }) 144 | 145 | //Check if build folder exists 146 | expect(fs.pathExistsSync(buildFolder)).toBe(true) 147 | //Check for files and directories 148 | expect(fs.existsSync(`${buildFolder}/settings.json`)).toBe(true) 149 | expect(fs.existsSync(`${buildFolder}/metadata.json`)).toBe(true) 150 | expect(fs.existsSync(`${buildFolder}/startApp.js`)).toBe(true) 151 | expect(fs.existsSync(`${buildFolder}/index.html`)).toBe(true) 152 | expect(fs.existsSync(`${buildFolder}/appBundle.es5.js`)).toBe(true) 153 | expect(fs.existsSync(`${buildFolder}/appBundle.es5.js.map`)).toBe(true) 154 | }, 10000) 155 | 156 | it('Should build app with rollup and es6', async () => { 157 | // Clean up the test by deleting the app folder 158 | fs.removeSync(`${global.appConfig.appPath}/build`) 159 | jest.spyOn(console, 'log').mockImplementation(() => {}) 160 | // global.setEnvironmentValue('LNG_BUNDLER', 'rollup') 161 | process.env.LNG_BUNDLER = 'rollup' 162 | global.changeEsEnv('es6') 163 | 164 | const buildResult = await buildApp(true) 165 | await addMsg({ message: JSON.stringify(buildResult, null, 2) }) 166 | //TODO inconsistent return value -> es5 returns object with metadata.json content, es6 returns Boolean false 167 | 168 | // expect(spinner.start).toHaveBeenCalledWith('Building ES6 appBundle and saving to build') 169 | await addMsg({ message: spinner.start.mock.calls.join('\n') }) 170 | 171 | //Check if build folder exists 172 | expect(fs.pathExistsSync(buildFolder)).toBe(true) 173 | //Check for files and directories 174 | expect(fs.existsSync(`${buildFolder}/settings.json`)).toBe(true) 175 | expect(fs.existsSync(`${buildFolder}/metadata.json`)).toBe(true) 176 | expect(fs.existsSync(`${buildFolder}/startApp.js`)).toBe(true) 177 | expect(fs.existsSync(`${buildFolder}/index.html`)).toBe(true) 178 | expect(fs.existsSync(`${buildFolder}/appBundle.js`)).toBe(true) 179 | expect(fs.existsSync(`${buildFolder}/appBundle.js.map`)).toBe(true) 180 | 181 | //Check file contents 182 | const metadataJson = fs.readJsonSync(`${buildFolder}/metadata.json`) 183 | expect(metadataJson.identifier).toBe(global.appConfig.id) 184 | 185 | const indexHtml = fs.readFileSync(`${buildFolder}/index.html`, 'utf8') 186 | expect(indexHtml).toContain('') 187 | 188 | console.log(buildHelpers.getAppVersion()) 189 | }) 190 | }) 191 | -------------------------------------------------------------------------------- /tests/03.lng_dev.test.js: -------------------------------------------------------------------------------- 1 | const dev = require('../src/actions/dev') 2 | const watch = require('../src/actions/watch') 3 | const serve = require('../src/actions/serve') 4 | const buildHelpers = require('../src/helpers/build') 5 | 6 | jest.mock('../src/actions/watch') 7 | jest.mock('../src/actions/serve') 8 | jest.mock('../src/helpers/build') 9 | 10 | describe('dev', () => { 11 | beforeEach(() => { 12 | jest.clearAllMocks() 13 | }) 14 | 15 | test('should call all necessary functions in the correct order', async () => { 16 | const ensureLightningAppMock = jest 17 | .spyOn(buildHelpers, 'ensureLightningApp') 18 | .mockImplementation(() => {}) 19 | 20 | const logMock = jest.spyOn(console, 'log') 21 | watch.mockImplementation((serveFn, callback) => { 22 | serveFn() 23 | callback() 24 | }) 25 | 26 | await dev() 27 | 28 | expect(ensureLightningAppMock).toHaveBeenCalled() 29 | expect(watch).toHaveBeenCalledWith(serve, expect.any(Function)) 30 | expect(logMock).toHaveBeenCalledWith('') 31 | expect(logMock).toHaveBeenCalledWith(expect.any(String)) 32 | expect(logMock).toHaveBeenCalledWith('') 33 | }) 34 | 35 | test('should display "Navigate to web browser" when LNG_LIVE_RELOAD is true', async () => { 36 | process.env.LNG_LIVE_RELOAD = 'true' 37 | const logMock = jest.spyOn(console, 'log') 38 | 39 | await dev() 40 | 41 | expect(logMock).toHaveBeenCalledWith('Navigate to web browser to see the changes') 42 | }) 43 | 44 | test('should display "Reload your web browser" when LNG_LIVE_RELOAD is not true', async () => { 45 | process.env.LNG_LIVE_RELOAD = 'false' 46 | const logMock = jest.spyOn(console, 'log') 47 | 48 | await dev() 49 | 50 | expect(logMock).toHaveBeenCalledWith('Reload your web browser to see the changes') 51 | }) 52 | }) 53 | -------------------------------------------------------------------------------- /tests/04.lng_watch.test.js: -------------------------------------------------------------------------------- 1 | const spinner = require('../src/helpers/spinner') 2 | const watch = require('watch') 3 | const createApp = require('../src/actions/create') 4 | const inquirer = require('inquirer') 5 | 6 | jest.mock('is-online', () => jest.fn()) 7 | 8 | jest.mock('inquirer', () => ({ 9 | prompt: jest.fn(), 10 | })) 11 | jest.mock('watch') 12 | 13 | describe('watch', () => { 14 | let watchCallback 15 | let initCallback 16 | let originalExit = process.exit 17 | let watchFolder 18 | beforeEach(async () => { 19 | spinner.start.mockReset() 20 | inquirer.prompt 21 | .mockResolvedValueOnce({ q: global.appConfig.name }) //What is the name of your Lightning App? 22 | .mockResolvedValueOnce({ q: global.appConfig.id }) //What is the App identifier? 23 | .mockResolvedValueOnce({ q: global.appConfig.id }) //In what (relative) folder do you want to create the new App? 24 | .mockResolvedValueOnce({ q: 'No' }) //Do you want to write your App in TypeScript? 25 | .mockResolvedValueOnce({ q: 'No' }) //Do you want to enable ESlint? 26 | .mockResolvedValueOnce({ q: 'Yes' }) //Do you want to install the NPM dependencies now? 27 | .mockResolvedValue({ q: 'No' }) //Do you want to initialize an empty GIT repository? (And all questions after this) 28 | await createApp() 29 | process.chdir(global.appConfig.appPath) 30 | watchFolder = `${process.cwd()}/src` 31 | process.exit = jest.fn() 32 | }, 30000) 33 | 34 | afterAll(async () => { 35 | process.exit = originalExit 36 | process.chdir(global.originalCWD) 37 | }) 38 | 39 | beforeEach(() => { 40 | // Reset the mocks and clear any mock function calls 41 | jest.clearAllMocks() 42 | 43 | spinner.start.mockReset() 44 | // Reset the callbacks 45 | watchCallback = jest.fn() 46 | initCallback = jest.fn().mockResolvedValue() 47 | }) 48 | 49 | test('Should watch for file changes and automatically rebuild the App', async () => { 50 | const { watchTree } = watch 51 | 52 | // Mock watch.watchTree to simulate file changes 53 | watchTree.mockImplementationOnce((dir, options, callback) => { 54 | callback(`${watchFolder}/index.js`, {}, {}) 55 | }) 56 | 57 | // Call the function being tested 58 | const result = await require('../src/actions/watch')(initCallback, watchCallback) 59 | 60 | // Assert that the initCallback is not called 61 | expect(initCallback).not.toHaveBeenCalled() 62 | 63 | // Assert that the watchCallback is called 64 | expect(watchCallback).toHaveBeenCalled() 65 | 66 | // Assert that the result is the same as the watch object 67 | expect(result).toBe(watch) 68 | }, 50000) 69 | }) 70 | -------------------------------------------------------------------------------- /tests/05.lng_dist.test.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs-extra') 2 | const { addMsg } = require('jest-html-reporters/helper') 3 | const puppeteer = require('puppeteer') 4 | const { toMatchImageSnapshot } = require('jest-image-snapshot') 5 | const createApp = require('../src/actions/create') 6 | const buildApp = require('../src/actions/build') 7 | 8 | const inquirer = require('inquirer') 9 | expect.extend({ toMatchImageSnapshot }) 10 | 11 | const lngDist = require('../src/actions/dist') 12 | jest.mock('../src/helpers/spinner') 13 | jest.mock('inquirer', () => ({ 14 | prompt: jest.fn(), 15 | })) 16 | 17 | describe('lng dist', () => { 18 | let browser, page, distFolder 19 | let originalExit = process.exit 20 | 21 | beforeAll(async () => { 22 | process.exit = jest.fn() 23 | inquirer.prompt 24 | .mockResolvedValueOnce({ q: global.appConfig.name }) //What is the name of your Lightning App? 25 | .mockResolvedValueOnce({ q: global.appConfig.id }) //What is the App identifier? 26 | .mockResolvedValueOnce({ q: global.appConfig.id }) //In what (relative) folder do you want to create the new App? 27 | .mockResolvedValueOnce({ q: 'No' }) //Do you want to write your App in TypeScript? 28 | .mockResolvedValueOnce({ q: 'No' }) //Do you want to enable ESlint? 29 | .mockResolvedValueOnce({ q: 'Yes' }) //Do you want to install the NPM dependencies now? 30 | .mockResolvedValue({ q: 'No' }) //Do you want to initialize an empty GIT repository? (And all questions after this) 31 | await createApp() 32 | process.chdir(global.appConfig.appPath) 33 | distFolder = `${process.cwd()}/dist` 34 | 35 | // await buildApp(true) 36 | browser = await puppeteer.launch({ headless: true }) 37 | page = await browser.newPage() 38 | // Set screen size 39 | await page.setViewport({ width: 1920, height: 1080 }) 40 | global.changeShowVersion(false) 41 | }, 30000) 42 | 43 | afterAll(async () => { 44 | await browser.close() 45 | process.exit = originalExit 46 | process.chdir(global.originalCWD) 47 | fs.removeSync(`${global.appConfig.appPath}`) 48 | }) 49 | 50 | it('Should create a distributable version with rollup and es5', async () => { 51 | jest.spyOn(console, 'log').mockImplementation(() => {}) 52 | // Clean up the test by deleting the app folder 53 | fs.removeSync(`${global.appConfig.appPath}/dist`) 54 | 55 | process.env.LNG_BUNDLER = 'rollup' 56 | const distResult = await lngDist({ 57 | types: ['es5'], 58 | isWatchEnabled: false, 59 | }) 60 | await addMsg({ message: JSON.stringify(distResult, null, 2) }) 61 | 62 | //Check if dist folder exists 63 | expect(fs.pathExistsSync(distFolder)).toBe(true) 64 | expect(fs.pathExistsSync(`${distFolder}/es5`)).toBe(true) 65 | //Check for files and directories 66 | expect(fs.existsSync(`${distFolder}/es5/index.html`)).toBe(true) 67 | expect(fs.existsSync(`${distFolder}/es5/js/appBundle.es5.js`)).toBe(true) 68 | expect(fs.existsSync(`${distFolder}/es5/js/lightning.es5.min.js`)).toBe(true) 69 | expect(fs.existsSync(`${distFolder}/es5/js/polyfills.js`)).toBe(true) 70 | 71 | //TODO Cors prevents image loads 72 | // await page.goto(`file://${distFolder}/es5/index.html`, { 73 | // waitUntil: 'networkidle0' 74 | // }) 75 | 76 | // const image = await page.screenshot() 77 | // expect(image).toMatchImageSnapshot() 78 | 79 | // await addAttach({ 80 | // attach: image, 81 | // description: 'Default App', 82 | // }) 83 | }, 10000) 84 | 85 | it('Should create a distributable version with rollup and es6', async () => { 86 | // Clean up the test by deleting the app folder 87 | fs.removeSync(`${global.appConfig.appPath}/dist`) 88 | process.env.LNG_BUNDLER = 'rollup' 89 | const distResult = await lngDist({ 90 | types: ['es6'], 91 | isWatchEnabled: false, 92 | }) 93 | await addMsg({ message: JSON.stringify(distResult, null, 2) }) 94 | 95 | //Check if dist folder exists 96 | expect(fs.pathExistsSync(distFolder)).toBe(true) 97 | expect(fs.pathExistsSync(`${distFolder}/es6`)).toBe(true) 98 | //Check for files and directories 99 | expect(fs.existsSync(`${distFolder}/es6/index.html`)).toBe(true) 100 | expect(fs.existsSync(`${distFolder}/es6/js/appBundle.js`)).toBe(true) 101 | expect(fs.existsSync(`${distFolder}/es6/js/lightning.min.js`)).toBe(true) 102 | }, 10000) 103 | 104 | it('Should create a distributable version with rollup and es5 and es6', async () => { 105 | // Clean up the test by deleting the app folder 106 | fs.removeSync(`${global.appConfig.appPath}/dist`) 107 | process.env.LNG_BUNDLER = 'rollup' 108 | const distResult = await lngDist({ 109 | types: ['es5', 'es6'], 110 | isWatchEnabled: false, 111 | }) 112 | await addMsg({ message: JSON.stringify(distResult, null, 2) }) 113 | 114 | //es5 115 | //Check if dist folder exists 116 | expect(fs.pathExistsSync(distFolder)).toBe(true) 117 | expect(fs.pathExistsSync(`${distFolder}/es5`)).toBe(true) 118 | //Check for files and directories 119 | expect(fs.existsSync(`${distFolder}/es5/index.html`)).toBe(true) 120 | expect(fs.existsSync(`${distFolder}/es5/js/appBundle.es5.js`)).toBe(true) 121 | expect(fs.existsSync(`${distFolder}/es5/js/lightning.es5.min.js`)).toBe(true) 122 | expect(fs.existsSync(`${distFolder}/es5/js/polyfills.js`)).toBe(true) 123 | 124 | //es6 125 | //Check if dist folder exists 126 | expect(fs.pathExistsSync(distFolder)).toBe(true) 127 | expect(fs.pathExistsSync(`${distFolder}/es6`)).toBe(true) 128 | //Check for files and directories 129 | expect(fs.existsSync(`${distFolder}/es6/index.html`)).toBe(true) 130 | expect(fs.existsSync(`${distFolder}/es6/js/appBundle.js`)).toBe(true) 131 | expect(fs.existsSync(`${distFolder}/es6/js/lightning.min.js`)).toBe(true) 132 | }, 10000) 133 | 134 | it('Should create a distributable version with esbuild and es5', async () => { 135 | // Clean up the test by deleting the app folder 136 | fs.removeSync(`${global.appConfig.appPath}/dist`) 137 | // process.env.LNG_BUNDLER = 'esbuild' 138 | global.setEnvironmentValue('LNG_BUNDLER', 'esbuild') 139 | 140 | const distResult = await lngDist({ 141 | types: ['es5'], 142 | isWatchEnabled: false, 143 | }) 144 | await addMsg({ message: JSON.stringify(distResult, null, 2) }) 145 | 146 | //Check if dist folder exists 147 | expect(fs.pathExistsSync(distFolder)).toBe(true) 148 | expect(fs.pathExistsSync(`${distFolder}/es5`)).toBe(true) 149 | //Check for files and directories 150 | expect(fs.existsSync(`${distFolder}/es5/index.html`)).toBe(true) 151 | expect(fs.existsSync(`${distFolder}/es5/js/appBundle.es5.js`)).toBe(true) 152 | expect(fs.existsSync(`${distFolder}/es5/js/lightning.es5.min.js`)).toBe(true) 153 | expect(fs.existsSync(`${distFolder}/es5/js/polyfills.js`)).toBe(true) 154 | }, 10000) 155 | 156 | it('Should create a distributable version with esbuild and es6', async () => { 157 | // Clean up the test by deleting the app folder 158 | fs.removeSync(`${global.appConfig.appPath}/dist`) 159 | // process.env.LNG_BUNDLER = 'esbuild' 160 | global.setEnvironmentValue('LNG_BUNDLER', 'esbuild') 161 | 162 | const distResult = await lngDist({ 163 | types: ['es6'], 164 | isWatchEnabled: false, 165 | }) 166 | await addMsg({ message: JSON.stringify(distResult, null, 2) }) 167 | 168 | //Check if dist folder exists 169 | expect(fs.pathExistsSync(distFolder)).toBe(true) 170 | expect(fs.pathExistsSync(`${distFolder}/es6`)).toBe(true) 171 | //Check for files and directories 172 | expect(fs.existsSync(`${distFolder}/es6/index.html`)).toBe(true) 173 | expect(fs.existsSync(`${distFolder}/es6/js/appBundle.js`)).toBe(true) 174 | expect(fs.existsSync(`${distFolder}/es6/js/lightning.min.js`)).toBe(true) 175 | }, 10000) 176 | 177 | it('Should create a distributable version with esbuild and es5 and es6', async () => { 178 | // Clean up the test by deleting the app folder 179 | fs.removeSync(`${global.appConfig.appPath}/dist`) 180 | // process.env.LNG_BUNDLER = 'esbuild' 181 | global.setEnvironmentValue('LNG_BUNDLER', 'esbuild') 182 | const distResult = await lngDist({ 183 | types: ['es5', 'es6'], 184 | isWatchEnabled: false, 185 | }) 186 | await addMsg({ message: JSON.stringify(distResult, null, 2) }) 187 | 188 | //es5 189 | //Check if dist folder exists 190 | expect(fs.pathExistsSync(distFolder)).toBe(true) 191 | expect(fs.pathExistsSync(`${distFolder}/es5`)).toBe(true) 192 | //Check for files and directories 193 | expect(fs.existsSync(`${distFolder}/es5/index.html`)).toBe(true) 194 | expect(fs.existsSync(`${distFolder}/es5/js/appBundle.es5.js`)).toBe(true) 195 | expect(fs.existsSync(`${distFolder}/es5/js/lightning.es5.min.js`)).toBe(true) 196 | expect(fs.existsSync(`${distFolder}/es5/js/polyfills.js`)).toBe(true) 197 | //es6 198 | //Check if dist folder exists 199 | expect(fs.pathExistsSync(distFolder)).toBe(true) 200 | expect(fs.pathExistsSync(`${distFolder}/es6`)).toBe(true) 201 | //Check for files and directories 202 | expect(fs.existsSync(`${distFolder}/es6/index.html`)).toBe(true) 203 | expect(fs.existsSync(`${distFolder}/es6/js/appBundle.js`)).toBe(true) 204 | expect(fs.existsSync(`${distFolder}/es6/js/lightning.min.js`)).toBe(true) 205 | }, 10000) 206 | }) 207 | -------------------------------------------------------------------------------- /tests/06.lng_serve.test.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs-extra') 2 | const { addAttach, addMsg } = require('jest-html-reporters/helper') 3 | const puppeteer = require('puppeteer') 4 | const { toMatchImageSnapshot } = require('jest-image-snapshot') 5 | expect.extend({ toMatchImageSnapshot }) 6 | 7 | const buildApp = require('../src/actions/build') 8 | const serveApp = require('../src/actions/serve') 9 | const createApp = require('../src/actions/create') 10 | 11 | const inquirer = require('inquirer') 12 | jest.mock('../src/helpers/spinner') 13 | jest.mock('inquirer', () => ({ 14 | prompt: jest.fn(), 15 | })) 16 | describe('lng serve', () => { 17 | let buildFolder = null 18 | let originalExit = process.exit 19 | let browser 20 | let page 21 | 22 | beforeAll(async () => { 23 | process.exit = jest.fn() 24 | inquirer.prompt 25 | .mockResolvedValueOnce({ q: global.appConfig.name }) //What is the name of your Lightning App? 26 | .mockResolvedValueOnce({ q: global.appConfig.id }) //What is the App identifier? 27 | .mockResolvedValueOnce({ q: global.appConfig.id }) //In what (relative) folder do you want to create the new App? 28 | .mockResolvedValueOnce({ q: 'No' }) //Do you want to write your App in TypeScript? 29 | .mockResolvedValueOnce({ q: 'No' }) //Do you want to enable ESlint? 30 | .mockResolvedValueOnce({ q: 'Yes' }) //Do you want to install the NPM dependencies now? 31 | .mockResolvedValue({ q: 'No' }) //Do you want to initialize an empty GIT repository? (And all questions after this) 32 | await createApp() 33 | process.chdir(global.appConfig.appPath) 34 | buildFolder = `${process.cwd()}/build` 35 | browser = await puppeteer.launch({ headless: true }) 36 | page = await browser.newPage() 37 | // Set screen size 38 | await page.setViewport({ width: 1920, height: 1080 }) 39 | }, 30000) 40 | 41 | afterAll(async () => { 42 | await browser.close() 43 | process.exit = originalExit 44 | process.chdir(global.originalCWD) 45 | }) 46 | 47 | it('Should build app with rollup and es6 without version', async () => { 48 | jest.spyOn(console, 'log').mockImplementation(() => {}) 49 | 50 | process.env.LNG_BUNDLER = 'rollup' 51 | global.changeEsEnv('es6') 52 | global.changeShowVersion(false) 53 | 54 | const buildResult = await buildApp(true) 55 | await addMsg({ message: JSON.stringify(buildResult, null, 2) }) 56 | //TODO inconsistent return value -> es5 returns object with metadata.json content, es6 returns Boolean false 57 | 58 | //Check if build folder exists 59 | expect(fs.pathExistsSync(buildFolder)).toBe(true) 60 | //Check for files and directories 61 | expect(fs.existsSync(`${buildFolder}/settings.json`)).toBe(true) 62 | expect(fs.existsSync(`${buildFolder}/metadata.json`)).toBe(true) 63 | expect(fs.existsSync(`${buildFolder}/startApp.js`)).toBe(true) 64 | expect(fs.existsSync(`${buildFolder}/index.html`)).toBe(true) 65 | expect(fs.existsSync(`${buildFolder}/appBundle.js`)).toBe(true) 66 | expect(fs.existsSync(`${buildFolder}/appBundle.js.map`)).toBe(true) 67 | }) 68 | 69 | it('Start a local webserver and run a built Lightning App in a web browser', async () => { 70 | const preventstout = jest.spyOn(process.stdout, 'write').mockImplementation(() => {}) 71 | 72 | jest.spyOn(console, 'log').mockImplementation(() => {}) 73 | const devServer = await serveApp() 74 | 75 | expect(devServer.config.url).toMatch( 76 | /https?:\/\/(?:[0-9]{1,3}\.){3}[0-9]{1,3}:[0-9]{1,5}(?=\s*$)/ 77 | ) 78 | await page.goto(devServer.config.url, { 79 | waitUntil: 'networkidle0', 80 | }) 81 | const image = await page.screenshot() 82 | expect(image).toMatchImageSnapshot() 83 | 84 | await addAttach({ 85 | attach: image, 86 | description: 'Default App', 87 | }) 88 | 89 | await addMsg({ message: JSON.stringify(devServer.config, null, 2) }) 90 | //Stop the http server 91 | await devServer.process.cancel() 92 | preventstout.mockRestore() 93 | }, 10000) 94 | }) 95 | -------------------------------------------------------------------------------- /tests/07.lng_docs.test.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs-extra') 2 | const puppeteer = require('puppeteer') 3 | const { addAttach } = require('jest-html-reporters/helper') 4 | const { toMatchImageSnapshot } = require('jest-image-snapshot') 5 | expect.extend({ toMatchImageSnapshot }) 6 | const createApp = require('../src/actions/create') 7 | 8 | const lngDocs = require('../src/actions/docs') 9 | 10 | const inquirer = require('inquirer') 11 | jest.mock('../src/helpers/spinner') 12 | jest.mock('inquirer', () => ({ 13 | prompt: jest.fn(), 14 | })) 15 | 16 | describe('lng docs', () => { 17 | let originalExit = process.exit 18 | let browser 19 | let page 20 | 21 | beforeAll(async () => { 22 | process.exit = jest.fn() 23 | inquirer.prompt 24 | .mockResolvedValueOnce({ q: global.appConfig.name }) //What is the name of your Lightning App? 25 | .mockResolvedValueOnce({ q: global.appConfig.id }) //What is the App identifier? 26 | .mockResolvedValueOnce({ q: global.appConfig.id }) //In what (relative) folder do you want to create the new App? 27 | .mockResolvedValueOnce({ q: 'No' }) //Do you want to write your App in TypeScript? 28 | .mockResolvedValueOnce({ q: 'No' }) //Do you want to enable ESlint? 29 | .mockResolvedValueOnce({ q: 'Yes' }) //Do you want to install the NPM dependencies now? 30 | .mockResolvedValue({ q: 'No' }) //Do you want to initialize an empty GIT repository? (And all questions after this) 31 | await createApp() 32 | process.chdir(global.appConfig.appPath) 33 | browser = await puppeteer.launch({ headless: true }) 34 | page = await browser.newPage() 35 | // Set screen size 36 | await page.setViewport({ width: 1920, height: 1080 }) 37 | }, 30000) 38 | 39 | afterAll(async () => { 40 | await browser.close() 41 | process.exit = originalExit 42 | fs.removeSync(`${global.appConfig.appPath}`) 43 | process.chdir(global.originalCWD) 44 | }) 45 | 46 | it('Should serve the documentation for Lightning-Cli', async () => { 47 | jest.spyOn(console, 'log').mockImplementation(() => {}) 48 | const docServer = await lngDocs() 49 | 50 | expect(docServer.config.url).toMatch( 51 | /https?:\/\/(?:[0-9]{1,3}\.){3}[0-9]{1,3}:[0-9]{1,5}(?=\s*$)/ 52 | ) 53 | await page.goto(docServer.config.url, { 54 | waitUntil: 'networkidle0', 55 | }) 56 | 57 | const landingPageImage = await page.screenshot() 58 | await addAttach({ 59 | attach: landingPageImage, 60 | description: 'Documentation', 61 | }) 62 | 63 | // Type into search box 64 | await page.type('input[placeholder="Type to search"]', 'changelog') 65 | 66 | // Wait and click on first result 67 | const searchResultSelector = '.matching-post' 68 | await page.waitForSelector(searchResultSelector) 69 | await page.click(searchResultSelector) 70 | 71 | const changelogPageImage = await page.screenshot() 72 | await addAttach({ 73 | attach: changelogPageImage, 74 | description: 'Documentation Changelog', 75 | }) 76 | await docServer.process.cancel() 77 | }, 10000) 78 | }) 79 | -------------------------------------------------------------------------------- /tests/08.lng_update.test.js: -------------------------------------------------------------------------------- 1 | const { addMsg } = require('jest-html-reporters/helper') 2 | const upToDate = require('../src/helpers/uptodate') 3 | const createApp = require('../src/actions/create') 4 | const inquirer = require('inquirer') 5 | 6 | jest.mock('../src/helpers/spinner') 7 | jest.mock('inquirer', () => ({ 8 | prompt: jest.fn(), 9 | })) 10 | describe('lng update', () => { 11 | let originalExit = process.exit 12 | 13 | beforeAll(async () => { 14 | process.exit = jest.fn() 15 | inquirer.prompt 16 | .mockResolvedValueOnce({ q: global.appConfig.name }) //What is the name of your Lightning App? 17 | .mockResolvedValueOnce({ q: global.appConfig.id }) //What is the App identifier? 18 | .mockResolvedValueOnce({ q: global.appConfig.id }) //In what (relative) folder do you want to create the new App? 19 | .mockResolvedValueOnce({ q: 'No' }) //Do you want to write your App in TypeScript? 20 | .mockResolvedValueOnce({ q: 'No' }) //Do you want to enable ESlint? 21 | .mockResolvedValueOnce({ q: 'Yes' }) //Do you want to install the NPM dependencies now? 22 | .mockResolvedValue({ q: 'No' }) //Do you want to initialize an empty GIT repository? (And all questions after this) 23 | await createApp() 24 | process.chdir(global.appConfig.appPath) 25 | process.exit = jest.fn() 26 | }, 30000) 27 | 28 | afterAll(async () => { 29 | process.exit = originalExit 30 | process.chdir(global.originalCWD) 31 | }) 32 | it('Update tests', async () => { 33 | const result = await upToDate() 34 | await addMsg({ message: JSON.stringify(result, null, 2) }) 35 | 36 | // expect(result).not.toContain('git folder exists') 37 | expect(result).toContain('git folder exists') 38 | }) 39 | }) 40 | -------------------------------------------------------------------------------- /tests/10.lng_upload.test.js: -------------------------------------------------------------------------------- 1 | describe('lng upload', () => { 2 | it('Should give a warning that the command has moved', () => { 3 | const log = jest.spyOn(console, 'log').mockImplementation(() => {}) 4 | global.cli('upload') 5 | 6 | expect(log).toHaveBeenCalledWith( 7 | '\x1B[33mThe `lng upload` command is no longer part of the Lightning-CLI, but has moved to a separate package.\x1B[39m' 8 | ) 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /tests/global_configs/globalMocks.js: -------------------------------------------------------------------------------- 1 | global.spinner = jest.mock('./../../src/helpers/spinner', () => { 2 | return { 3 | start: jest.fn(), 4 | stop: jest.fn(), 5 | succeed: jest.fn(), 6 | fail: jest.fn(), 7 | warn: jest.fn(), 8 | } 9 | }) 10 | global.cli = (command, options) => { 11 | const originalArgv = process.argv.slice() 12 | process.argv.length = 2 13 | process.argv[2] = command || 'help' 14 | 15 | if (Array.isArray(options)) 16 | options.forEach(element => { 17 | process.argv.push(element) 18 | }) 19 | require('../../bin/index') 20 | process.argv = originalArgv 21 | } 22 | -------------------------------------------------------------------------------- /tests/global_configs/globalSetup.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs-extra') 2 | 3 | module.exports = async function() { 4 | global.setEnvironmentValue = (key, value) => { 5 | //Check if the .env exists if not create it 6 | if (!fs.existsSync('.env')) fs.writeFileSync('.env', '') 7 | 8 | // read the contents of the .env file into a string 9 | let envFile = fs.readFileSync('.env', 'utf-8') 10 | 11 | // split the string into an array of lines 12 | let envLines = envFile.split('\n') 13 | 14 | // find the line that contains the variable you want to update 15 | let myVarLine = envLines.findIndex(line => line.startsWith(`${key}=`)) 16 | 17 | //when the variable doesn't exists create it. 18 | if (myVarLine == -1) envLines[envLines.length] = `${key}=${value}` 19 | // update the value of the variable 20 | else envLines[myVarLine] = `${key}=${value}` 21 | 22 | // join the lines back into a string and write it to the .env file 23 | fs.writeFileSync('.env', envLines.join('\n')) 24 | } 25 | 26 | global.changeSettingsJson = obj => { 27 | const file = 'settings.json' 28 | 29 | let settingsJson = JSON.parse(fs.readFileSync(file)) 30 | 31 | if (obj.platformSettings) 32 | settingsJson.platformSettings = { 33 | ...settingsJson.platformSettings, 34 | ...obj.platformSettings, 35 | } 36 | 37 | fs.writeFileSync(file, JSON.stringify(settingsJson, null, 2)) 38 | return settingsJson 39 | } 40 | 41 | global.changeEsEnv = esEnv => { 42 | const obj = { 43 | platformSettings: { 44 | esEnv: esEnv, 45 | }, 46 | } 47 | return global.changeSettingsJson(obj) 48 | } 49 | global.changeShowVersion = show => { 50 | const obj = { 51 | platformSettings: { 52 | showVersion: show, 53 | }, 54 | } 55 | return global.changeSettingsJson(obj) 56 | } 57 | 58 | global.DelayTest = new Promise(resolve => { 59 | setTimeout(resolve, 1000) 60 | }) 61 | 62 | const appName = 'My Awesome App' 63 | global.appConfig = { 64 | name: appName, 65 | id: `com.domain.app.${appName.replace(/[^A-Z0-9]/gi, '')}`, 66 | appPath: process.cwd() + `/com.domain.app.${appName.replace(/[^A-Z0-9]/gi, '')}`, 67 | } 68 | 69 | global.originalCWD = process.cwd() 70 | } 71 | -------------------------------------------------------------------------------- /tests/global_configs/globalTeardown.js: -------------------------------------------------------------------------------- 1 | const opener = require('opener') 2 | const fs = require('fs-extra') 3 | module.exports = async function() { 4 | // Clean up the test by deleting the app folder 5 | fs.removeSync(`${global.appConfig.appPath}`) 6 | console.log('Finish up tests') 7 | opener('./tests_report/report.html') 8 | } 9 | -------------------------------------------------------------------------------- /tests/global_configs/runSequence.js: -------------------------------------------------------------------------------- 1 | const Sequencer = require('@jest/test-sequencer').default 2 | 3 | class CustomSequencer extends Sequencer { 4 | /** 5 | * Select tests for shard requested via --shard=shardIndex/shardCount 6 | * Sharding is applied before sorting 7 | */ 8 | shard(tests, { shardIndex, shardCount }) { 9 | const shardSize = Math.ceil(tests.length / shardCount) 10 | const shardStart = shardSize * (shardIndex - 1) 11 | const shardEnd = shardSize * shardIndex 12 | 13 | return [...tests].sort((a, b) => (a.path > b.path ? 1 : -1)).slice(shardStart, shardEnd) 14 | } 15 | 16 | /** 17 | * Sort method for identifying order of execution in alphabetical order 18 | */ 19 | sort(tests) { 20 | const testsClone = tests.slice().map(test => Object.assign({}, test)) 21 | return testsClone.sort((t1, t2) => t1.path.localeCompare(t2.path)) 22 | } 23 | } 24 | 25 | module.exports = CustomSequencer 26 | --------------------------------------------------------------------------------