├── .eslintrc.js ├── .eslintrs.js ├── .github └── workflows │ └── codeql-analysis.yml ├── .gitignore ├── .prettierrc ├── .vscode └── settings.json ├── .vscodeignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── ThirdPartyNotices.txt ├── app-icon.png ├── app-icon.pxd ├── QuickLook │ ├── Icon.tiff │ └── Thumbnail.tiff ├── data │ ├── 68C71D65-F1B3-4721-A8FC-AD55FFCA7902 │ ├── A32F8B02-F377-41DB-B43A-C85CE42E3F7D │ └── selectionForContentTransform │ │ ├── meta │ │ └── shapeSelection │ │ ├── meta │ │ └── path └── metadata.info ├── azure-pipelines.yml ├── images ├── empty_close_tag_setting.png ├── example_deploy_object.gif ├── example_script_maximo.png ├── extract_scripts.png ├── install_scripts.png ├── install_scripts_complete.png ├── install_scripts_progress.png ├── logging_permission.png ├── palette_compare_example.gif ├── palette_compare_screen_example.gif ├── palette_form_deploy_example.gif ├── palette_form_extract_example.gif ├── palette_password_deploy_example.gif ├── palette_password_extract_example.gif ├── palette_report_deploy_example.gif ├── palette_report_extract_example.gif ├── palette_screen_deploy_example.gif ├── palette_screen_extract_example.gif ├── palette_script_deploy_example.gif ├── prettier_config.png ├── select_environment.png ├── sharptree_utils_permission.png ├── snippet_description.png ├── snippets.gif ├── stream_maximo_log.gif └── unique_id_example.gif ├── jsconfig.json ├── no.eslintrc.json ├── package-lock.json ├── package.json ├── resources ├── sharptree.autoscript.admin.js ├── sharptree.autoscript.deploy.js ├── sharptree.autoscript.extract.js ├── sharptree.autoscript.form.js ├── sharptree.autoscript.install.js ├── sharptree.autoscript.library.js ├── sharptree.autoscript.logging.js ├── sharptree.autoscript.report.js ├── sharptree.autoscript.screens.js └── sharptree.autoscript.store.js ├── schemas ├── crontask.json ├── domain.json ├── logger.json ├── maxobject.json ├── message.json └── property.json ├── snippets ├── snippets_javascript.json └── snippets_python.json ├── src ├── commands │ ├── compare-command.js │ ├── deploy-command.js │ ├── deploy-form-command.js │ ├── deploy-report-command.js │ ├── deploy-screen-command.js │ ├── deploy-script-command.js │ ├── extract-form-command.js │ ├── extract-forms-command.js │ ├── extract-report-command.js │ ├── extract-reports-command.js │ ├── extract-screen-command.js │ ├── extract-screens-command.js │ ├── extract-script-command.js │ ├── extract-scripts-command.js │ └── select-environment.js ├── config.js ├── extension.js ├── maximo │ ├── errors.js │ ├── install.js │ ├── maximo-client.js │ ├── maximo-config.js │ └── provider.js └── settings.js ├── test ├── runTest.js └── suite │ ├── extension.test.js │ └── index.js └── webpack.config.js /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'env': { 3 | 'browser': true, 4 | 'es2021': true 5 | }, 6 | 'extends': 'eslint:recommended', 7 | 'parserOptions': { 8 | 'ecmaVersion': 'latest', 9 | 'sourceType': 'module' 10 | }, 11 | 'rules': { 12 | 'indent': [ 13 | 'error', 14 | 4 15 | ], 16 | 'linebreak-style': [ 17 | 'error', 18 | 'unix' 19 | ], 20 | 'quotes': [ 21 | 'error', 22 | 'single' 23 | ], 24 | 'semi': [ 25 | 'error', 26 | 'always' 27 | ] 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /.eslintrs.js: -------------------------------------------------------------------------------- 1 | /**@type {import('eslint').Linter.Config} */ 2 | // eslint-disable-next-line no-undef 3 | module.exports = { 4 | root: true, 5 | parser: '@typescript-eslint/parser', 6 | plugins: [ 7 | '@typescript-eslint', 8 | ], 9 | extends: [ 10 | 'eslint:recommended', 11 | 'plugin:@typescript-eslint/recommended', 12 | ], 13 | rules: { 14 | 'semi': [2, "always"], 15 | '@typescript-eslint/no-unused-vars': 0, 16 | '@typescript-eslint/no-explicit-any': 0, 17 | '@typescript-eslint/explicit-module-boundary-types': 0, 18 | '@typescript-eslint/no-non-null-assertion': 0, 19 | "no-constant-condition": 0 20 | } 21 | }; -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: 'CodeQL' 13 | 14 | on: 15 | push: 16 | branches: [main] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [main] 20 | schedule: 21 | - cron: '20 22 * * 0' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: ['javascript'] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v2 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v1 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 54 | # If this step fails, then you should remove it and run the build manually (see below) 55 | - name: Autobuild 56 | uses: github/codeql-action/autobuild@v1 57 | 58 | # ℹ️ Command-line programs to run using the OS shell. 59 | # 📚 https://git.io/JvXDl 60 | 61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 62 | # and modify them (or add more) to build your code if your project 63 | # uses a compiled language 64 | 65 | #- run: | 66 | # make bootstrap 67 | # make release 68 | 69 | - name: Perform CodeQL Analysis 70 | uses: github/codeql-action/analyze@v1 71 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vscode-test/ 3 | *.vsix 4 | dist 5 | maximo.log 6 | src/.DS_Store 7 | 8 | .DS_Store 9 | 10 | .devtools-config.json -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "tabWidth": 4 4 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "AUTOSCRIPT", 4 | "BIRT", 5 | "Charsets", 6 | "Huizen", 7 | "maxauth", 8 | "Sharptree" 9 | ] 10 | } -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | test/** 4 | .gitignore 5 | .yarnrc 6 | vsc-extension-quickstart.md 7 | **/jsconfig.json 8 | **/*.map 9 | **/.eslintrc.json 10 | .vscode 11 | node_modules 12 | out/ 13 | src/ 14 | test/ 15 | tsconfig.json 16 | webpack.config.js 17 | 18 | 19 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Release Notes 2 | ## 1.21.3 3 | - Fixed issue with deployment script for adding a table domain. 4 | 5 | ## 1.21.2 6 | - Fixed table domain handling and property refreshing. 7 | 8 | ## 1.20.1 9 | - Fixed bug that caused reports to be extracted to the forms extraction folder. Thank you to Jason Pun for pointing this out. 10 | 11 | ## 1.20.0 12 | - Add support for manifest files to specify multiple files to deploy at once. 13 | - Fixed case comparison of object attributes. 14 | - Fixed issue where an object description was required otherwise the object description would be removed. 15 | - Fixed screen deployment to finally not require a WebClientSession object, which has been a source of issues forever. 16 | - Allow Table Domain types to be created against a non-existent table that is created as part of the deployment. 17 | - Fixed bug that caused the extension to fail to load if a folder was not selected on load. 18 | - Removed references to Nashorn and switched to the more generic "javascript" 19 | - Add support for "object" instead of "maxObject" in deployment descriptor to be more consistent 20 | 21 | ## 1.19.2 22 | - Fixed issue where single environment configurations would not encrypt the password on save. 23 | - Added extract support for the allowInvokingScriptFunctions (INTERFACE) attribute. 24 | - Added support for automatically setting the allowInvokingScriptFunctions (INTERFACE) attribute to true for APPBEAN and DATABEAN scripts. 25 | 26 | ## 1.19.1 27 | - Add support for jy Python files. 28 | 29 | ## 1.19.0 30 | - Multiple environment configurations selection support. 31 | 32 | ## 1.18.0 33 | - Deployments are now cancellable. 34 | - Logging handles disconnects properly. 35 | - Deployment scripts can now provide progress updates using the deployId. 36 | 37 | ## 1.17.3 38 | - Support for MAS 9 log streaming. 39 | - Handling for cleaning up log streaming sessions if a client disconnects unexpectedly. 40 | 41 | ## 1.17.2 42 | - Fixed CSRF handling during the installation bootstrap process. 43 | 44 | ## 1.17.1 45 | - Enhanced error reporting on long running configuration tasks. 46 | 47 | ## 1.17.0 48 | - Add support for specifying a configuration script timeout. 49 | - Add CSRF support. 50 | 51 | ## 1.16.0 52 | - Fixed reports.xml multilookup value from true/false to 1/0 53 | - Added support for specifying a proxy. 54 | - Added support for non-English script status. 55 | ## 1.15.9 56 | - Fixed a bug with MaxVars handling (again). 57 | 58 | ## 1.15.8 59 | - Add domains, added support for Allow Invoking Script Functions? and fixed a bug with MaxVars handling. 60 | 61 | ## 1.15.7 62 | - Provide more flexible source fetching with fallback to check language. 63 | 64 | ## 1.15.6 65 | - Added compatibility to use MXAPIAUTOSCRIPT if MXSCRIPT is not available. 66 | 67 | ## 1.15.5 68 | - Fixed error with deployment file path errors. 69 | 70 | ## 1.15.4 71 | - Fixed errors related to toolbar position defaulting to zero. 72 | - Added nicer support for missing design files in Maximo. 73 | 74 | ## 1.15.3 75 | - Fixed defaults for dploc, qlloc and padloc to be NONE since it is missing from some of the out of the box reports.xml files. 76 | 77 | ## 1.15.2 78 | - Fixed export formatting issues that could cause problems with re-imports. 79 | - Added application name to selection list. 80 | 81 | ## 1.15.1 82 | - Documentation updates. 83 | 84 | ## 1.15.0 85 | - Significant code restructuring to better accommodate future command modules. 86 | - Add support for exporting and importing BIRT reports. 87 | 88 | ## 1.14.4 89 | - Remove Maximo version check because all versions are now supported. 90 | - Minor documentation updated. 91 | 92 | ## 1.14.3 93 | - Updated security to ensure only users with SHARPTREE_UTILS : DEPLOYSCRIPT can perform deploy actions. 94 | 95 | ## 1.14.2 96 | - Minor change so deploy script returns the deployed script's name for the command line tools. 97 | 98 | ## 1.14.1 99 | - Fixed incorrect handling for service, persistent and alternative indexes. 100 | 101 | ## 1.14.0 102 | - Added support for apply Maximo object configurations including placing the server in Admin Mode and running Database Configuration 103 | 104 | ## 1.13.9 105 | - Minor fix for Launch Point variable overrides to ensure the override checkbox is checked and the value updated. 106 | 107 | ## 1.13.8 108 | - Fixed issue with LITERAL script variables. Thanks to Jared Schrag [https://github.com/jaredschrag](https://github.com/jaredschrag) for helping trouble shoot this issue. 109 | 110 | ## 1.13.7 111 | - Emergency rollback of incompatible axios version. 112 | 113 | ## 1.13.6 114 | - Added fallback support for importing screens where the PresentationLoader is not available. 115 | 116 | ## 1.13.5 117 | - Added support for Maximo Manage stand alone development instance. 118 | - Fixed bug with the MaxAuth Only option being ignored. 119 | - Fixed bug that required deploying the selected script again if an install or upgrade was required. 120 | 121 | ## 1.13.3 122 | - Added support for naming a deployment script with a `.deploy` in addition to `-deploy` for naming consistency. 123 | 124 | ## 1.13.3 125 | - Added support for MAS 8.11 126 | 127 | ## 1.13.2 128 | - Added support for JDOM2 as JDOM was removed from 8.6 for screen extracting. 129 | 130 | ## 1.13.1 131 | - Script deploy fix 132 | - MAS / 7.6 inspection form compatibility fix. 133 | 134 | ## 1.13.0 135 | - Add support for JSON deployment object definitions. 136 | - Minor fixes to the Inspection form handling for differences between versions of Maximo. 137 | - Minor snippet fixes. 138 | 139 | ## 1.12.0 140 | - Add snippets support. 141 | 142 | ## 1.11.0 143 | - Add the ability to extract single scripts, screens and forms. 144 | - Removed dependency on DigestUtils which was not available in all versions and patch levels. 145 | - Fixed bug with importing inspection forms with more than one file upload questions. 146 | 147 | ## 1.10.1 148 | - Remove the deploy script by default after the deployment completes. 149 | 150 | ## 1.10.0 151 | - Add support for .devools-config.json local configuration file. 152 | - Fixed issue with cookie handling with the latest release of MAS8 153 | 154 | ## 1.9.0 155 | - Add support for onDeployScript and automatic use of .DEPLOY extension scripts. 156 | - Add support for automatically deploying scripts with the same name as the primary script, with a `-deploy` suffix. 157 | 158 | ## 1.8.5 159 | - Fixed script version numbering. 160 | 161 | ## 1.8.4 162 | - Add the `request` implicit variable to the `onDeploy` context. 163 | 164 | ## 1.8.3 165 | - Fixed issue with extracting inspection forms with names that include path characters. 166 | - Fixed issue with support for missing AUDIOCACHE attributes. 167 | - Fixed incorrect messages for exporting forms. 168 | 169 | ## 1.8.2 170 | - Fixed issue where inspection form null integer values were being exported as zeros instead of null. 171 | 172 | ## 1.8.1 173 | - Add support for extracting and deploying inspection domains and signatures. 174 | 175 | ## 1.8.0 176 | - Add support for extracting and deploying inspection forms. 177 | 178 | ## 1.7.0 179 | - Fixed bug in python scriptConfig parser that would find the scriptConfig even if it was commented out. 180 | - Add support for specifying maxvar, properties and maxmessages values in the scriptConfig. 181 | 182 | ## 1.6.4 183 | - Fixed typo in documentation. 184 | - Removed debug console.log and System.out statements. 185 | 186 | ## 1.6.3 187 | - Compatibility fixes for Maximo Manage 8.5. 188 | - Fixed log header issue that caused log streaming to fail prematurely. 189 | 190 | ## 1.6.2 191 | - Updated dependencies to address security bulletins. 192 | 193 | ## 1.6.1 194 | - Fixed error that could occur when applying the log level as part of the initial install. 195 | 196 | ## 1.6.0 197 | - Added support for exporting screen definition conditional properties. 198 | - Added support for Log4j 2, for Maximo environments that have been patched for Log4Shell. 199 | 200 | ## 1.5.1 201 | - Added support for systemlib presentation XML. 202 | 203 | ## 1.5.0 204 | - Added support for screen extract and deploy. 205 | - Added tag Id generation shortcut. 206 | - Updated documentation and screen shots. 207 | 208 | ## 1.4.0 209 | - Added log streaming to local file. 210 | - Bug fixes 211 | 212 | ## 1.3.0 213 | - Added api/script context support. 214 | 215 | ## 1.2.0 216 | - Added support for API Key authentication. 217 | 218 | ## 1.1.1 219 | - Fixed missing check for action 220 | 221 | ## 1.1.0 222 | - Add source comparison with the server. 223 | - Fix action name missing from extract. 224 | 225 | ## 1.0.26 226 | - Replace Filbert Python/Jython parsing library with regex to extract the config string. 227 | 228 | ## 1.0.25 229 | - Allow for only sending the Maxauth header. 230 | 231 | ## 1.0.24 232 | - Change to dark theme. 233 | 234 | ## 1.0.23 235 | - Documentation edits. 236 | - Updated icon. 237 | - Updated banner theme. 238 | - Move change log to CHANGELOG.md 239 | 240 | ## 1.0.22 241 | - Add feature for defining an `onDeploy` function that will be called when the script is deployed. 242 | 243 | ## 1.0.21 244 | - Fixed error when extracting scripts with spaces in the name. 245 | 246 | ## 1.0.20 247 | - Documentation update. 248 | 249 | ## 1.0.19 250 | - Documentation updates. 251 | - Prettier configuration details for preserving property quotes. 252 | 253 | ## 1.0.18 254 | - Replaced Authentication Type setting with automatic detection of the authentication type. 255 | 256 | ## 1.0.16 / 17 257 | - Fixed formatting of the Automation Scripts table. 258 | - Fixed untrusted SSL handling. 259 | - Added custom CA setting and handling. 260 | 261 | ## 1.0.15 262 | - Documentation fixes. 263 | 264 | ## 1.0.14 265 | - Documentation updates and build pipeline testing. 266 | 267 | ## 1.0.13 268 | - Documentation updates. 269 | 270 | ## 1.0.12 271 | - MAS 8 with OIDC login support. 272 | - Fixes for Form based login. 273 | 274 | ## 1.0.11 275 | - Updated documentation with Python / Jython example. 276 | 277 | ## 1.0.10 278 | - Fixed Windows path handling. 279 | 280 | ## 1.0.9 281 | - Fixed paging size 282 | - Fixed extract script naming issue. 283 | 284 | ## 1.0.8 285 | - Moved the version dependency back to 1.46. 286 | 287 | ## 1.0.7 288 | - Added extract script functionality. 289 | 290 | ## 1.0.6 291 | 292 | - Fixed checks for attribute launch points. 293 | - Added setting for network timeout. 294 | - Fixed try / catch / finally Python parsing support. 295 | 296 | ## 1.0.4 297 | 298 | - Added Python support. 299 | - Added deployment tracking. 300 | 301 | ## 1.0.3 302 | 303 | - Added context support. 304 | - Added automatic upgrade path support. 305 | 306 | ## 1.0.2 307 | 308 | - Removed check for Java version due to permission issues checking Maximo JVM information. 309 | 310 | ## 1.0.1 311 | 312 | - Add checks for supported versions of Maximo and Java. 313 | - Improve deployment progress feedback. 314 | - Fixed compatibility issue with Maximo versions prior to 7.6.1.2. 315 | 316 | ## 1.0.0 317 | 318 | - Initial release of the Sharptree VS Code Automation Script Deployment Utility. -------------------------------------------------------------------------------- /ThirdPartyNotices.txt: -------------------------------------------------------------------------------- 1 | THIRD-PARTY SOFTWARE NOTICES AND INFORMATION 2 | For Sharptree vscode-autoscript-deploy 3 | 4 | This project incorporates material from the project(s) listed below (collectively, “Third Party Code”). 5 | Sharptree is not the original author of the Third Party Code. The original copyright notice and license 6 | under which Sharptree received such Third Party Code are set out below. This Third Party Code is licensed 7 | to you under their original license terms set forth below. Sharptree reserves all other rights not expressly 8 | granted, whether by implication, estoppel or otherwise. 9 | 10 | 1. Axios version "^0.21.4" (https://github.com/axios/axios) 11 | Copyright (c) 2014-present Matt Zabriskie 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy 14 | of this software and associated documentation files (the "Software"), to deal 15 | in the Software without restriction, including without limitation the rights 16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | copies of the Software, and to permit persons to whom the Software is 18 | furnished to do so, subject to the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be included in 21 | all copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 29 | THE SOFTWARE. 30 | 31 | 2. is-valid-hostname version "^1.0.2" (https://github.com/miguelmota/is-valid-hostname) 32 | MIT license 33 | 34 | Copyright (C) 2014 Miguel Mota 35 | 36 | Permission is hereby granted, free of charge, to any person obtaining a copy of 37 | this software and associated documentation files (the "Software"), to deal in 38 | the Software without restriction, including without limitation the rights to 39 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 40 | of the Software, and to permit persons to whom the Software is furnished to do 41 | so, subject to the following conditions: 42 | 43 | The above copyright notice and this permission notice shall be included in all 44 | copies or substantial portions of the Software. 45 | 46 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 47 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 48 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 49 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 50 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 51 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 52 | SOFTWARE. 53 | 54 | 55 | 3. tough-cookie version "^4.0.0" (https://github.com/salesforce/tough-cookie) 56 | 57 | Copyright (c) 2015, Salesforce.com, Inc. 58 | All rights reserved. 59 | 60 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 61 | 62 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 63 | 64 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 65 | 66 | 3. Neither the name of Salesforce.com nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 67 | 68 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 69 | 70 | 4. filbert version "0.5.0" (https://github.com/differentmatt/filbert) 71 | 72 | Copyright (C) 2012-2014 by Marijn Haverbeke 73 | Copyright (C) 2014 by Matt Lott 74 | 75 | Permission is hereby granted, free of charge, to any person obtaining a copy 76 | of this software and associated documentation files (the "Software"), to deal 77 | in the Software without restriction, including without limitation the rights 78 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 79 | copies of the Software, and to permit persons to whom the Software is 80 | furnished to do so, subject to the following conditions: 81 | 82 | The above copyright notice and this permission notice shall be included in 83 | all copies or substantial portions of the Software. 84 | 85 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 86 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 87 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 88 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 89 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 90 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 91 | THE SOFTWARE. 92 | 93 | Please note that some subdirectories of the CodeMirror distribution 94 | include their own LICENSE files, and are released under different 95 | licences. -------------------------------------------------------------------------------- /app-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharptree/vscode-autoscript-deploy/677ae5d5aed47006a20276958a3c4d9dc9ebe06d/app-icon.png -------------------------------------------------------------------------------- /app-icon.pxd/QuickLook/Icon.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharptree/vscode-autoscript-deploy/677ae5d5aed47006a20276958a3c4d9dc9ebe06d/app-icon.pxd/QuickLook/Icon.tiff -------------------------------------------------------------------------------- /app-icon.pxd/QuickLook/Thumbnail.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharptree/vscode-autoscript-deploy/677ae5d5aed47006a20276958a3c4d9dc9ebe06d/app-icon.pxd/QuickLook/Thumbnail.tiff -------------------------------------------------------------------------------- /app-icon.pxd/data/68C71D65-F1B3-4721-A8FC-AD55FFCA7902: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharptree/vscode-autoscript-deploy/677ae5d5aed47006a20276958a3c4d9dc9ebe06d/app-icon.pxd/data/68C71D65-F1B3-4721-A8FC-AD55FFCA7902 -------------------------------------------------------------------------------- /app-icon.pxd/data/A32F8B02-F377-41DB-B43A-C85CE42E3F7D: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharptree/vscode-autoscript-deploy/677ae5d5aed47006a20276958a3c4d9dc9ebe06d/app-icon.pxd/data/A32F8B02-F377-41DB-B43A-C85CE42E3F7D -------------------------------------------------------------------------------- /app-icon.pxd/data/selectionForContentTransform/meta: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | backingScale 6 | 1 7 | mode 8 | 0 9 | shapeSelectionFilename 10 | shapeSelection 11 | size 12 | 13 | NC10UHpTVFAQAAAAQILAAAAAAABAgsAAAAAAAA== 14 | 15 | softness 16 | 0.0 17 | timestamp 18 | 663791693.48011804 19 | transform 20 | 21 | 1 22 | 0.0 23 | 0.0 24 | 1 25 | 0.0 26 | 0.0 27 | 0.0 28 | 0.0 29 | 30 | version 31 | 2 32 | 33 | 34 | -------------------------------------------------------------------------------- /app-icon.pxd/data/selectionForContentTransform/shapeSelection/meta: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | backingScale 6 | 1 7 | pathFilename 8 | path 9 | version 10 | 1 11 | 12 | 13 | -------------------------------------------------------------------------------- /app-icon.pxd/data/selectionForContentTransform/shapeSelection/path: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharptree/vscode-autoscript-deploy/677ae5d5aed47006a20276958a3c4d9dc9ebe06d/app-icon.pxd/data/selectionForContentTransform/shapeSelection/path -------------------------------------------------------------------------------- /app-icon.pxd/metadata.info: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharptree/vscode-autoscript-deploy/677ae5d5aed47006a20276958a3c4d9dc9ebe06d/app-icon.pxd/metadata.info -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # Node.js 2 | # Build a general Node.js project with npm. 3 | # Add steps that analyze code, save build artifacts, deploy, and more: 4 | # https://docs.microsoft.com/azure/devops/pipelines/languages/javascript 5 | 6 | trigger: 7 | - main 8 | 9 | pool: 10 | vmImage: ubuntu-latest 11 | 12 | variables: 13 | - group: vscode-autoscript-deploy 14 | 15 | steps: 16 | - task: NodeTool@0 17 | inputs: 18 | versionSpec: '18.x' 19 | displayName: 'Install Node.js' 20 | 21 | - script: npm install 22 | displayName: Install NPM 23 | 24 | - bash: | 25 | sudo npm install -g webpack 26 | sudo npm install -g vsce 27 | 28 | displayName: Install VSCE and Webpack 29 | 30 | - script: npm run vsce:package 31 | displayName: Create VSIX 32 | env: 33 | GITHUB_PAT: $(github_pat) 34 | 35 | # For releasable builds, we'll want the branch 36 | # Expects that a 'version.txt' has been laid down by a previous step 37 | - bash: | 38 | PACKAGE_VERSION=$(node -p "require('./package.json').version") 39 | echo "$PACKAGE_VERSION" > version.txt 40 | 41 | echo $(Build.SourceBranch) | sed "s|refs/[^/]*/||" > branch.txt 42 | VERSION_REGEX="## $(echo $PACKAGE_VERSION | sed 's/\./\\./g')" 43 | displayName: Get branch 44 | 45 | # Choose files to publish 46 | - task: CopyFiles@2 47 | displayName: Stage VSIX for publishing 48 | inputs: 49 | contents: |- 50 | *.vsix 51 | version.txt 52 | branch.txt 53 | targetFolder: $(Build.ArtifactStagingDirectory) 54 | 55 | # Publish files as an artifact 56 | - task: PublishPipelineArtifact@1 57 | displayName: Publish VSIX 58 | inputs: 59 | artifact: script-deploy-vs-code 60 | targetPath: $(Build.ArtifactStagingDirectory) 61 | 62 | - bash: | 63 | 64 | PACKAGE_VERSION=$(cat version.txt) 65 | 66 | vsce publish -p $(marketplace_pat) --packagePath $(Build.ArtifactStagingDirectory)/maximo-script-deploy-$PACKAGE_VERSION.vsix 67 | 68 | displayName: 'Publish to Marketplace' 69 | 70 | - bash: | 71 | git config --local user.name "Jason VenHuizen" 72 | git config --local user.email "jason@sharptree.io" 73 | 74 | git remote add github https://jvenhuizen:$(github_pat)@github.com/sharptree/vscode-autoscript-deploy.git 75 | git push github HEAD:main 76 | 77 | PACKAGE_VERSION=$(cat version.txt) 78 | if [ ! $(git tag -l "release/$PACKAGE_VERSION") ]; then 79 | git tag "release/$PACKAGE_VERSION" 80 | fi 81 | 82 | git push github release/$PACKAGE_VERSION 83 | 84 | displayName: 'Publish GitHub' 85 | -------------------------------------------------------------------------------- /images/empty_close_tag_setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharptree/vscode-autoscript-deploy/677ae5d5aed47006a20276958a3c4d9dc9ebe06d/images/empty_close_tag_setting.png -------------------------------------------------------------------------------- /images/example_deploy_object.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharptree/vscode-autoscript-deploy/677ae5d5aed47006a20276958a3c4d9dc9ebe06d/images/example_deploy_object.gif -------------------------------------------------------------------------------- /images/example_script_maximo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharptree/vscode-autoscript-deploy/677ae5d5aed47006a20276958a3c4d9dc9ebe06d/images/example_script_maximo.png -------------------------------------------------------------------------------- /images/extract_scripts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharptree/vscode-autoscript-deploy/677ae5d5aed47006a20276958a3c4d9dc9ebe06d/images/extract_scripts.png -------------------------------------------------------------------------------- /images/install_scripts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharptree/vscode-autoscript-deploy/677ae5d5aed47006a20276958a3c4d9dc9ebe06d/images/install_scripts.png -------------------------------------------------------------------------------- /images/install_scripts_complete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharptree/vscode-autoscript-deploy/677ae5d5aed47006a20276958a3c4d9dc9ebe06d/images/install_scripts_complete.png -------------------------------------------------------------------------------- /images/install_scripts_progress.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharptree/vscode-autoscript-deploy/677ae5d5aed47006a20276958a3c4d9dc9ebe06d/images/install_scripts_progress.png -------------------------------------------------------------------------------- /images/logging_permission.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharptree/vscode-autoscript-deploy/677ae5d5aed47006a20276958a3c4d9dc9ebe06d/images/logging_permission.png -------------------------------------------------------------------------------- /images/palette_compare_example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharptree/vscode-autoscript-deploy/677ae5d5aed47006a20276958a3c4d9dc9ebe06d/images/palette_compare_example.gif -------------------------------------------------------------------------------- /images/palette_compare_screen_example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharptree/vscode-autoscript-deploy/677ae5d5aed47006a20276958a3c4d9dc9ebe06d/images/palette_compare_screen_example.gif -------------------------------------------------------------------------------- /images/palette_form_deploy_example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharptree/vscode-autoscript-deploy/677ae5d5aed47006a20276958a3c4d9dc9ebe06d/images/palette_form_deploy_example.gif -------------------------------------------------------------------------------- /images/palette_form_extract_example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharptree/vscode-autoscript-deploy/677ae5d5aed47006a20276958a3c4d9dc9ebe06d/images/palette_form_extract_example.gif -------------------------------------------------------------------------------- /images/palette_password_deploy_example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharptree/vscode-autoscript-deploy/677ae5d5aed47006a20276958a3c4d9dc9ebe06d/images/palette_password_deploy_example.gif -------------------------------------------------------------------------------- /images/palette_password_extract_example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharptree/vscode-autoscript-deploy/677ae5d5aed47006a20276958a3c4d9dc9ebe06d/images/palette_password_extract_example.gif -------------------------------------------------------------------------------- /images/palette_report_deploy_example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharptree/vscode-autoscript-deploy/677ae5d5aed47006a20276958a3c4d9dc9ebe06d/images/palette_report_deploy_example.gif -------------------------------------------------------------------------------- /images/palette_report_extract_example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharptree/vscode-autoscript-deploy/677ae5d5aed47006a20276958a3c4d9dc9ebe06d/images/palette_report_extract_example.gif -------------------------------------------------------------------------------- /images/palette_screen_deploy_example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharptree/vscode-autoscript-deploy/677ae5d5aed47006a20276958a3c4d9dc9ebe06d/images/palette_screen_deploy_example.gif -------------------------------------------------------------------------------- /images/palette_screen_extract_example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharptree/vscode-autoscript-deploy/677ae5d5aed47006a20276958a3c4d9dc9ebe06d/images/palette_screen_extract_example.gif -------------------------------------------------------------------------------- /images/palette_script_deploy_example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharptree/vscode-autoscript-deploy/677ae5d5aed47006a20276958a3c4d9dc9ebe06d/images/palette_script_deploy_example.gif -------------------------------------------------------------------------------- /images/prettier_config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharptree/vscode-autoscript-deploy/677ae5d5aed47006a20276958a3c4d9dc9ebe06d/images/prettier_config.png -------------------------------------------------------------------------------- /images/select_environment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharptree/vscode-autoscript-deploy/677ae5d5aed47006a20276958a3c4d9dc9ebe06d/images/select_environment.png -------------------------------------------------------------------------------- /images/sharptree_utils_permission.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharptree/vscode-autoscript-deploy/677ae5d5aed47006a20276958a3c4d9dc9ebe06d/images/sharptree_utils_permission.png -------------------------------------------------------------------------------- /images/snippet_description.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharptree/vscode-autoscript-deploy/677ae5d5aed47006a20276958a3c4d9dc9ebe06d/images/snippet_description.png -------------------------------------------------------------------------------- /images/snippets.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharptree/vscode-autoscript-deploy/677ae5d5aed47006a20276958a3c4d9dc9ebe06d/images/snippets.gif -------------------------------------------------------------------------------- /images/stream_maximo_log.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharptree/vscode-autoscript-deploy/677ae5d5aed47006a20276958a3c4d9dc9ebe06d/images/stream_maximo_log.gif -------------------------------------------------------------------------------- /images/unique_id_example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharptree/vscode-autoscript-deploy/677ae5d5aed47006a20276958a3c4d9dc9ebe06d/images/unique_id_example.gif -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "ES2020", 5 | "checkJs": true, /* Typecheck .js files. */ 6 | "lib": [ 7 | "ES2020" 8 | ] 9 | }, 10 | "exclude": [ 11 | "node_modules" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /no.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": false, 4 | "commonjs": true, 5 | "es6": true, 6 | "node": true, 7 | "mocha": true 8 | }, 9 | "parserOptions": { 10 | "ecmaVersion": 2018, 11 | "ecmaFeatures": { 12 | "jsx": true 13 | }, 14 | "sourceType": "module" 15 | }, 16 | "rules": { 17 | "no-const-assign": "warn", 18 | "no-this-before-super": "warn", 19 | "no-undef": "warn", 20 | "no-unreachable": "warn", 21 | "no-unused-vars": "warn", 22 | "constructor-super": "warn", 23 | "valid-typeof": "warn" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "maximo-script-deploy", 3 | "displayName": "Maximo Development Tools", 4 | "description": "An extension for developing and deploying Maximo automation scripts and screens from Visual Studio Code.", 5 | "publisher": "Sharptree", 6 | "version": "1.20.3", 7 | "galleryBanner": { 8 | "color": "#372063", 9 | "theme": "dark" 10 | }, 11 | "icon": "app-icon.png", 12 | "author": { 13 | "name": "Jason VenHuizen", 14 | "email": "jason@sharptree.io" 15 | }, 16 | "bugs": { 17 | "url": "https://github.com/sharptree/vscode-autoscript-deploy/issues" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/sharptree/vscode-autoscript-deploy" 22 | }, 23 | "categories": [ 24 | "Programming Languages", 25 | "Other" 26 | ], 27 | "engines": { 28 | "vscode": "^1.73.0" 29 | }, 30 | "activationEvents": [ 31 | "onCommand:maximo-script-deploy.extract", 32 | "onCommand:maximo-script-deploy.extractOne", 33 | "onCommand:maximo-script-deploy.deploy", 34 | "onCommand:maximo-script-deploy.screens", 35 | "onCommand:maximo-script-deploy.screensOne", 36 | "onCommand:maximo-script-deploy.compare", 37 | "onCommand:maximo-script-deploy.forms", 38 | "onCommand:maximo-script-deploy.formsOne", 39 | "onCommand:maximo-script-deploy.selectEnvironment", 40 | "onStartupFinished" 41 | ], 42 | "main": "./dist/extension", 43 | "contributes": { 44 | "snippets": [ 45 | { 46 | "language": "javascript", 47 | "path": "./snippets/snippets_javascript.json" 48 | }, 49 | { 50 | "language": "python", 51 | "path": "./snippets/snippets_python.json" 52 | } 53 | ], 54 | "configuration": { 55 | "title": "Maximo", 56 | "properties": { 57 | "sharptree.maximo.customCA": { 58 | "type": "string", 59 | "default": null, 60 | "editPresentation": "multilineText", 61 | "description": "The full CA chain in PEM format." 62 | }, 63 | "sharptree.maximo.host": { 64 | "type": "string", 65 | "default": null, 66 | "description": "The Maximo host name." 67 | }, 68 | "sharptree.maximo.user": { 69 | "type": "string", 70 | "default": null, 71 | "description": "The Maximo user name." 72 | }, 73 | "sharptree.maximo.context": { 74 | "type": "string", 75 | "default": "maximo", 76 | "description": "The Maximo URL context." 77 | }, 78 | "sharptree.maximo.extractLocation": { 79 | "type": "string", 80 | "default": "", 81 | "description": "The Maximo script extraction location." 82 | }, 83 | "sharptree.maximo.extractScreenLocation": { 84 | "type": "string", 85 | "default": "", 86 | "description": "The Maximo screens extraction location." 87 | }, 88 | "sharptree.maximo.extractInspectionFormsLocation": { 89 | "type": "string", 90 | "default": "", 91 | "description": "The Maximo inspection forms extraction location." 92 | }, 93 | "sharptree.maximo.extractReportsLocation": { 94 | "type": "string", 95 | "default": "", 96 | "description": "The Maximo BIRT reports extraction location." 97 | }, 98 | "sharptree.maximo.port": { 99 | "type": "number", 100 | "default": 443, 101 | "description": "The Maximo port number." 102 | }, 103 | "sharptree.maximo.useSSL": { 104 | "type": "boolean", 105 | "default": true, 106 | "description": "Connect to Maximo using SSL?" 107 | }, 108 | "sharptree.maximo.timeout": { 109 | "type": "number", 110 | "default": 30, 111 | "description": "The timeout in seconds.", 112 | "minimum": 0, 113 | "maximum": 300 114 | }, 115 | "sharptree.maximo.allowUntrustedCerts": { 116 | "type": "boolean", 117 | "default": false, 118 | "description": "Allow untrusted SSL certificates." 119 | }, 120 | "sharptree.maximo.maxauthOnly": { 121 | "type": "boolean", 122 | "default": false, 123 | "description": "Do not send Basic authentication header." 124 | }, 125 | "sharptree.maximo.apiKey": { 126 | "type": "string", 127 | "default": "", 128 | "description": "The Maximo API Key." 129 | }, 130 | "sharptree.maximo.configurationTimeout": { 131 | "type": "number", 132 | "default": 5, 133 | "description": "The Maximo script configuration timeout in minutes.", 134 | "minimum": 0, 135 | "maximum": 60 136 | }, 137 | "sharptree.maximo.logging.outputFile": { 138 | "type": "string", 139 | "default": "maximo.log", 140 | "description": "The relative or absolute output log file path." 141 | }, 142 | "sharptree.maximo.logging.openEditorOnStart": { 143 | "type": "boolean", 144 | "default": true, 145 | "description": "Open an editor to display the log file on start of log streaming." 146 | }, 147 | "sharptree.maximo.logging.append": { 148 | "type": "boolean", 149 | "default": false, 150 | "description": "Append to the current log file." 151 | }, 152 | "sharptree.maximo.logging.follow": { 153 | "type": "boolean", 154 | "default": true, 155 | "description": "Follow the log file when displayed in an editor." 156 | }, 157 | "sharptree.maximo.logging.timeout": { 158 | "type": "number", 159 | "default": 30, 160 | "description": "Number of seconds between logging requests. The value should be no more than 30 seconds, to avoid server side timeouts." 161 | }, 162 | "sharptree.maximo.proxy.host": { 163 | "type": "string", 164 | "default": "", 165 | "description": "The proxy host name." 166 | }, 167 | "sharptree.maximo.proxy.port": { 168 | "type": "number", 169 | "default": 3128, 170 | "description": "The proxy port number." 171 | }, 172 | "sharptree.maximo.proxy.user": { 173 | "type": "string", 174 | "default": "", 175 | "description": "The proxy username for proxy authentication." 176 | }, 177 | "sharptree.maximo.proxy.password": { 178 | "type": "string", 179 | "default": "", 180 | "description": "The proxy password for proxy authentication." 181 | } 182 | } 183 | }, 184 | "commands": [ 185 | { 186 | "command": "maximo-script-deploy.deploy", 187 | "title": "Deploy to Maximo" 188 | }, 189 | { 190 | "command": "maximo-script-deploy.extract", 191 | "title": "Extract All Automation Scripts" 192 | }, 193 | { 194 | "command": "maximo-script-deploy.extractOne", 195 | "title": "Extract Automation Script" 196 | }, 197 | { 198 | "command": "maximo-script-deploy.compare", 199 | "title": "Compare with Maximo" 200 | }, 201 | { 202 | "command": "maximo-script-deploy.screens", 203 | "title": "Extract All Screen Definitions" 204 | }, 205 | { 206 | "command": "maximo-script-deploy.screensOne", 207 | "title": "Extract Screen Definition" 208 | }, 209 | { 210 | "command": "maximo-script-deploy.id", 211 | "title": "Insert Unique Id for Maximo Presentation Tag" 212 | }, 213 | { 214 | "command": "maximo-script-deploy.forms", 215 | "title": "Extract All Inspection Forms" 216 | }, 217 | { 218 | "command": "maximo-script-deploy.formsOne", 219 | "title": "Extract Inspection Form" 220 | }, 221 | { 222 | "command": "maximo-script-deploy.reports", 223 | "title": "Extract All BIRT Reports" 224 | }, 225 | { 226 | "command": "maximo-script-deploy.reportsOne", 227 | "title": "Extract BIRT Report" 228 | }, 229 | { 230 | "command": "maximo-script-deploy.selectEnvironment", 231 | "title": "Select Maximo Environment" 232 | } 233 | ], 234 | "keybindings": [ 235 | { 236 | "command": "maximo-script-deploy.deploy", 237 | "key": "ctrl+alt+m", 238 | "mac": "ctrl+cmd+m", 239 | "when": "editorFocus" 240 | }, 241 | { 242 | "command": "maximo-script-deploy.id", 243 | "key": "ctrl+alt+i", 244 | "mac": "ctrl+cmd+i", 245 | "when": "editorFocus" 246 | } 247 | ] 248 | }, 249 | "scripts": { 250 | "lint": "eslint .", 251 | "pretest": "npm run lint", 252 | "test": "node ./test/runTest.js", 253 | "webpack": "webpack --mode production", 254 | "webpack-dev": "webpack --mode development --watch", 255 | "package": "webpack --mode production --devtool hidden-source-map", 256 | "vscode:prepublish": "npm run package", 257 | "vsce:package": "vsce package" 258 | }, 259 | "devDependencies": { 260 | "@types/glob": "^7.1.4", 261 | "@types/mocha": "^9.1.1", 262 | "@types/node": "^14.18.48", 263 | "@types/vscode": "^1.57", 264 | "@vscode/test-electron": "^1.4.6", 265 | "eslint": "^8.41.0", 266 | "glob": "^7.2.3", 267 | "mocha": "^9.2.2", 268 | "typescript": "^4.9.5", 269 | "webpack": "^5.84.1", 270 | "webpack-cli": "^4.10.0" 271 | }, 272 | "dependencies": { 273 | "archiver": "^7.0.1", 274 | "axios": "^0.26.1", 275 | "axios-oauth-client": "^1.5.0", 276 | "axios-token-interceptor": "^0.2.0", 277 | "is-valid-hostname": "^1.0.2", 278 | "readline": "^1.3.0", 279 | "semver": "^7.5.1", 280 | "temp": "^0.9.4", 281 | "text-encoding": "^0.7.0", 282 | "tough-cookie": "^4.1.2", 283 | "uuid": "^10.0.0", 284 | "webpack-node-externals": "^3.0.0", 285 | "xml-formatter": "^2.6.1", 286 | "xml2js": "^0.6.0", 287 | "yauzl": "^3.1.3" 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /resources/sharptree.autoscript.admin.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | // @ts-nocheck 3 | RuntimeException = Java.type('java.lang.RuntimeException'); 4 | ConfigureServiceRemote = Java.type('psdi.app.configure.ConfigureServiceRemote'); 5 | AdminModeManager = Java.type('psdi.security.AdminModeManager'); 6 | MXServer = Java.type('psdi.server.MXServer'); 7 | SqlFormat = Java.type('psdi.mbo.SqlFormat'); 8 | 9 | MXException = Java.type('psdi.util.MXException'); 10 | 11 | main(); 12 | 13 | function main() { 14 | // if the implicit request variable is present then we are in the context of a REST request 15 | if (typeof request !== 'undefined' && request) { 16 | try { 17 | checkPermissions('SHARPTREE_UTILS', 'DEPLOYSCRIPT'); 18 | 19 | var result = {}; 20 | try { 21 | var action = getRequestAction(); 22 | 23 | if (action) { 24 | action = action.toLowerCase(); 25 | } 26 | 27 | if ( 28 | action == 'adminmodeon' && 29 | request.getHttpMethod() == 'POST' 30 | ) { 31 | setAdminModeOn(); 32 | result.adminModeOnStarted = true; 33 | } else if ( 34 | action == 'adminmodeoff' && 35 | request.getHttpMethod() == 'POST' 36 | ) { 37 | setAdminModeOff(); 38 | result.adminModeOffStarted = true; 39 | } else if ( 40 | action == 'adminmodeon' && 41 | request.getHttpMethod() == 'GET' 42 | ) { 43 | result.adminModeOn = isAdminModeOn(); 44 | } else if ( 45 | action == 'adminmodeoff' && 46 | request.getHttpMethod() == 'GET' 47 | ) { 48 | result.adminModeOff = isAdminModeOff(); 49 | } else if ( 50 | action == 'configuring' && 51 | request.getHttpMethod() == 'GET' 52 | ) { 53 | result.configuring = dbConfigInProgress(); 54 | } else if ( 55 | action == 'configdbrequired' && 56 | request.getHttpMethod() == 'GET' 57 | ) { 58 | result.configDBRequired = configDBRequired(); 59 | } else if ( 60 | action == 'configdblevel' && 61 | request.getHttpMethod() == 'GET' 62 | ) { 63 | result.configDBLevel = configDBLevel(); 64 | } else if ( 65 | action == 'configdbrequiresadminmode' && 66 | request.getHttpMethod() == 'GET' 67 | ) { 68 | result.configDBRequiresAdminMode = configDBRequiresAdmin(); 69 | } else if ( 70 | action == 'applyconfigdb' && 71 | request.getHttpMethod() == 'POST' 72 | ) { 73 | applyConfigDB(); 74 | result.configDBStarted = true; 75 | } else if ( 76 | action == 'configmessages' && 77 | request.getHttpMethod() == 'GET' 78 | ) { 79 | result.messages = getConfigurationMessages(); 80 | } 81 | result.status = 'ok'; 82 | } catch (error) { 83 | result.error = error.toString(); 84 | result.status = 'error'; 85 | } 86 | 87 | responseBody = JSON.stringify(result, null, 4); 88 | } catch (error) { 89 | var response = {}; 90 | response.status = 'error'; 91 | Java.type('java.lang.System').out.println(error); 92 | if (error instanceof AdminError) { 93 | response.message = error.message; 94 | response.reason = error.reason; 95 | } else if (error instanceof Error) { 96 | response.message = error.message; 97 | } else if (error instanceof MXException) { 98 | response.reason = 99 | error.getErrorGroup() + '_' + error.getErrorKey(); 100 | response.message = error.getMessage(); 101 | } else if (error instanceof RuntimeException) { 102 | if (error.getCause() instanceof MXException) { 103 | response.reason = 104 | error.getCause().getErrorGroup() + 105 | '_' + 106 | error.getCause().getErrorKey(); 107 | response.message = error.getCause().getMessage(); 108 | } else { 109 | response.reason = 'runtime_exception'; 110 | response.message = error.getMessage(); 111 | } 112 | } else { 113 | response.cause = error; 114 | } 115 | 116 | if (typeof httpMethod !== 'undefined') { 117 | responseBody = JSON.stringify(response); 118 | } 119 | 120 | service.log_error(error); 121 | 122 | return; 123 | } 124 | } 125 | } 126 | 127 | function getConfigurationMessages() { 128 | var configureService = MXServer.getMXServer().lookup('CONFIGURE'); 129 | 130 | var listenerSet = MXServer.getMXServer().getMboSet( 131 | 'PROCESSMONITOR', 132 | userInfo 133 | ); 134 | try { 135 | var listener = listenerSet.add(); 136 | configureService.listenToConfigDB(listener, true); 137 | 138 | return listener.getString('STATUS'); 139 | } finally { 140 | if (listenerSet) { 141 | try { 142 | listenerSet.close(); 143 | listenerSet.cleanup(); 144 | } catch (ignored) { 145 | // ignored 146 | } 147 | } 148 | } 149 | } 150 | 151 | function setAdminModeOn() { 152 | if ( 153 | !MXServer.getMXServer() 154 | .lookup('SECURITY') 155 | .getProfile(userInfo) 156 | .hasAppOption('CONFIGUR', 'ADMINMODE') 157 | ) { 158 | throw new Error( 159 | 'You do not have permission to manage the system Admin Mode. Make sure you have the "Manage Admin Mode" security permission for the "Database Configuration" application.' 160 | ); 161 | } 162 | if (isAdminModeOff()) { 163 | MXServer.getMXServer().reloadAdminModeByThread( 164 | AdminModeManager.ADMIN_ON, 165 | null 166 | ); 167 | } 168 | } 169 | 170 | function setAdminModeOff() { 171 | if ( 172 | !MXServer.getMXServer() 173 | .lookup('SECURITY') 174 | .getProfile(userInfo) 175 | .hasAppOption('CONFIGUR', 'ADMINMODE') 176 | ) { 177 | throw new Error( 178 | 'You do not have permission to manage the system Admin Mode. Make sure you have the "Manage Admin Mode" security permission for the "Database Configuration" application.' 179 | ); 180 | } 181 | if (isAdminModeOn()) { 182 | MXServer.getMXServer().reloadAdminModeByThread( 183 | AdminModeManager.ADMIN_OFF, 184 | null 185 | ); 186 | } 187 | } 188 | 189 | function isAdminModeOn() { 190 | return MXServer.getMXServer().isAdminModeOn(true); 191 | } 192 | 193 | function isAdminModeOff() { 194 | return MXServer.getMXServer().isAdminModeOff(true); 195 | } 196 | 197 | function configDBRequired() { 198 | var configureService = MXServer.getMXServer().lookup('CONFIGURE'); 199 | return ( 200 | configureService.getConfigLevel(userInfo) !== 201 | ConfigureServiceRemote.CONFIGDB_NONE 202 | ); 203 | } 204 | 205 | function configDBLevel() { 206 | var configureService = MXServer.getMXServer().lookup('CONFIGURE'); 207 | return configureService.getConfigLevel(userInfo); 208 | } 209 | 210 | function configDBRequiresAdmin() { 211 | var configureService = MXServer.getMXServer().lookup('CONFIGURE'); 212 | return ( 213 | configureService.getConfigLevel(userInfo) == 214 | ConfigureServiceRemote.CONFIGDB_STRUCT 215 | ); 216 | } 217 | 218 | function dbConfigInProgress() { 219 | var maxVarsService = MXServer.getMXServer().lookup('MAXVARS'); 220 | var configureService = MXServer.getMXServer().lookup('CONFIGURE'); 221 | return ( 222 | maxVarsService.getBoolean('CONFIGURING', null) || 223 | configureService.configIsRunning() 224 | ); 225 | } 226 | 227 | function applyConfigDB() { 228 | if ( 229 | !MXServer.getMXServer() 230 | .lookup('SECURITY') 231 | .getProfile(userInfo) 232 | .hasAppOption('CONFIGUR', 'CONFIGURE') 233 | ) { 234 | throw new Error( 235 | 'You do not have permission to configure the database. Make sure you have the "Apply Configuration Changes" security permission for the "Database Configuration" application.' 236 | ); 237 | } 238 | 239 | var set = MXServer.getMXServer().getMboSet('PROCESSMONITOR', userInfo); 240 | try { 241 | var monitor = set.add(); 242 | var configureService = MXServer.getMXServer().lookup('CONFIGURE'); 243 | configureService.runConfigDB(monitor); 244 | } finally { 245 | try { 246 | set.close(); 247 | set.cleanup(); 248 | } catch (ignored) { 249 | // ignored 250 | } 251 | } 252 | } 253 | 254 | function getRequestAction() { 255 | var httpRequest = request.getHttpServletRequest(); 256 | 257 | var requestURI = httpRequest.getRequestURI(); 258 | var contextPath = httpRequest.getContextPath(); 259 | var resourceReq = requestURI; 260 | 261 | if (contextPath && contextPath !== '') { 262 | resourceReq = requestURI.substring(contextPath.length()); 263 | } 264 | 265 | if (!resourceReq.startsWith('/')) { 266 | resourceReq = '/' + resourceReq; 267 | } 268 | 269 | var isOSLC = true; 270 | 271 | if ( 272 | !resourceReq 273 | .toLowerCase() 274 | .startsWith('/oslc/script/' + service.scriptName.toLowerCase()) 275 | ) { 276 | if ( 277 | !resourceReq 278 | .toLowerCase() 279 | .startsWith('/api/script/' + service.scriptName.toLowerCase()) 280 | ) { 281 | return null; 282 | } else { 283 | osOSLC = false; 284 | } 285 | } 286 | 287 | var baseReqPath = isOSLC 288 | ? '/oslc/script/' + service.scriptName 289 | : '/api/script/' + service.scriptName; 290 | var action = resourceReq.substring(baseReqPath.length); 291 | 292 | if (action.startsWith('/')) { 293 | action = action.substring(1); 294 | } 295 | 296 | if (!action || action.trim() === '') { 297 | return null; 298 | } 299 | 300 | return action.toLowerCase(); 301 | } 302 | 303 | function checkPermissions(app, optionName) { 304 | if (!userInfo) { 305 | throw new AdminError( 306 | 'no_user_info', 307 | 'The userInfo global variable has not been set, therefore the user permissions cannot be verified.' 308 | ); 309 | } 310 | 311 | var userProfile = MXServer.getMXServer() 312 | .lookup('SECURITY') 313 | .getProfile(userInfo); 314 | 315 | if (!userProfile.hasAppOption(app, optionName) && !isInAdminGroup()) { 316 | throw new AdminError( 317 | 'no_permission', 318 | 'The user ' + 319 | userInfo.getUserName() + 320 | ' does not have access to the ' + 321 | optionName + 322 | ' option in the ' + 323 | app + 324 | ' object structure.' 325 | ); 326 | } 327 | } 328 | 329 | // Determines if the current user is in the administrator group, returns true if the user is, false otherwise. 330 | function isInAdminGroup() { 331 | var user = userInfo.getUserName(); 332 | service.log_info( 333 | 'Determining if the user ' + user + ' is in the administrator group.' 334 | ); 335 | var groupUserSet; 336 | 337 | try { 338 | groupUserSet = MXServer.getMXServer().getMboSet( 339 | 'GROUPUSER', 340 | MXServer.getMXServer().getSystemUserInfo() 341 | ); 342 | 343 | // Get the ADMINGROUP MAXVAR value. 344 | var adminGroup = MXServer.getMXServer() 345 | .lookup('MAXVARS') 346 | .getString('ADMINGROUP', null); 347 | 348 | // Query for the current user and the found admin group. 349 | // The current user is determined by the implicity `user` variable. 350 | sqlFormat = new SqlFormat('userid = :1 and groupname = :2'); 351 | sqlFormat.setObject(1, 'GROUPUSER', 'USERID', user); 352 | sqlFormat.setObject(2, 'GROUPUSER', 'GROUPNAME', adminGroup); 353 | groupUserSet.setWhere(sqlFormat.format()); 354 | 355 | if (!groupUserSet.isEmpty()) { 356 | service.log_info( 357 | 'The user ' + 358 | user + 359 | ' is in the administrator group ' + 360 | adminGroup + 361 | '.' 362 | ); 363 | return true; 364 | } else { 365 | service.log_info( 366 | 'The user ' + 367 | user + 368 | ' is not in the administrator group ' + 369 | adminGroup + 370 | '.' 371 | ); 372 | return false; 373 | } 374 | } finally { 375 | _close(groupUserSet); 376 | } 377 | } 378 | 379 | // Cleans up the MboSet connections and closes the set. 380 | function _close(set) { 381 | if (set) { 382 | try { 383 | set.cleanup(); 384 | set.close(); 385 | } catch (ignore) { 386 | // ignored 387 | } 388 | } 389 | } 390 | 391 | function AdminError(reason, message) { 392 | Error.call(this, message); 393 | this.reason = reason; 394 | this.message = message; 395 | } 396 | 397 | // ConfigurationError derives from Error 398 | AdminError.prototype = Object.create(Error.prototype); 399 | AdminError.prototype.constructor = AdminError; 400 | AdminError.prototype.element; 401 | 402 | // eslint-disable-next-line no-unused-vars 403 | var scriptConfig = { 404 | autoscript: 'SHARPTREE.AUTOSCRIPT.ADMIN', 405 | description: 'Sharptree automation script for admin operations', 406 | version: '1.0.0', 407 | active: true, 408 | logLevel: 'INFO', 409 | }; 410 | -------------------------------------------------------------------------------- /resources/sharptree.autoscript.install.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | /* eslint-disable no-unused-vars */ 3 | // @ts-nocheck 4 | /* 5 | * Bootstrap script that is used to install the Sharptree Automation Script deploy utility. 6 | */ 7 | MboConstants = Java.type('psdi.mbo.MboConstants'); 8 | 9 | DBShortcut = Java.type('psdi.mbo.DBShortcut'); 10 | 11 | SqlFormat = Java.type('psdi.mbo.SqlFormat'); 12 | 13 | MXServer = Java.type('psdi.server.MXServer'); 14 | 15 | MXSession = Java.type('psdi.util.MXSession'); 16 | 17 | MXException = Java.type('psdi.util.MXException'); 18 | 19 | RuntimeException = Java.type('java.lang.RuntimeException'); 20 | 21 | main(); 22 | 23 | function main() { 24 | try { 25 | // Before doing anything else, make sure we have a valid installation user that is part of the administrator group. 26 | if (!isInAdminGroup()) { 27 | // The installation user must be in the admin group. 28 | throw new ScriptError( 29 | 'not_admin', 30 | 'The user ' + 31 | userInfo.getUserName() + 32 | ' is not in the Maximo Administrators group and cannot perform the install.' 33 | ); 34 | } 35 | // Set up the Maximo loggers that will be used by the Sharptree Automation Script deploy utility. 36 | setupLoggers(); 37 | 38 | verifyIntegrationObjectSecurity(); 39 | 40 | setupSigOptions(); 41 | 42 | var success = { 43 | status: 'success', 44 | }; 45 | 46 | responseBody = JSON.stringify(response); 47 | return; 48 | } catch (error) { 49 | var response = {}; 50 | response.status = 'error'; 51 | 52 | if (error instanceof ScriptError) { 53 | response.message = error.message; 54 | response.reason = error.reason; 55 | } else if (error instanceof Error) { 56 | response.message = error.message; 57 | } else if (error instanceof MXException) { 58 | response.reason = error.getErrorGroup() + '_' + error.getErrorKey(); 59 | response.message = error.getMessage(); 60 | } else if (error instanceof RuntimeException) { 61 | if (error.getCause() instanceof MXException) { 62 | response.reason = 63 | error.getCause().getErrorGroup() + 64 | '_' + 65 | error.getCause().getErrorKey(); 66 | response.message = error.getCause().getMessage(); 67 | } else { 68 | response.reason = 'runtime_exception'; 69 | response.message = error.getMessage(); 70 | } 71 | } else { 72 | response.cause = error; 73 | } 74 | 75 | responseBody = JSON.stringify(response); 76 | return; 77 | } 78 | } 79 | 80 | function fixInspectionForms() { 81 | // Because Maximo demo data is poor, inspection forms are shipped with missing YORN values that need to be fixed. 82 | var db = new DBShortcut(); 83 | try { 84 | db.connect(userInfo.getConnectionKey()); 85 | db.execute( 86 | DBShortcut.UPDATE, 87 | 'update inspectionform set readconfirmation = 0 where readconfirmation is null' 88 | ); 89 | db.execute( 90 | DBShortcut.UPDATE, 91 | 'update inspectionform set audioguided = 0 where audioguided is null' 92 | ); 93 | db.commit(); 94 | } finally { 95 | db.close(); 96 | } 97 | } 98 | 99 | // Set up the Sharptree Automation Script deploy utility loggers 100 | function setupLoggers() { 101 | service.log_info( 102 | 'Setting up the Sharptree Automation Script deploy utility Maximo loggers.' 103 | ); 104 | 105 | var loggerSet; 106 | 107 | try { 108 | loggerSet = MXServer.getMXServer().getMboSet( 109 | 'MAXLOGGER', 110 | MXServer.getMXServer().getSystemUserInfo() 111 | ); 112 | 113 | var sqlFormat = new SqlFormat('logger = :1'); 114 | sqlFormat.setObject(1, 'MAXLOGGER', 'LOGGER', 'autoscript'); 115 | 116 | loggerSet.setWhere(sqlFormat.format()); 117 | 118 | // if the out of the box root logger is missing, abort. 119 | if (!loggerSet.isEmpty()) { 120 | var scriptLogger = loggerSet.getMbo(0); 121 | // Add or update the Sharptree and Opqo loggers. 122 | addOrUpdateLogger('SHARPTREE.AUTOSCRIPT', 'WARN', scriptLogger); 123 | loggerSet.save(); 124 | try { 125 | MXServer.getMXServer().lookup('LOGGING').applySettings(false); 126 | } catch (error) { 127 | if ( 128 | error instanceof MXException && 129 | error.getErrorKey() == 'applySettingsForFile' && 130 | error.getErrorGroup() == 'logging' 131 | ) { 132 | service.log_info( 133 | 'Set up the Sharptree Automation Script deploy utility Maximo loggers.' 134 | ); 135 | return; 136 | } else { 137 | throw error; 138 | } 139 | } 140 | service.log_info( 141 | 'Set up the Sharptree Automation Script deploy utility Maximo loggers.' 142 | ); 143 | } else { 144 | service.log_warn( 145 | // eslint-disable-next-line quotes 146 | "The root out of the box logger 'autoscript' does not exist, skipping setting up Sharptree Automation Script deploy utility Maximo loggers." 147 | ); 148 | } 149 | } finally { 150 | _close(loggerSet); 151 | } 152 | } 153 | 154 | // Adds or updates a Sharptree Automation Script deploy utility logger. 155 | // This relies on the calling function calling save on the provided parent MboSet to save the changes. 156 | function addOrUpdateLogger(logger, level, parent) { 157 | service.log_info( 158 | 'Adding or updating the logger ' + 159 | logger + 160 | ' and setting the level to ' + 161 | level + 162 | '.' 163 | ); 164 | var loggerSet; 165 | try { 166 | loggerSet = MXServer.getMXServer().getMboSet( 167 | 'MAXLOGGER', 168 | MXServer.getMXServer().getSystemUserInfo() 169 | ); 170 | 171 | // Query for the log key 172 | var sqlFormat = new SqlFormat('logkey = :1'); 173 | sqlFormat.setObject( 174 | 1, 175 | 'MAXLOGGER', 176 | 'LOGKEY', 177 | parent.getString('LOGKEY') + '.' + logger 178 | ); 179 | 180 | loggerSet.setWhere(sqlFormat.format()); 181 | var child; 182 | // if the logkey does not exist create it, otherwise get the existing logger and update its level. 183 | if (loggerSet.isEmpty()) { 184 | child = parent.getMboSet('CHILDLOGGERS').add(); 185 | child.setValue('LOGGER', logger); 186 | service.log_info( 187 | 'Added the logger ' + 188 | logger + 189 | ' and set the level to ' + 190 | level + 191 | '.' 192 | ); 193 | } else { 194 | // Create a unique child MboSet name and then query based on the logkey where clause. 195 | var mboSetName = '$' + logger; 196 | child = parent 197 | .getMboSet(mboSetName, 'MAXLOGGER', sqlFormat.format()) 198 | .getMbo(0); 199 | service.log_info( 200 | 'Updated the logger ' + 201 | logger + 202 | ' and set the level to ' + 203 | level + 204 | '.' 205 | ); 206 | } 207 | 208 | child.setValue('LOGLEVEL', level); 209 | } finally { 210 | _close(loggerSet); 211 | } 212 | } 213 | 214 | // Verifies that object security has been configured on the STAUTOSCRIPT integration object structure. 215 | function verifyIntegrationObjectSecurity() { 216 | var maxIntObjectSet; 217 | var sigOptionSet; 218 | var appAuthSet; 219 | try { 220 | service.log_info( 221 | 'Verifying that the integration object security has been configured.' 222 | ); 223 | 224 | maxIntObjectSet = MXServer.getMXServer().getMboSet( 225 | 'MAXINTOBJECT', 226 | MXServer.getMXServer().getSystemUserInfo() 227 | ); 228 | 229 | var sqlFormat = new SqlFormat('intobjectname = :1'); 230 | sqlFormat.setObject( 231 | 1, 232 | 'MAXINTOBJECT', 233 | 'INTOBJECTNAME', 234 | 'SHARPTREE_UTILS' 235 | ); 236 | 237 | maxIntObjectSet.setWhere(sqlFormat.format()); 238 | 239 | var maxIntObject; 240 | 241 | if (maxIntObjectSet.isEmpty()) { 242 | maxIntObject = maxIntObjectSet.add(); 243 | maxIntObject.setValue('INTOBJECTNAME', 'SHARPTREE_UTILS'); 244 | maxIntObject.setValue( 245 | 'DESCRIPTION', 246 | 'Sharptree Utilities Integration Security' 247 | ); 248 | maxIntObject.setValue('USEWITH', 'INTEGRATION'); 249 | var maxIntObjDetail = maxIntObject 250 | .getMboSet('MAXINTOBJDETAIL') 251 | .add(); 252 | maxIntObjDetail.setValue('OBJECTNAME', 'DUMMY_TABLE'); 253 | maxIntObjectSet.save(); 254 | 255 | var id = maxIntObject.getUniqueIDValue(); 256 | maxIntObjectSet.reset(); 257 | maxIntObject = maxIntObjectSet.getMboForUniqueId(id); 258 | } else { 259 | maxIntObject = maxIntObjectSet.getMbo(0); 260 | } 261 | 262 | if (!maxIntObject.getBoolean('USEOSSECURITY')) { 263 | service.log_info( 264 | 'Object structure security has not be configured for SHARPTREE_UTILS, checking for pre-existing security configurations.' 265 | ); 266 | 267 | // check if the left over options have been granted to any groups and if so then remove them. 268 | appAuthSet = MXServer.getMXServer().getMboSet( 269 | 'APPLICATIONAUTH', 270 | MXServer.getMXServer().getSystemUserInfo() 271 | ); 272 | 273 | sqlFormat = new SqlFormat('app = :1'); 274 | sqlFormat.setObject(1, 'APPLICATIONAUTH', 'APP', 'SHARPTREE_UTILS'); 275 | 276 | appAuthSet.setWhere(sqlFormat.format()); 277 | 278 | if (!appAuthSet.isEmpty()) { 279 | appAuthSet.deleteAll(); 280 | appAuthSet.save(); 281 | } 282 | 283 | // Deleting the object structure does not remove the security objects, so we need to make sure they aren't left over from a previous install. 284 | sigOptionSet = MXServer.getMXServer().getMboSet( 285 | 'SIGOPTION', 286 | MXServer.getMXServer().getSystemUserInfo() 287 | ); 288 | 289 | sqlFormat = new SqlFormat('app = :1'); 290 | sqlFormat.setObject(1, 'SIGOPTION', 'APP', 'SHARPTREE_UTILS'); 291 | 292 | sigOptionSet.setWhere(sqlFormat.format()); 293 | 294 | if (!sigOptionSet.isEmpty()) { 295 | sigOptionSet.deleteAll(); 296 | sigOptionSet.save(); 297 | } 298 | 299 | service.log_info( 300 | 'Security options are configured for SHARPTREE_UTILS, removing before setting up object structure security.' 301 | ); 302 | 303 | maxIntObject['setValue(String, boolean)']('USEOSSECURITY', true); 304 | } 305 | 306 | maxIntObjectSet.save(); 307 | 308 | service.log_info( 309 | 'Verified that the integration object security has been configured.' 310 | ); 311 | } finally { 312 | _close(appAuthSet); 313 | _close(sigOptionSet); 314 | _close(maxIntObjectSet); 315 | } 316 | } 317 | 318 | // Sets up the required signature security options for performing the Sharptree Automation Script deploy utility installation. 319 | function setupSigOptions() { 320 | service.log_info( 321 | 'Setting up deploy script security options for Sharptree Automation Script deploy utility.' 322 | ); 323 | var sigOptionSet; 324 | try { 325 | sigOptionSet = MXServer.getMXServer().getMboSet( 326 | 'SIGOPTION', 327 | MXServer.getMXServer().getSystemUserInfo() 328 | ); 329 | 330 | // Query for the STAUTOSCRIPT app with the STADMINACTION option. 331 | var sqlFormat = new SqlFormat('app = :1 and optionname = :2'); 332 | sqlFormat.setObject(1, 'SIGOPTION', 'APP', 'SHARPTREE_UTILS'); 333 | sqlFormat.setObject(2, 'SIGOPTION', 'OPTIONNAME', 'DEPLOYSCRIPT'); 334 | 335 | sigOptionSet.setWhere(sqlFormat.format()); 336 | 337 | // If the STADMINACTION does not exist then create it. 338 | if (sigOptionSet.isEmpty()) { 339 | service.log_info( 340 | 'The administrative security option DEPLOYSCRIPT for the SHARPTREE_UTILS integration object structure does not exist, creating it.' 341 | ); 342 | 343 | var sigoption = sigOptionSet.add(); 344 | sigoption.setValue('APP', 'SHARPTREE_UTILS'); 345 | sigoption.setValue('OPTIONNAME', 'DEPLOYSCRIPT'); 346 | sigoption.setValue('DESCRIPTION', 'Deploy Automation Script'); 347 | sigoption.setValue('ESIGENABLED', false); 348 | sigoption.setValue('VISIBLE', true); 349 | sigOptionSet.save(); 350 | reloadRequired = true; 351 | } else { 352 | service.log_info( 353 | 'The administrative security option DEPLOYSCRIPT for the SHARPTREE_UTILS integration object structure already exists, skipping.' 354 | ); 355 | } 356 | 357 | service.log_info( 358 | 'Set up administrative security options for Sharptree Automation Script deploy utility.' 359 | ); 360 | } finally { 361 | _close(sigOptionSet); 362 | } 363 | } 364 | 365 | // Determines if the current user is in the administrator group, returns true if the user is, false otherwise. 366 | function isInAdminGroup() { 367 | var user = userInfo.getUserName(); 368 | service.log_info( 369 | 'Determining if the user ' + user + ' is in the administrator group.' 370 | ); 371 | var groupUserSet; 372 | 373 | try { 374 | groupUserSet = MXServer.getMXServer().getMboSet( 375 | 'GROUPUSER', 376 | MXServer.getMXServer().getSystemUserInfo() 377 | ); 378 | 379 | // Get the ADMINGROUP MAXVAR value. 380 | var adminGroup = MXServer.getMXServer() 381 | .lookup('MAXVARS') 382 | .getString('ADMINGROUP', null); 383 | 384 | // Query for the current user and the found admin group. 385 | // The current user is determined by the implicity `user` variable. 386 | sqlFormat = new SqlFormat('userid = :1 and groupname = :2'); 387 | sqlFormat.setObject(1, 'GROUPUSER', 'USERID', user); 388 | sqlFormat.setObject(2, 'GROUPUSER', 'GROUPNAME', adminGroup); 389 | groupUserSet.setWhere(sqlFormat.format()); 390 | 391 | if (!groupUserSet.isEmpty()) { 392 | service.log_info( 393 | 'The user ' + 394 | user + 395 | ' is in the administrator group ' + 396 | adminGroup + 397 | '.' 398 | ); 399 | return true; 400 | } else { 401 | service.log_info( 402 | 'The user ' + 403 | user + 404 | ' is not in the administrator group ' + 405 | adminGroup + 406 | '.' 407 | ); 408 | return false; 409 | } 410 | } finally { 411 | _close(groupUserSet); 412 | } 413 | } 414 | 415 | function ScriptError(reason, message) { 416 | Error.call(this, message); 417 | this.reason = reason; 418 | this.message = message; 419 | } 420 | 421 | // ConfigurationError derives from Error 422 | ScriptError.prototype = Object.create(Error.prototype); 423 | ScriptError.prototype.constructor = ScriptError; 424 | ScriptError.prototype.element; 425 | 426 | // Cleans up the MboSet connections and closes the set. 427 | function _close(set) { 428 | if (set) { 429 | try { 430 | set.close(); 431 | set.cleanup(); 432 | } catch (ignored) {} 433 | } 434 | } 435 | -------------------------------------------------------------------------------- /schemas/crontask.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "$id": "https://raw.githubusercontent.com/sharptree/vscode-autoscript-deploy/main/schemas/crontask.json", 4 | "title": "Cron Task", 5 | "description": "A Maximo Cron Task", 6 | "type": "object", 7 | "properties": { 8 | "delete":{ 9 | "description":"Flag to indicate that the cron task should be deleted if it exists from the target system.", 10 | "type":"boolean", 11 | "default":false 12 | }, 13 | "cronTaskName": { 14 | "description": "The name of the Cron Task (CRONTASKDEF.CRONTASKNAME)", 15 | "type": "string" 16 | }, 17 | "description": { 18 | "description": "The description of the Cron Task (CRONTASKDEF.DESCRIPTION)", 19 | "type": "string" 20 | }, 21 | "className": { 22 | "description": "The Java class name of the Cron Task implementation (CRONTASKDEF.CLASSNAME)", 23 | "type": "string" 24 | }, 25 | "accessLevel": { 26 | "description": "The access level for the Maximo Cron Task (CRONTASKDEF.ACCESSLEVEL)", 27 | "type": "string", 28 | "enum": ["FULL", "MODIFYONLY", "READONLY"], 29 | "default": "FULL" 30 | }, 31 | "cronTaskInstance": { 32 | "description": "An array of Maximo Cron Task instance definitions", 33 | "type": "array", 34 | "items": { 35 | "type": "object", 36 | "properties": { 37 | "instanceName": { 38 | "description": "The name of the Cron Task instance (CRONTASKINSTANCE.INSTANCENAME)", 39 | "type": "string" 40 | }, 41 | "description": { 42 | "description": "The description of the Cron Task instance (CRONTASKINSTANCE.DESCRIPTION)", 43 | "type": "string" 44 | }, 45 | "schedule": { 46 | "description": "The schedule of the Cron Task instance in Unix cron task format (CRONTASKINSTANCE.SCHEDULE)", 47 | "type": "string", 48 | "default": "1h,*,0,*,*,*,*,*,*,*" 49 | }, 50 | "active": { 51 | "description": "Flag to indicate if the Cron Task instance is active or not (CRONTASKINSTANCE.ACTIVE)", 52 | "type": "boolean", 53 | "default": "false" 54 | }, 55 | "keepHistory": { 56 | "description": "Flag to indicate if the Cron Task instance will retain a history of runs or not (CRONTASKINSTANCE.RETAINHISTORY)", 57 | "type": "boolean", 58 | "default": "true" 59 | }, 60 | "runAsUserId": { 61 | "description": "The user Id that will be used to run the Cron Task instance, must be a valid Maximo user Id (CRONTASKINSTANCE.RUNASUSERID)", 62 | "type": "string", 63 | "default": "MAXADMIN" 64 | }, 65 | "maxHistory": { 66 | "description": "The maximum number of history records to retain (CRONTASKINSTANCE.MAXHISTORY)", 67 | "type": "integer", 68 | "default": "1000" 69 | }, 70 | "cronTaskParam": { 71 | "description": "An array of parameter values for the Cron Task instance.", 72 | "type": "array", 73 | "items": { 74 | "type": "object", 75 | "properties": { 76 | "parameter": { 77 | "description": "The name of the Cron Task parameter. The parameters are automatically added by the class and the name is matched after the parameters are added (CRONTASKPARAM.PARAMETER)", 78 | "type": "string" 79 | }, 80 | "value": { 81 | "description": "The value of the Cron Task parameter (CRONTASKPARAM.VALUE)", 82 | "type": "string" 83 | }, 84 | 85 | "required": "parameter" 86 | } 87 | } 88 | }, 89 | "required": ["instanceName"] 90 | } 91 | } 92 | }, 93 | "required": ["cronTaskName", "className"] 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /schemas/logger.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "$id": "https://raw.githubusercontent.com/sharptree/vscode-autoscript-deploy/main/schemas/logger.json", 4 | "title": "Logger", 5 | "description": "A Maximo logger", 6 | "type": "object", 7 | "properties": { 8 | "delete":{ 9 | "description":"Flag to indicate that the logger should be deleted if it exists from the target system.", 10 | "type":"boolean", 11 | "default":false 12 | }, 13 | "logger": { 14 | "description": "The base logger name for the Maximo logger (MAXLOGGER.LOGGER)", 15 | "type": "string" 16 | }, 17 | "parentLogKey": { 18 | "description": "The log key for the parent logger, this is used to find the parent logger Id (MAXLOGGER.PARENTLOGGERID)", 19 | "type": "string" 20 | }, 21 | "logKey": { 22 | "description": "The log key for the logger, if not provided defaults to log4j.logger.maximo. plus the logger name (MAXLOGGER.LOGKEY)", 23 | "type": "string", 24 | "default": "log4j.logger.maximo. + logKey" 25 | }, 26 | "logLevel": { 27 | "description": "The log level for the Maximo logger (MAXLOGGER.LOGLEVEL)", 28 | "type": "string", 29 | "enum": ["DEBUG", "ERROR", "FATAL", "INFO", "WARN"], 30 | "default": "ERROR" 31 | }, 32 | "active": { 33 | "description": "Flag to indicate if the logger is active or not (MAXLOGGER.ACTIVE)", 34 | "type": "boolean", 35 | "default": "true" 36 | }, 37 | "appenders": { 38 | "description": "A comma delimited list of appenders Maximo logger (MAXLOGGER.APPENDERS)", 39 | "type": "string" 40 | }, 41 | "required": ["logger"] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /schemas/message.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "$id": "https://raw.githubusercontent.com/sharptree/vscode-autoscript-deploy/main/schemas/message.json", 4 | "title": "Message", 5 | "description": "A Maximo message", 6 | "type": "object", 7 | "properties": { 8 | "delete": { 9 | "description": "Flag to indicate that the message should be deleted if it exists from the target system.", 10 | "type": "boolean", 11 | "default": false 12 | }, 13 | "msgGroup": { 14 | "description": "The group for the Maximo message (MAXMESSAGES.MSGGROUP)", 15 | "type": "string" 16 | }, 17 | "msgKey": { 18 | "description": "The key for the Maximo message (MAXMESSAGES.MSGKEY)", 19 | "type": "string" 20 | }, 21 | "value": { 22 | "description": "The value for the Maximo message (MAXMESSAGES.VALUE)", 23 | "type": "string" 24 | }, 25 | "displayMethod": { 26 | "description": "The display for the Maximo message (MAXMESSAGES.DISPLAYMETHOD)", 27 | "type": "string", 28 | "enum": ["MSGBOX", "STATUS", "TEXT"], 29 | "default": "MSGBOX" 30 | }, 31 | "options": { 32 | "description": "The display options for the Maximo message, values are derived from the UI labels and translated to the internal integer value (MAXMESSAGES.OPTIONS)", 33 | "type": "array", 34 | "items": { 35 | "type": "string" 36 | }, 37 | "minItems": 1, 38 | "uniqueItems": true, 39 | "enum": ["ok", "close", "cancel", "yes", "no"], 40 | "default": "ok" 41 | }, 42 | "prefix": { 43 | "description": "The message prefix for the Maximo message (MAXMESSAGES.MSGIDPREFIX)", 44 | "type": "string", 45 | "default": "BMXZZ" 46 | }, 47 | "suffix": { 48 | "description": "The message suffix for the Maximo message (MAXMESSAGES.MSGIDSUFFIX)", 49 | "type": "string", 50 | "enum": ["E", "I", "W"], 51 | "default": "E" 52 | }, 53 | "explanation": { 54 | "description": "The value for the Maximo message explanation (MAXMESSAGES.EXPLANATION)", 55 | "type": "string" 56 | }, 57 | "operatorResponse": { 58 | "description": "The value for the Maximo message operator response (MAXMESSAGES.OPERATORRESPONSE)", 59 | "type": "string" 60 | }, 61 | "adminResponse": { 62 | "description": "The value for the Maximo message admin response (MAXMESSAGES.ADMINRESPONSE)", 63 | "type": "string" 64 | }, 65 | "systemAction": { 66 | "description": "The value for the Maximo message system action (MAXMESSAGES.SYSTEMACTION)", 67 | "type": "string" 68 | }, 69 | 70 | "required": ["msgGroup", "msgKey", "value"] 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /schemas/property.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "$id": "https://raw.githubusercontent.com/sharptree/vscode-autoscript-deploy/main/schemas/logger.json", 4 | "title": "Property", 5 | "description": "A Maximo property", 6 | "type": "object", 7 | "properties": { 8 | "delete":{ 9 | "description":"Flag to indicate that the property should be deleted if it exists from the target system.", 10 | "type":"boolean", 11 | "default":false 12 | }, 13 | "propName": { 14 | "description": "The name for the Maximo property (MAXPROP.PROPNAME)", 15 | "type": "string" 16 | }, 17 | "description": { 18 | "description": "The property description for the Maximo property (MAXPROP.DESCRIPTION)", 19 | "type": "string" 20 | }, 21 | "domainId": { 22 | "description": "The Maximo domain Id associated with the Maximo property. (MAXPROP.DOMAINID)", 23 | "type": "string" 24 | }, 25 | "encrypted": { 26 | "description": "Flag to indicate if the Maximo property is encrypted or not (MAXPROP.ENCRYPTED)", 27 | "type": "boolean", 28 | "default": "false" 29 | }, 30 | "globalOnly": { 31 | "description": "Flag to indicate if the Maximo property is only available in a global context or not (MAXPROP.GLOBALONLY)", 32 | "type": "boolean", 33 | "default": "false" 34 | }, 35 | "instanceOnly": { 36 | "description": "Flag to indicate if the Maximo property is only available in an instance context or not (MAXPROP.INSTANCEONLY)", 37 | "type": "boolean", 38 | "default": "false" 39 | }, 40 | "liveRefresh": { 41 | "description": "Flag to indicate if the Maximo property is able to be live refreshed or not (MAXPROP.LIVEREFRESH)", 42 | "type": "boolean", 43 | "default": "false" 44 | }, 45 | "masked": { 46 | "description": "Flag to indicate if the Maximo property is masked or not when displayed in the System Properties application (MAXPROP.MASKED)", 47 | "type": "boolean", 48 | "default": "false" 49 | }, 50 | "maxType": { 51 | "description": "The Maximo data type of the property (MAXPROP.MAXTYPE)", 52 | "type": "string", 53 | "enum": ["ALN", "INTEGER", "YORN"], 54 | "default": "ALN" 55 | }, 56 | "nullsAllowed": { 57 | "description": "Flag to indicate if the Maximo property allows nulls or not (MAXPROP.NULLSALLOWED)", 58 | "type": "boolean", 59 | "default": "true" 60 | }, 61 | "onlineChanges": { 62 | "description": "Flag to indicate if the Maximo property may be overridden via the Maximo System Properties application or not (MAXPROP.ONLINECHANGES)", 63 | "type": "boolean", 64 | "default": "true" 65 | }, 66 | "secureLevel": { 67 | "description": "The level of access permitted for the property (MAXPROP.SECURELEVEL)", 68 | "type": "string", 69 | "enum": ["PRIVATE", "PUBLIC", "SECURE"], 70 | "default": "PUBLIC" 71 | }, 72 | "propValue": { 73 | "description": "The global value for the property (MAXPROP.DISPPROPVALUE)", 74 | "type": "string" 75 | }, 76 | "maximoDefault": { 77 | "description": "The Maximo default value for the property (MAXPROP.MAXIMODEFAULT)", 78 | "type": "string" 79 | }, 80 | "maxPropInstance": { 81 | "type": "array", 82 | "items": { 83 | "type": "object", 84 | "properties": { 85 | "serverName": { 86 | "description": "The name of the Maximo server for the property instance (MAXPROPINSTANCE.SERVERNAME)", 87 | "type": "string" 88 | }, 89 | "propValue": { 90 | "description": "The value of the Maximo server for the property instance (MAXPROPINSTANCE.DISPPROPVALUE)", 91 | "type": "string" 92 | }, 93 | "serverHost": { 94 | "description": "The value of the Maximo server host name for the property instance (MAXPROPINSTANCE.SERVERHOST)", 95 | "type": "string" 96 | }, 97 | "required": ["serverName"] 98 | } 99 | } 100 | }, 101 | 102 | "required": ["propName"] 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/commands/compare-command.js: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import { parseString } from 'xml2js'; 3 | import { commands, ProgressLocation, window, Uri } from 'vscode'; 4 | 5 | // get a reference to the fetched source object 6 | import { fetchedSource } from '../extension'; 7 | 8 | // @ts-ignore 9 | import * as format from 'xml-formatter'; 10 | 11 | export default async function compareCommand(client) { 12 | // Get the active text editor 13 | const editor = window.activeTextEditor; 14 | 15 | if (editor) { 16 | let document = editor.document; 17 | 18 | if (document) { 19 | let fileName = path.basename(document.fileName); 20 | let fileExt = path.extname(fileName); 21 | if (fileExt === '.js' || fileExt === '.py' || fileExt === '.jy') { 22 | // Get the document text 23 | const script = document.getText(); 24 | if (script && script.trim().length > 0) { 25 | await window.withProgress( 26 | { 27 | cancellable: false, 28 | title: 'Script', 29 | location: ProgressLocation.Notification, 30 | }, 31 | async (progress) => { 32 | progress.report({ 33 | message: 'Getting script from the server.', 34 | increment: 0, 35 | }); 36 | 37 | await new Promise((resolve) => 38 | setTimeout(resolve, 500) 39 | ); 40 | let result = await client.getScriptSource( 41 | script, 42 | progress, 43 | fileName 44 | ); 45 | 46 | if (result) { 47 | if (result.status === 'error') { 48 | if (result.message) { 49 | window.showErrorMessage( 50 | result.message, 51 | { modal: true } 52 | ); 53 | } else if (result.cause) { 54 | window.showErrorMessage( 55 | `Error: ${JSON.stringify( 56 | result.cause 57 | )}`, 58 | { modal: true } 59 | ); 60 | } else { 61 | window.showErrorMessage( 62 | 'An unknown error occurred: ' + 63 | JSON.stringify(result), 64 | { modal: true } 65 | ); 66 | } 67 | } else { 68 | if (result.source) { 69 | progress.report({ 70 | increment: 100, 71 | message: 72 | 'Successfully got script from the server.', 73 | }); 74 | await new Promise((resolve) => 75 | setTimeout(resolve, 2000) 76 | ); 77 | let localScript = document.uri; 78 | let serverScript = Uri.parse( 79 | 'vscode-autoscript-deploy:' + 80 | fileName 81 | ); 82 | 83 | fetchedSource[serverScript.path] = 84 | result.source; 85 | 86 | commands.executeCommand( 87 | 'vscode.diff', 88 | localScript, 89 | serverScript, 90 | '↔ server ' + fileName 91 | ); 92 | } else { 93 | window.showErrorMessage( 94 | `The ${fileName} was not found.\n\nCheck that the scriptConfig.autoscript value matches a script on the server.`, 95 | { modal: true } 96 | ); 97 | } 98 | } 99 | } else { 100 | window.showErrorMessage( 101 | 'Did not receive a response from Maximo.', 102 | { modal: true } 103 | ); 104 | } 105 | return result; 106 | } 107 | ); 108 | } else { 109 | window.showErrorMessage( 110 | 'The selected Automation Script cannot be empty.', 111 | { modal: true } 112 | ); 113 | } 114 | } else if (fileExt === '.xml') { 115 | // Get the document text 116 | const screen = document.getText(); 117 | let screenName; 118 | if (screen && screen.trim().length > 0) { 119 | parseString(screen, function (error, result) { 120 | if (error) { 121 | window.showErrorMessage(error.message, { 122 | modal: true, 123 | }); 124 | return; 125 | } else { 126 | if (result.presentation) { 127 | screenName = result.presentation.$.id; 128 | } else if (result.systemlib) { 129 | screenName = result.systemlib.$.id; 130 | } else { 131 | window.showErrorMessage( 132 | 'Current XML document does not have an root element of "presentation" or "systemlib".', 133 | { 134 | modal: true, 135 | } 136 | ); 137 | return; 138 | } 139 | } 140 | }); 141 | 142 | if (!screenName) { 143 | window.showErrorMessage( 144 | 'Unable to find presentation or systemlib id from current document. Cannot fetch screen from server to compare.', 145 | { modal: true } 146 | ); 147 | return; 148 | } 149 | 150 | await window.withProgress( 151 | { 152 | cancellable: false, 153 | title: 'Screen', 154 | location: ProgressLocation.Notification, 155 | }, 156 | async (progress) => { 157 | progress.report({ 158 | message: 'Getting screen from the server.', 159 | increment: 0, 160 | }); 161 | 162 | await new Promise((resolve) => 163 | setTimeout(resolve, 500) 164 | ); 165 | let result = await client.getScreen( 166 | screenName, 167 | progress, 168 | fileName 169 | ); 170 | 171 | if (result) { 172 | if (result.status === 'error') { 173 | if (result.message) { 174 | window.showErrorMessage( 175 | result.message, 176 | { modal: true } 177 | ); 178 | } else if (result.cause) { 179 | window.showErrorMessage( 180 | `Error: ${JSON.stringify( 181 | result.cause 182 | )}`, 183 | { modal: true } 184 | ); 185 | } else { 186 | window.showErrorMessage( 187 | 'An unknown error occurred: ' + 188 | JSON.stringify(result), 189 | { modal: true } 190 | ); 191 | } 192 | } else { 193 | if (result.presentation) { 194 | progress.report({ 195 | increment: 100, 196 | message: 197 | 'Successfully got screen from the server.', 198 | }); 199 | await new Promise((resolve) => 200 | setTimeout(resolve, 2000) 201 | ); 202 | let localScreen = document.uri; 203 | let serverScreen = Uri.parse( 204 | 'vscode-autoscript-deploy:' + 205 | fileName 206 | ); 207 | 208 | fetchedSource[serverScreen.path] = 209 | format(result.presentation); 210 | 211 | commands.executeCommand( 212 | 'vscode.diff', 213 | localScreen, 214 | serverScreen, 215 | '↔ server ' + fileName 216 | ); 217 | } else { 218 | window.showErrorMessage( 219 | `The ${fileName} was not found.\n\nCheck that the presentation id attribute value matches a Screen Definition on the server.`, 220 | { modal: true } 221 | ); 222 | } 223 | } 224 | } else { 225 | window.showErrorMessage( 226 | 'Did not receive a response from Maximo.', 227 | { modal: true } 228 | ); 229 | } 230 | return result; 231 | } 232 | ); 233 | } else { 234 | window.showErrorMessage( 235 | 'The selected Screen Definition cannot be empty.', 236 | { modal: true } 237 | ); 238 | } 239 | } else { 240 | window.showErrorMessage( 241 | "The selected file must have a Javascript ('.js'), Python ('.py') or Jython ('.jy') file extension for an automation script or ('.xml') for a Screen Definition.", 242 | { modal: true } 243 | ); 244 | } 245 | } else { 246 | window.showErrorMessage( 247 | 'An Automation Script or Screen Definition must be selected to compare.', 248 | { modal: true } 249 | ); 250 | } 251 | } else { 252 | window.showErrorMessage( 253 | 'An Automation Script or Screen Definition must be selected to compare.', 254 | { modal: true } 255 | ); 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /src/commands/deploy-command.js: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as fs from 'fs'; 3 | import { window } from 'vscode'; 4 | 5 | import deployScript from './deploy-script-command'; 6 | import deployScreen from './deploy-screen-command'; 7 | import deployForm from './deploy-form-command'; 8 | import deployReport from './deploy-report-command'; 9 | 10 | export default async function deployCommand(client) { 11 | // Get the active text editor 12 | const editor = window.activeTextEditor; 13 | 14 | if (editor) { 15 | let document = editor.document; 16 | 17 | if (document) { 18 | let filePath = document.fileName; 19 | let fileExt = path.extname(filePath); 20 | 21 | if (fileExt === '.js' || fileExt === '.py' || fileExt === '.jy') { 22 | await deployScript(client, filePath, document.getText()); 23 | } else if (fileExt === '.xml') { 24 | await deployScreen(client, filePath, document.getText()); 25 | } else if (fileExt === '.json') { 26 | try { 27 | let manifest = JSON.parse(document.getText()); 28 | if ( 29 | manifest && 30 | Object.prototype.hasOwnProperty.call( 31 | manifest, 32 | 'manifest' 33 | ) && 34 | Array.isArray(manifest.manifest) 35 | ) { 36 | const directory = path.dirname(filePath); 37 | for (const item of manifest.manifest) { 38 | let itemValue = item; 39 | if ( 40 | typeof item === 'object' && 41 | Object.prototype.hasOwnProperty.call( 42 | item, 43 | 'path' 44 | ) 45 | ) { 46 | itemValue = item.path; 47 | } 48 | 49 | if (typeof itemValue === 'string') { 50 | let itemPath = fs.existsSync(itemValue) 51 | ? itemValue 52 | : path.join(directory, itemValue); 53 | if (fs.existsSync(itemPath)) { 54 | let content = fs.readFileSync( 55 | itemPath, 56 | 'utf8' 57 | ); 58 | let itemExt = path.extname(itemPath); 59 | if ( 60 | itemExt === '.js' || 61 | itemExt === '.py' || 62 | itemExt === '.jy' 63 | ) { 64 | await deployScript( 65 | client, 66 | itemPath, 67 | content 68 | ); 69 | } else if (itemExt === '.xml') { 70 | await deployScreen( 71 | client, 72 | itemPath, 73 | content 74 | ); 75 | } else if (itemExt === '.json') { 76 | await deployForm( 77 | client, 78 | itemPath, 79 | content 80 | ); 81 | } else if (itemExt === '.rptdesign') { 82 | await deployReport( 83 | client, 84 | itemPath, 85 | content 86 | ); 87 | } 88 | } 89 | } 90 | } 91 | } else { 92 | await deployForm(client, filePath, document.getText()); 93 | } 94 | } catch (error) { 95 | window.showErrorMessage('Unexpected Error: ' + error); 96 | return; 97 | } 98 | } else if (fileExt === '.rptdesign') { 99 | await deployReport(client, filePath, document.getText()); 100 | } else { 101 | window.showErrorMessage( 102 | // eslint-disable-next-line quotes 103 | "The selected file must have a Javascript ('.js') or Python ('.py') file extension for an automation script, ('.xml') for a screen definition, ('.rptdesign') for a BIRT report or ('.json') for an inspection form.", 104 | { modal: true } 105 | ); 106 | } 107 | } else { 108 | window.showErrorMessage( 109 | 'An automation script, screen definition, BIRT report or inspection form must be selected to deploy.', 110 | { modal: true } 111 | ); 112 | } 113 | } else { 114 | window.showErrorMessage( 115 | 'An automation script, screen definition, BIRT report or inspection form must be selected to deploy.', 116 | { modal: true } 117 | ); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/commands/deploy-form-command.js: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import * as path from 'path'; 3 | import * as fs from 'fs'; 4 | 5 | import { window, ProgressLocation } from 'vscode'; 6 | 7 | import MaximoClient from '../maximo/maximo-client'; 8 | 9 | export default async function deployForm(client, filePath, form) { 10 | if ( 11 | !client || 12 | client === null || 13 | client instanceof MaximoClient === false 14 | ) { 15 | throw new Error( 16 | 'The client parameter is required and must be an instance of the MaximoClient class.' 17 | ); 18 | } 19 | 20 | if (!filePath || filePath === null || !fs.existsSync(filePath)) { 21 | throw new Error( 22 | 'The filePath parameter is required and must be a valid file path to a inspection form file.' 23 | ); 24 | } 25 | 26 | if (!form || form === null || form.trim().length === 0) { 27 | form = fs.readFileSync(filePath, { encoding: 'utf8' }); 28 | } 29 | 30 | if (!form || form.trim().length <= 0) { 31 | window.showErrorMessage( 32 | 'The selected inspection form cannot be empty.', 33 | { modal: true } 34 | ); 35 | return; 36 | } 37 | 38 | let fileName = path.basename(filePath); 39 | 40 | try { 41 | let formObject = JSON.parse(form); 42 | 43 | if (typeof formObject.name === 'undefined' || !formObject.name) { 44 | window.showErrorMessage( 45 | `File ${fileName} does not have a 'name' attribute and is not a valid inspection form extract.`, 46 | { 47 | modal: true, 48 | } 49 | ); 50 | return; 51 | } 52 | 53 | await window.withProgress( 54 | { 55 | cancellable: false, 56 | title: 'Inspection Form', 57 | location: ProgressLocation.Notification, 58 | }, 59 | async (progress) => { 60 | progress.report({ 61 | message: `Inspection form ${formObject.name}`, 62 | increment: 0, 63 | }); 64 | 65 | await new Promise((resolve) => setTimeout(resolve, 500)); 66 | 67 | let result = await client.postForm(formObject, progress); 68 | 69 | if (result) { 70 | if (result.status === 'error') { 71 | if (result.message) { 72 | window.showErrorMessage(result.message, { 73 | modal: true, 74 | }); 75 | } else if (result.cause) { 76 | window.showErrorMessage( 77 | `Error: ${JSON.stringify(result.cause)}`, 78 | { modal: true } 79 | ); 80 | } else { 81 | window.showErrorMessage( 82 | 'An unknown error occurred: ' + 83 | JSON.stringify(result), 84 | { modal: true } 85 | ); 86 | } 87 | } else { 88 | progress.report({ 89 | increment: 100, 90 | message: `Successfully deployed ${formObject.name}`, 91 | }); 92 | await new Promise((resolve) => 93 | setTimeout(resolve, 2000) 94 | ); 95 | } 96 | } else { 97 | window.showErrorMessage( 98 | 'Did not receive a response from Maximo.', 99 | { modal: true } 100 | ); 101 | } 102 | return result; 103 | } 104 | ); 105 | } catch (error) { 106 | window.showErrorMessage( 107 | `File ${fileName} is not a valid JSON formatted inspection form extract.\n\n${error.message}`, 108 | { 109 | modal: true, 110 | } 111 | ); 112 | return; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/commands/deploy-report-command.js: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as fs from 'fs'; 3 | import * as archiver from 'archiver'; 4 | 5 | import { parseString } from 'xml2js'; 6 | 7 | import { PassThrough } from 'stream'; 8 | 9 | import { window, ProgressLocation } from 'vscode'; 10 | 11 | import MaximoClient from '../maximo/maximo-client'; 12 | 13 | export default async function deployReport(client, filePath, report) { 14 | if ( 15 | !client || 16 | client === null || 17 | client instanceof MaximoClient === false 18 | ) { 19 | throw new Error( 20 | 'The client parameter is required and must be an instance of the MaximoClient class.' 21 | ); 22 | } 23 | 24 | if (!filePath || filePath === null || !fs.existsSync(filePath)) { 25 | throw new Error( 26 | 'The filePath parameter is required and must be a valid file path to a report file.' 27 | ); 28 | } 29 | 30 | if (!report || report === null || report.trim().length === 0) { 31 | report = fs.readFileSync(filePath, { encoding: 'utf8' }); 32 | } 33 | 34 | if (!report || report.trim().length <= 0) { 35 | window.showErrorMessage('The selected report cannot be empty.', { 36 | modal: true, 37 | }); 38 | return; 39 | } 40 | 41 | let fileName = path.basename(filePath); 42 | let reportContent = fs.readFileSync(filePath, 'utf8'); 43 | 44 | let reportName = path.basename(fileName, path.extname(fileName)); 45 | 46 | let folderPath = path.dirname(filePath); 47 | 48 | // Get the name of the containing folder 49 | let appName = path.basename(folderPath); 50 | 51 | let reportsXML = folderPath + '/reports.xml'; 52 | 53 | if (!fs.existsSync(reportsXML)) { 54 | window.showErrorMessage( 55 | 'The selected report must have a reports.xml in the same folder that describes the report parameters.', 56 | { modal: true } 57 | ); 58 | return; 59 | } 60 | 61 | // Read the XML file 62 | let xmlContent = fs.readFileSync(reportsXML, 'utf8'); 63 | 64 | let reportConfigs = await new Promise((resolve, reject) => { 65 | parseString(xmlContent, function (error, result) { 66 | if (error) { 67 | reject(error); 68 | } else { 69 | resolve(result); 70 | } 71 | }); 72 | }); 73 | 74 | let reportConfig = reportConfigs.reports.report.filter( 75 | (report) => report.$.name === fileName 76 | )[0]; 77 | 78 | if ( 79 | typeof reportConfig === 'undefined' || 80 | reportConfig === null || 81 | reportConfig.attribute.length === 0 82 | ) { 83 | window.showErrorMessage( 84 | 'The selected report does not have an entry that contains at least one attribute value in the reports.xml.', 85 | { modal: true } 86 | ); 87 | return; 88 | } 89 | 90 | let resourceData = null; 91 | let resourceFolder = folderPath + '/' + reportName; 92 | if ( 93 | fs.existsSync(resourceFolder) && 94 | fs.readdirSync(resourceFolder).length > 0 95 | ) { 96 | resourceData = await createZipFromFolder(resourceFolder); 97 | } 98 | 99 | let attributes = reportConfig.attribute; 100 | 101 | let reportData = { 102 | reportName: reportConfig.$.name, 103 | description: 104 | attributes.find((attr) => attr.$.name === 'description')?._ ?? null, 105 | reportFolder: 106 | attributes.find((attr) => attr.$.name === 'reportfolder')?._ ?? 107 | null, 108 | appName: appName, 109 | toolbarLocation: 110 | attributes.find((attr) => attr.$.name === 'toolbarlocation')?._ ?? 111 | 'NONE', 112 | toolbarIcon: 113 | attributes.find((attr) => attr.$.name === 'toolbaricon')?._ ?? null, 114 | toolbarSequence: 115 | attributes.find((attr) => attr.$.name === 'toolbarsequence')?._ ?? 116 | null, 117 | noRequestPage: 118 | attributes.find((attr) => attr.$.name === 'norequestpage')?._ == 1 119 | ? true 120 | : false ?? false, 121 | detail: 122 | attributes.find((attr) => attr.$.name === 'detail')?._ == 1 123 | ? true 124 | : false ?? false, 125 | useWhereWithParam: 126 | attributes.find((attr) => attr.$.name === 'usewherewithparam')?._ == 127 | 1 128 | ? true 129 | : false ?? false, 130 | langCode: 131 | attributes.find((attr) => attr.$.name === 'langcode')?._ ?? null, 132 | recordLimit: 133 | attributes.find((attr) => attr.$.name === 'recordlimit')?._ ?? null, 134 | browserView: 135 | attributes.find((attr) => attr.$.name === 'ql')?._ == 1 136 | ? true 137 | : false ?? false, 138 | directPrint: 139 | attributes.find((attr) => attr.$.name === 'dp')?._ == 1 140 | ? true 141 | : false ?? false, 142 | printWithAttachments: 143 | attributes.find((attr) => attr.$.name === 'pad')?._ == 1 144 | ? true 145 | : false ?? false, 146 | browserViewLocation: 147 | attributes.find((attr) => attr.$.name === 'qlloc')?._ ?? 'NONE', 148 | directPrintLocation: 149 | attributes.find((attr) => attr.$.name === 'dploc')?._ ?? 'NONE', 150 | printWithAttachmentsLocation: 151 | attributes.find((attr) => attr.$.name === 'padloc')?._ ?? 'NONE', 152 | priority: 153 | attributes.find((attr) => attr.$.name === 'priority')?._ ?? null, 154 | scheduleOnly: 155 | attributes.find((attr) => attr.$.name === 'scheduleonly')?._ == 1 156 | ? true 157 | : false ?? false, 158 | displayOrder: 159 | attributes.find((attr) => attr.$.name === 'displayorder')?._ ?? 160 | null, 161 | paramColumns: 162 | attributes.find((attr) => attr.$.name === 'paramcolumns')?._ ?? 163 | null, 164 | design: reportContent, 165 | resources: resourceData, 166 | }; 167 | 168 | if ( 169 | typeof reportConfig.parameters !== 'undefined' && 170 | reportConfig.parameters.length == 1 && 171 | typeof reportConfig.parameters[0].parameter !== 'undefined' && 172 | reportConfig.parameters[0].parameter.length > 0 173 | ) { 174 | let parameters = reportConfig.parameters[0].parameter; 175 | reportData.parameters = []; 176 | parameters.forEach((parameter) => { 177 | let attributes = parameter.attribute; 178 | 179 | reportData.parameters.push({ 180 | parameterName: parameter.$.name, 181 | attributeName: 182 | attributes.find((attr) => attr.$.name === 'attributename') 183 | ?._ ?? null, 184 | defaultValue: 185 | attributes.find((attr) => attr.$.name === 'defaultvalue') 186 | ?._ ?? null, 187 | labelOverride: 188 | attributes.find((attr) => attr.$.name === 'labeloverride') 189 | ?._ ?? null, 190 | sequence: 191 | attributes.find((attr) => attr.$.name === 'sequence')?._ ?? 192 | null, 193 | lookupName: 194 | attributes.find((attr) => attr.$.name === 'lookupname') 195 | ?._ ?? null, 196 | required: 197 | attributes.find((attr) => attr.$.name === 'required')?._ == 198 | 1 199 | ? true 200 | : false ?? false, 201 | hidden: 202 | attributes.find((attr) => attr.$.name === 'hidden')?._ == 1 203 | ? true 204 | : false ?? false, 205 | multiLookup: 206 | attributes.find((attr) => attr.$.name === 'multilookup') 207 | ?._ == 1 208 | ? true 209 | : false ?? false, 210 | operator: 211 | attributes.find((attr) => attr.$.name === 'operator')?._ ?? 212 | null, 213 | }); 214 | }); 215 | } 216 | 217 | await window.withProgress( 218 | { 219 | cancellable: false, 220 | title: 'Report', 221 | location: ProgressLocation.Notification, 222 | }, 223 | async (progress) => { 224 | progress.report({ 225 | message: `Deploying report ${fileName}`, 226 | increment: 0, 227 | }); 228 | 229 | await new Promise((resolve) => setTimeout(resolve, 500)); 230 | let result = await client.postReport( 231 | reportData, 232 | progress, 233 | fileName 234 | ); 235 | 236 | if (result) { 237 | if (result.status === 'error') { 238 | if (result.message) { 239 | window.showErrorMessage(result.message, { 240 | modal: true, 241 | }); 242 | } else if (result.cause) { 243 | window.showErrorMessage( 244 | `Error: ${JSON.stringify(result.cause)}`, 245 | { modal: true } 246 | ); 247 | } else { 248 | window.showErrorMessage( 249 | 'An unknown error occurred: ' + 250 | JSON.stringify(result), 251 | { modal: true } 252 | ); 253 | } 254 | } else { 255 | progress.report({ 256 | increment: 100, 257 | message: `Successfully deployed ${fileName}`, 258 | }); 259 | await new Promise((resolve) => setTimeout(resolve, 2000)); 260 | } 261 | } else { 262 | window.showErrorMessage( 263 | 'Did not receive a response from Maximo.', 264 | { modal: true } 265 | ); 266 | } 267 | return result; 268 | } 269 | ); 270 | } 271 | 272 | async function createZipFromFolder(folderPath) { 273 | let result = await new Promise((resolve, reject) => { 274 | const archive = archiver('zip', { 275 | zlib: { level: 9 }, // Sets the compression level. 276 | }); 277 | 278 | const bufferStream = new PassThrough(); 279 | let chunks = []; 280 | 281 | bufferStream.on('data', (chunk) => { 282 | chunks.push(chunk); 283 | }); 284 | 285 | // Good practice to catch warnings (like stat failures and other non-blocking errors) 286 | archive.on('warning', function (err) { 287 | if (err.code === 'ENOENT') { 288 | console.warn(err); 289 | } else { 290 | // Throw error for any unexpected warning 291 | reject(err); 292 | } 293 | }); 294 | 295 | // Catch errors explicitly 296 | archive.on('error', function (err) { 297 | reject(err); 298 | }); 299 | 300 | archive.on('finish', function () { 301 | const fullBuffer = Buffer.concat(chunks); 302 | const base64String = fullBuffer.toString('base64'); 303 | resolve(base64String); 304 | }); 305 | 306 | // Pipe archive data to the file 307 | archive.pipe(bufferStream); 308 | 309 | // // Append files from a directory 310 | archive.directory(folderPath, false); 311 | 312 | // Finalize the archive (ie we are done appending files but streams have to finish yet) 313 | // 'close', 'end' or 'finish' may be fired right after calling this method so register to them beforehand 314 | archive.finalize(); 315 | }); 316 | 317 | return result; 318 | } 319 | -------------------------------------------------------------------------------- /src/commands/deploy-screen-command.js: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as fs from 'fs'; 3 | import { parseString } from 'xml2js'; 4 | 5 | import { window, ProgressLocation } from 'vscode'; 6 | 7 | import MaximoClient from '../maximo/maximo-client'; 8 | 9 | export default async function deployScreen(client, filePath, screen) { 10 | if ( 11 | !client || 12 | client === null || 13 | client instanceof MaximoClient === false 14 | ) { 15 | throw new Error( 16 | 'The client parameter is required and must be an instance of the MaximoClient class.' 17 | ); 18 | } 19 | 20 | if (!filePath || filePath === null || !fs.existsSync(filePath)) { 21 | throw new Error( 22 | 'The filePath parameter is required and must be a valid file path to a screen file.' 23 | ); 24 | } 25 | 26 | if (!screen || screen === null || screen.trim().length === 0) { 27 | screen = fs.readFileSync(filePath, { encoding: 'utf8' }); 28 | } 29 | 30 | if (!screen || screen.trim().length <= 0) { 31 | window.showErrorMessage( 32 | 'The selected screen definition cannot be empty.', 33 | { modal: true } 34 | ); 35 | return; 36 | } 37 | 38 | let fileName = path.basename(filePath); 39 | var screenName; 40 | var parseError; 41 | 42 | parseString(screen, function (error, result) { 43 | parseError = error; 44 | if (error) { 45 | return; 46 | } else { 47 | if (result.presentation) { 48 | screenName = result.presentation.$.id; 49 | } else if (result.systemlib) { 50 | screenName = result.systemlib.$.id; 51 | } else { 52 | parseError = { 53 | message: 54 | 'Current XML document does not have an root element of "presentation" or "systemlib".', 55 | }; 56 | } 57 | } 58 | }); 59 | 60 | if (parseError) { 61 | // @ts-ignore 62 | window.showErrorMessage( 63 | `Error parsing ${fileName}: ${parseError.message}`, 64 | { modal: true } 65 | ); 66 | return; 67 | } 68 | 69 | if (!screenName) { 70 | window.showErrorMessage( 71 | 'Unable to find presentation or systemlib id from current document. Cannot fetch screen from server to compare.', 72 | { 73 | modal: true, 74 | } 75 | ); 76 | return; 77 | } 78 | 79 | await window.withProgress( 80 | { 81 | cancellable: false, 82 | title: 'Screen', 83 | location: ProgressLocation.Notification, 84 | }, 85 | async (progress) => { 86 | progress.report({ 87 | message: `Deploying screen ${fileName}`, 88 | increment: 0, 89 | }); 90 | 91 | await new Promise((resolve) => setTimeout(resolve, 500)); 92 | let result = await client.postScreen(screen, progress, fileName); 93 | 94 | if (result) { 95 | if (result.status === 'error') { 96 | if (result.message) { 97 | window.showErrorMessage(result.message, { 98 | modal: true, 99 | }); 100 | } else if (result.cause) { 101 | window.showErrorMessage( 102 | `Error: ${JSON.stringify(result.cause)}`, 103 | { modal: true } 104 | ); 105 | } else { 106 | window.showErrorMessage( 107 | 'An unknown error occurred: ' + 108 | JSON.stringify(result), 109 | { modal: true } 110 | ); 111 | } 112 | } else { 113 | progress.report({ 114 | increment: 100, 115 | message: `Successfully deployed ${fileName}`, 116 | }); 117 | await new Promise((resolve) => setTimeout(resolve, 2000)); 118 | } 119 | } else { 120 | window.showErrorMessage( 121 | 'Did not receive a response from Maximo.', 122 | { modal: true } 123 | ); 124 | } 125 | return result; 126 | } 127 | ); 128 | } 129 | -------------------------------------------------------------------------------- /src/commands/extract-form-command.js: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as crypto from 'crypto'; 3 | 4 | import { ProgressLocation, window, workspace } from 'vscode'; 5 | 6 | // get a reference to the Maximo configuration object 7 | import { getMaximoConfig } from '../extension'; 8 | 9 | export default async function extractFormCommand(client) { 10 | let extractLoc = (await getMaximoConfig()).extractLocationForms; 11 | // if the extract location has not been specified use the workspace folder. 12 | if (typeof extractLoc === 'undefined' || !extractLoc) { 13 | if (workspace.workspaceFolders !== undefined) { 14 | extractLoc = workspace.workspaceFolders[0].uri.fsPath; 15 | } else { 16 | window.showErrorMessage( 17 | 'A working folder must be selected or an export folder configured before exporting inspection forms.', 18 | { 19 | modal: true, 20 | } 21 | ); 22 | return; 23 | } 24 | } 25 | 26 | if (!fs.existsSync(extractLoc)) { 27 | window.showErrorMessage( 28 | `The inspection form extract folder ${extractLoc} does not exist.`, 29 | { modal: true } 30 | ); 31 | return; 32 | } 33 | 34 | let formFullNames = await window.withProgress( 35 | { 36 | title: 'Getting inspection forms', 37 | location: ProgressLocation.Notification, 38 | }, 39 | async () => { 40 | return await client.getAllForms(); 41 | } 42 | ); 43 | 44 | if (typeof formFullNames !== 'undefined' && formFullNames.length > 0) { 45 | var formNames = formFullNames.map((x) => x.name); 46 | formNames.sort(); 47 | await window 48 | .showQuickPick(formNames, { placeHolder: 'Search for form' }) 49 | .then(async (formName) => { 50 | if (typeof formName !== 'undefined') { 51 | var form = formFullNames.find((x) => x.name == formName); 52 | 53 | await window.withProgress( 54 | { 55 | title: `Extracting Inspection Form ${formName}`, 56 | location: ProgressLocation.Notification, 57 | cancellable: true, 58 | }, 59 | async (progress, cancelToken) => { 60 | let formInfo = await client.getForm(form.id); 61 | 62 | let fileExtension = '.json'; 63 | 64 | let outputFile = 65 | extractLoc + 66 | '/' + 67 | formInfo.name 68 | .toLowerCase() 69 | .replaceAll(' ', '-') 70 | .replaceAll('/', '-') 71 | .replaceAll('\\', '-') + 72 | fileExtension; 73 | let source = JSON.stringify(formInfo, null, 4); 74 | // if the file doesn't exist then just write it out. 75 | if (!fs.existsSync(outputFile)) { 76 | fs.writeFileSync(outputFile, source); 77 | } else { 78 | let incomingHash = crypto 79 | .createHash('sha256') 80 | .update(source) 81 | .digest('hex'); 82 | let fileHash = crypto 83 | .createHash('sha256') 84 | .update(fs.readFileSync(outputFile)) 85 | .digest('hex'); 86 | 87 | if (fileHash !== incomingHash) { 88 | await window 89 | .showInformationMessage( 90 | `The inspection form ${form.name} exists. \nReplace?`, 91 | { modal: true }, 92 | ...['Replace'] 93 | ) 94 | .then(async (response) => { 95 | if (response === 'Replace') { 96 | fs.writeFileSync( 97 | outputFile, 98 | source 99 | ); 100 | } else { 101 | // @ts-ignore 102 | cancelToken.cancel(); 103 | } 104 | }); 105 | } 106 | } 107 | 108 | if (cancelToken.isCancellationRequested) { 109 | return; 110 | } else { 111 | window.showInformationMessage( 112 | `Inspection form "${formName}" extracted.`, 113 | { modal: true } 114 | ); 115 | } 116 | } 117 | ); 118 | } 119 | }); 120 | } else { 121 | window.showErrorMessage('No inspection forms were found to extract.', { 122 | modal: true, 123 | }); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/commands/extract-forms-command.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable indent */ 2 | import * as fs from 'fs'; 3 | import * as crypto from 'crypto'; 4 | 5 | import { ProgressLocation, window, workspace } from 'vscode'; 6 | 7 | // get a reference to the Maximo configuration object 8 | import { getMaximoConfig, asyncForEach } from '../extension'; 9 | 10 | export default async function extractFormsCommand(client) { 11 | let extractLoc = (await getMaximoConfig()).extractLocationForms; 12 | // if the extract location has not been specified use the workspace folder. 13 | if (typeof extractLoc === 'undefined' || !extractLoc) { 14 | if (workspace.workspaceFolders !== undefined) { 15 | extractLoc = workspace.workspaceFolders[0].uri.fsPath; 16 | } else { 17 | window.showErrorMessage( 18 | 'A working folder must be selected or an export folder configured before exporting inspection forms.', 19 | { 20 | modal: true, 21 | } 22 | ); 23 | return; 24 | } 25 | } 26 | 27 | if (!fs.existsSync(extractLoc)) { 28 | window.showErrorMessage( 29 | `The inspection form extract folder ${extractLoc} does not exist.`, 30 | { modal: true } 31 | ); 32 | return; 33 | } 34 | 35 | let formNames = await window.withProgress( 36 | { 37 | title: 'Getting inspection forms', 38 | location: ProgressLocation.Notification, 39 | }, 40 | async () => { 41 | return await client.getAllForms(); 42 | } 43 | ); 44 | 45 | if (typeof formNames !== 'undefined' && formNames.length > 0) { 46 | await window 47 | .showInformationMessage( 48 | 'Do you want to extract ' + 49 | (formNames.length > 1 50 | ? 'the ' + formNames.length + ' inspection forms?' 51 | : ' the one inspection form?'), 52 | { modal: true }, 53 | ...['Yes'] 54 | ) 55 | .then(async (response) => { 56 | if (response === 'Yes') { 57 | await window.withProgress( 58 | { 59 | title: 'Extracting Inspection Forms', 60 | location: ProgressLocation.Notification, 61 | cancellable: true, 62 | }, 63 | async (progress, cancelToken) => { 64 | let percent = Math.round( 65 | (1 / formNames.length) * 100 66 | ); 67 | 68 | let overwriteAll = false; 69 | let overwrite = false; 70 | 71 | await asyncForEach(formNames, async (form) => { 72 | if (!cancelToken.isCancellationRequested) { 73 | progress.report({ 74 | increment: percent, 75 | message: `Extracting ${form.name.toLowerCase()} (${ 76 | form.inspformnum 77 | })`, 78 | }); 79 | let formInfo = await client.getForm( 80 | form.id 81 | ); 82 | 83 | let fileExtension = '.json'; 84 | 85 | let outputFile = 86 | extractLoc + 87 | '/' + 88 | formInfo.name 89 | .toLowerCase() 90 | .replaceAll(' ', '-') 91 | .replaceAll('/', '-') 92 | .replaceAll('\\', '-') + 93 | fileExtension; 94 | let source = JSON.stringify( 95 | formInfo, 96 | null, 97 | 4 98 | ); 99 | // if the file doesn't exist then just write it out. 100 | if (!fs.existsSync(outputFile)) { 101 | fs.writeFileSync(outputFile, source); 102 | } else { 103 | let incomingHash = crypto 104 | .createHash('sha256') 105 | .update(source) 106 | .digest('hex'); 107 | let fileHash = crypto 108 | .createHash('sha256') 109 | .update(fs.readFileSync(outputFile)) 110 | .digest('hex'); 111 | 112 | if (fileHash !== incomingHash) { 113 | if (!overwriteAll) { 114 | await window 115 | .showInformationMessage( 116 | `The inspection form ${form.name} exists. \nReplace?`, 117 | { modal: true }, 118 | ...[ 119 | 'Replace', 120 | 'Replace All', 121 | 'Skip', 122 | ] 123 | ) 124 | .then(async (response) => { 125 | if ( 126 | response === 127 | 'Replace' 128 | ) { 129 | overwrite = true; 130 | } else if ( 131 | response === 132 | 'Replace All' 133 | ) { 134 | overwriteAll = true; 135 | } else if ( 136 | response === 'Skip' 137 | ) { 138 | // do nothing 139 | overwrite = false; 140 | } else { 141 | // @ts-ignore 142 | cancelToken.cancel(); 143 | } 144 | }); 145 | } 146 | if (overwriteAll || overwrite) { 147 | fs.writeFileSync( 148 | outputFile, 149 | source 150 | ); 151 | overwrite = false; 152 | } 153 | } 154 | } 155 | 156 | if (cancelToken.isCancellationRequested) { 157 | return; 158 | } 159 | } 160 | }); 161 | 162 | if (!cancelToken.isCancellationRequested) { 163 | window.showInformationMessage( 164 | 'Inspection forms extracted.', 165 | { modal: true } 166 | ); 167 | } 168 | } 169 | ); 170 | } 171 | }); 172 | } else { 173 | window.showErrorMessage('No inspection forms were found to extract.', { 174 | modal: true, 175 | }); 176 | } 177 | } 178 | 179 | function getExtension(scriptLanguage) { 180 | switch (scriptLanguage.toLowerCase()) { 181 | case 'python': 182 | case 'jython': 183 | return '.py'; 184 | case 'nashorn': 185 | case 'javascript': 186 | case 'emcascript': 187 | case 'js': 188 | return '.js'; 189 | default: 190 | return '.unknown'; 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/commands/extract-reports-command.js: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as crypto from 'crypto'; 3 | 4 | import { ProgressLocation, window, workspace } from 'vscode'; 5 | import { writeResources, writeMetaData } from './extract-report-command'; 6 | // get a reference to the Maximo configuration object 7 | import { getMaximoConfig, asyncForEach } from '../extension'; 8 | 9 | export default async function extractReportsCommand(client) { 10 | let extractLoc = (await getMaximoConfig()).extractLocationReports; 11 | // if the extract location has not been specified use the workspace folder. 12 | if (typeof extractLoc === 'undefined' || !extractLoc) { 13 | if (workspace.workspaceFolders !== undefined) { 14 | extractLoc = workspace.workspaceFolders[0].uri.fsPath; 15 | } else { 16 | window.showErrorMessage( 17 | 'A working folder must be selected or an export folder configured before exporting reports.', 18 | { 19 | modal: true, 20 | } 21 | ); 22 | return; 23 | } 24 | } 25 | 26 | if (!fs.existsSync(extractLoc)) { 27 | window.showErrorMessage( 28 | `The reports extract folder ${extractLoc} does not exist.`, 29 | { modal: true } 30 | ); 31 | return; 32 | } 33 | 34 | let reportNames = await window.withProgress( 35 | { 36 | title: 'Getting report names', 37 | location: ProgressLocation.Notification, 38 | }, 39 | async () => { 40 | return await client.getAllReports(); 41 | } 42 | ); 43 | 44 | if (typeof reportNames !== 'undefined' && reportNames.length > 0) { 45 | let mappedReportNames = reportNames.map((report) => { 46 | return report.description + ' (' + report.report + ')'; 47 | }); 48 | 49 | await window 50 | .showInformationMessage( 51 | 'Do you want to extract ' + 52 | (reportNames.length > 1 53 | ? 'the ' + reportNames.length + ' reports?' 54 | : ' the one report?'), 55 | { modal: true }, 56 | ...['Yes'] 57 | ) 58 | .then(async (response) => { 59 | if (response === 'Yes') { 60 | await window.withProgress( 61 | { 62 | title: 'Extracting Reports', 63 | location: ProgressLocation.Notification, 64 | cancellable: true, 65 | }, 66 | async (progress, cancelToken) => { 67 | let percent = (1 / reportNames.length) * 100; 68 | 69 | let overwriteAll = false; 70 | let overwrite = false; 71 | let ignoreMissingDesign = false; 72 | await asyncForEach( 73 | mappedReportNames, 74 | async (reportName) => { 75 | if (!cancelToken.isCancellationRequested) { 76 | progress.report({ 77 | increment: percent, 78 | message: `Extracting ${reportName}`, 79 | }); 80 | var report = reportNames.find( 81 | (x) => 82 | x.description + 83 | ' (' + 84 | x.report + 85 | ')' == 86 | reportName 87 | ); 88 | let reportInfo; 89 | try { 90 | reportInfo = await client.getReport( 91 | report.reportId 92 | ); 93 | } catch (e) { 94 | if ( 95 | e.message && 96 | e.message.includes('BMXAA5478E') 97 | ) { 98 | if (!ignoreMissingDesign) { 99 | let response = 100 | await window.showInformationMessage( 101 | `The report design for ${reportName} is not present in Maximo.\n\nContinue extract reports?`, 102 | { modal: true }, 103 | ...[ 104 | 'Yes', 105 | 'Yes to All', 106 | ] 107 | ); 108 | if ( 109 | response === 110 | 'Yes to All' 111 | ) { 112 | ignoreMissingDesign = true; 113 | } else if ( 114 | response !== 'Yes' 115 | ) { 116 | // @ts-ignore 117 | cancelToken.cancel(); 118 | } 119 | } 120 | } else { 121 | throw e; 122 | } 123 | } 124 | if (reportInfo) { 125 | let outputFile = 126 | extractLoc + 127 | '/' + 128 | reportInfo.reportFolder + 129 | '/' + 130 | report.report; 131 | if (reportInfo.design) { 132 | let xml = reportInfo.design; 133 | 134 | // if the file doesn't exist then just write it out. 135 | if ( 136 | !fs.existsSync(outputFile) 137 | ) { 138 | fs.mkdirSync( 139 | extractLoc + 140 | '/' + 141 | reportInfo.reportFolder, 142 | { recursive: true } 143 | ); 144 | fs.writeFileSync( 145 | outputFile, 146 | xml 147 | ); 148 | } else { 149 | let incomingHash = crypto 150 | .createHash('sha256') 151 | .update(xml) 152 | .digest('hex'); 153 | let fileHash = crypto 154 | .createHash('sha256') 155 | .update( 156 | fs.readFileSync( 157 | outputFile 158 | ) 159 | ) 160 | .digest('hex'); 161 | 162 | if ( 163 | fileHash !== 164 | incomingHash 165 | ) { 166 | if (!overwriteAll) { 167 | await window 168 | .showInformationMessage( 169 | `The report ${reportName} exists. \nReplace?`, 170 | { 171 | modal: true, 172 | }, 173 | ...[ 174 | 'Replace', 175 | 'Replace All', 176 | 'Skip', 177 | ] 178 | ) 179 | .then( 180 | async ( 181 | response 182 | ) => { 183 | if ( 184 | response === 185 | 'Replace' 186 | ) { 187 | overwrite = true; 188 | } else if ( 189 | response === 190 | 'Replace All' 191 | ) { 192 | overwriteAll = true; 193 | } else if ( 194 | response === 195 | 'Skip' 196 | ) { 197 | // do nothing 198 | overwrite = false; 199 | } else { 200 | // @ts-ignore 201 | cancelToken.cancel(); 202 | } 203 | } 204 | ); 205 | } 206 | if ( 207 | overwriteAll || 208 | overwrite 209 | ) { 210 | fs.writeFileSync( 211 | outputFile, 212 | xml 213 | ); 214 | overwrite = false; 215 | } 216 | } 217 | await writeResources( 218 | reportInfo, 219 | extractLoc 220 | ); 221 | await writeMetaData( 222 | reportInfo, 223 | extractLoc 224 | ); 225 | } 226 | 227 | if ( 228 | cancelToken.isCancellationRequested 229 | ) { 230 | return; 231 | } 232 | } 233 | } 234 | } 235 | } 236 | ); 237 | 238 | if (!cancelToken.isCancellationRequested) { 239 | window.showInformationMessage( 240 | 'Reports extracted.', 241 | { modal: true } 242 | ); 243 | } 244 | } 245 | ); 246 | } 247 | }); 248 | } else { 249 | window.showErrorMessage('No reports were found to extract.', { 250 | modal: true, 251 | }); 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /src/commands/extract-screen-command.js: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as crypto from 'crypto'; 3 | // @ts-ignore 4 | import * as format from 'xml-formatter'; 5 | 6 | import { ProgressLocation, window, workspace } from 'vscode'; 7 | 8 | // get a reference to the Maximo configuration object 9 | import { getMaximoConfig } from '../extension'; 10 | 11 | export default async function extractScreenCommand(client) { 12 | let extractLoc = (await getMaximoConfig()).extractLocationScreens; 13 | // if the extract location has not been specified use the workspace folder. 14 | if (typeof extractLoc === 'undefined' || !extractLoc) { 15 | if (workspace.workspaceFolders !== undefined) { 16 | extractLoc = workspace.workspaceFolders[0].uri.fsPath; 17 | } else { 18 | window.showErrorMessage( 19 | 'A working folder must be selected or an export folder configured before exporting screen definitions.', 20 | { 21 | modal: true, 22 | } 23 | ); 24 | return; 25 | } 26 | } 27 | 28 | if (!fs.existsSync(extractLoc)) { 29 | window.showErrorMessage( 30 | `The screen extract folder ${extractLoc} does not exist.`, 31 | { modal: true } 32 | ); 33 | return; 34 | } 35 | 36 | let screenNames = await window.withProgress( 37 | { 38 | title: 'Getting screen names', 39 | location: ProgressLocation.Notification, 40 | }, 41 | async () => { 42 | return await client.getAllScreenNames(); 43 | } 44 | ); 45 | 46 | if (typeof screenNames !== 'undefined' && screenNames.length > 0) { 47 | screenNames.sort(); 48 | await window 49 | .showQuickPick(screenNames, { placeHolder: 'Search for screen' }) 50 | .then(async (screenName) => { 51 | if (typeof screenName !== 'undefined') { 52 | await window.withProgress( 53 | { 54 | title: `Extracting Screen ${screenName}`, 55 | location: ProgressLocation.Notification, 56 | cancellable: true, 57 | }, 58 | async (progress, cancelToken) => { 59 | let screenInfo = await client.getScreen(screenName); 60 | 61 | let fileExtension = '.xml'; 62 | 63 | let outputFile = 64 | extractLoc + 65 | '/' + 66 | screenName.toLowerCase() + 67 | fileExtension; 68 | let xml = format(screenInfo.presentation); 69 | // if the file doesn't exist then just write it out. 70 | if (!fs.existsSync(outputFile)) { 71 | fs.writeFileSync(outputFile, xml); 72 | } else { 73 | let incomingHash = crypto 74 | .createHash('sha256') 75 | .update(xml) 76 | .digest('hex'); 77 | let fileHash = crypto 78 | .createHash('sha256') 79 | .update(fs.readFileSync(outputFile)) 80 | .digest('hex'); 81 | 82 | if (fileHash !== incomingHash) { 83 | await window 84 | .showInformationMessage( 85 | `The screen ${screenName.toLowerCase()}${fileExtension} exists. \nReplace?`, 86 | { modal: true }, 87 | ...['Replace'] 88 | ) 89 | .then(async (response) => { 90 | if (response === 'Replace') { 91 | fs.writeFileSync( 92 | outputFile, 93 | xml 94 | ); 95 | } else { 96 | // @ts-ignore 97 | cancelToken.cancel(); 98 | } 99 | }); 100 | } 101 | } 102 | 103 | if (cancelToken.isCancellationRequested) { 104 | return; 105 | } else { 106 | window.showInformationMessage( 107 | `Screen "${screenName}" extracted.`, 108 | { modal: true } 109 | ); 110 | } 111 | } 112 | ); 113 | } 114 | }); 115 | } else { 116 | window.showErrorMessage( 117 | 'No screen definitions were found to extract.', 118 | { modal: true } 119 | ); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/commands/extract-screens-command.js: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as crypto from 'crypto'; 3 | // @ts-ignore 4 | import * as format from 'xml-formatter'; 5 | 6 | import { ProgressLocation, window, workspace } from 'vscode'; 7 | 8 | // get a reference to the Maximo configuration object 9 | import { getMaximoConfig, asyncForEach } from '../extension'; 10 | 11 | export default async function extractScreensCommand(client) { 12 | let extractLoc = (await getMaximoConfig()).extractLocationScreens; 13 | // if the extract location has not been specified use the workspace folder. 14 | if (typeof extractLoc === 'undefined' || !extractLoc) { 15 | if (workspace.workspaceFolders !== undefined) { 16 | extractLoc = workspace.workspaceFolders[0].uri.fsPath; 17 | } else { 18 | window.showErrorMessage( 19 | 'A working folder must be selected or an export folder configured before exporting screen definitions.', 20 | { 21 | modal: true, 22 | } 23 | ); 24 | return; 25 | } 26 | } 27 | 28 | if (!fs.existsSync(extractLoc)) { 29 | window.showErrorMessage( 30 | `The screen extract folder ${extractLoc} does not exist.`, 31 | { modal: true } 32 | ); 33 | return; 34 | } 35 | 36 | let screenNames = await window.withProgress( 37 | { 38 | title: 'Getting screen names', 39 | location: ProgressLocation.Notification, 40 | }, 41 | async () => { 42 | return await client.getAllScreenNames(); 43 | } 44 | ); 45 | 46 | if (typeof screenNames !== 'undefined' && screenNames.length > 0) { 47 | await window 48 | .showInformationMessage( 49 | 'Do you want to extract ' + 50 | (screenNames.length > 1 51 | ? 'the ' + screenNames.length + ' screen definitions?' 52 | : ' the one screen definition?'), 53 | { modal: true }, 54 | ...['Yes'] 55 | ) 56 | .then(async (response) => { 57 | if (response === 'Yes') { 58 | await window.withProgress( 59 | { 60 | title: 'Extracting Screen Definitions', 61 | location: ProgressLocation.Notification, 62 | cancellable: true, 63 | }, 64 | async (progress, cancelToken) => { 65 | let percent = (1 / screenNames.length) * 100; 66 | 67 | let overwriteAll = false; 68 | let overwrite = false; 69 | 70 | await asyncForEach( 71 | screenNames, 72 | async (screenName) => { 73 | if (!cancelToken.isCancellationRequested) { 74 | progress.report({ 75 | increment: percent, 76 | message: `Extracting ${screenName.toLowerCase()}`, 77 | }); 78 | let screenInfo = await client.getScreen( 79 | screenName 80 | ); 81 | 82 | let fileExtension = '.xml'; 83 | 84 | let outputFile = 85 | extractLoc + 86 | '/' + 87 | screenName.toLowerCase() + 88 | fileExtension; 89 | let xml = format( 90 | screenInfo.presentation 91 | ); 92 | // if the file doesn't exist then just write it out. 93 | if (!fs.existsSync(outputFile)) { 94 | fs.writeFileSync(outputFile, xml); 95 | } else { 96 | let incomingHash = crypto 97 | .createHash('sha256') 98 | .update(xml) 99 | .digest('hex'); 100 | let fileHash = crypto 101 | .createHash('sha256') 102 | .update( 103 | fs.readFileSync(outputFile) 104 | ) 105 | .digest('hex'); 106 | 107 | if (fileHash !== incomingHash) { 108 | if (!overwriteAll) { 109 | await window 110 | .showInformationMessage( 111 | `The screen ${screenName.toLowerCase()}${fileExtension} exists. \nReplace?`, 112 | { modal: true }, 113 | ...[ 114 | 'Replace', 115 | 'Replace All', 116 | 'Skip', 117 | ] 118 | ) 119 | .then( 120 | async ( 121 | response 122 | ) => { 123 | if ( 124 | response === 125 | 'Replace' 126 | ) { 127 | overwrite = true; 128 | } else if ( 129 | response === 130 | 'Replace All' 131 | ) { 132 | overwriteAll = true; 133 | } else if ( 134 | response === 135 | 'Skip' 136 | ) { 137 | // do nothing 138 | overwrite = false; 139 | } else { 140 | // @ts-ignore 141 | cancelToken.cancel(); 142 | } 143 | } 144 | ); 145 | } 146 | if (overwriteAll || overwrite) { 147 | fs.writeFileSync( 148 | outputFile, 149 | xml 150 | ); 151 | overwrite = false; 152 | } 153 | } 154 | } 155 | 156 | if ( 157 | cancelToken.isCancellationRequested 158 | ) { 159 | return; 160 | } 161 | } 162 | } 163 | ); 164 | 165 | if (!cancelToken.isCancellationRequested) { 166 | window.showInformationMessage( 167 | 'Screen definitions extracted.', 168 | { modal: true } 169 | ); 170 | } 171 | } 172 | ); 173 | } 174 | }); 175 | } else { 176 | window.showErrorMessage( 177 | 'No screen definitions were found to extract.', 178 | { modal: true } 179 | ); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/commands/extract-script-command.js: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as crypto from 'crypto'; 3 | 4 | import { ProgressLocation, window, workspace } from 'vscode'; 5 | 6 | // get a reference to the fetched source object 7 | import { getMaximoConfig } from '../extension'; 8 | 9 | export default async function extractScriptCommand(client) { 10 | let extractLoc = (await getMaximoConfig()).extractLocation; 11 | 12 | // if the extract location has not been specified use the workspace folder. 13 | if (typeof extractLoc === 'undefined' || !extractLoc) { 14 | if (workspace.workspaceFolders !== undefined) { 15 | extractLoc = workspace.workspaceFolders[0].uri.fsPath; 16 | } else { 17 | window.showErrorMessage( 18 | 'A working folder must be selected or an export folder configured before exporting automation scripts. ', 19 | { 20 | modal: true, 21 | } 22 | ); 23 | return; 24 | } 25 | } 26 | 27 | if (!fs.existsSync(extractLoc)) { 28 | window.showErrorMessage( 29 | `The script extract folder ${extractLoc} does not exist.`, 30 | { modal: true } 31 | ); 32 | return; 33 | } 34 | 35 | let scriptNames = await window.withProgress( 36 | { 37 | title: 'Getting script names', 38 | location: ProgressLocation.Notification, 39 | }, 40 | async (progress) => { 41 | return await client.getAllScriptNames(progress); 42 | } 43 | ); 44 | 45 | if (typeof scriptNames !== 'undefined' && scriptNames.length > 0) { 46 | scriptNames.sort(); 47 | await window 48 | .showQuickPick(scriptNames, { placeHolder: 'Search for script' }) 49 | .then(async (scriptName) => { 50 | if (typeof scriptName !== 'undefined') { 51 | await window.withProgress( 52 | { 53 | title: `Extracting ${scriptName}`, 54 | location: ProgressLocation.Notification, 55 | cancellable: true, 56 | }, 57 | async (progress, cancelToken) => { 58 | let scriptInfo = await client.getScript(scriptName); 59 | 60 | let fileExtension = getExtension( 61 | scriptInfo.scriptLanguage 62 | ); 63 | 64 | let outputFile = 65 | extractLoc + 66 | '/' + 67 | scriptName.toLowerCase() + 68 | fileExtension; 69 | 70 | // if the file doesn't exist then just write it out. 71 | if (!fs.existsSync(outputFile)) { 72 | fs.writeFileSync(outputFile, scriptInfo.script); 73 | } else { 74 | let incomingHash = crypto 75 | .createHash('sha256') 76 | .update(scriptInfo.script) 77 | .digest('hex'); 78 | let fileHash = crypto 79 | .createHash('sha256') 80 | .update(fs.readFileSync(outputFile)) 81 | .digest('hex'); 82 | 83 | if (fileHash !== incomingHash) { 84 | await window 85 | .showInformationMessage( 86 | `The script ${scriptName.toLowerCase()}${fileExtension} exists. \nReplace?`, 87 | { modal: true }, 88 | ...['Replace'] 89 | ) 90 | .then(async (response) => { 91 | if (response === 'Replace') { 92 | fs.writeFileSync( 93 | outputFile, 94 | scriptInfo.script 95 | ); 96 | } else { 97 | // @ts-ignore 98 | cancelToken.cancel(); 99 | } 100 | }); 101 | } 102 | } 103 | 104 | if (!cancelToken.isCancellationRequested) { 105 | window.showInformationMessage( 106 | `Automation script "${scriptName}" extracted.`, 107 | { modal: true } 108 | ); 109 | } 110 | } 111 | ); 112 | } 113 | }); 114 | } 115 | } 116 | 117 | function getExtension(scriptLanguage) { 118 | switch (scriptLanguage.toLowerCase()) { 119 | case 'python': 120 | case 'jython': 121 | return '.py'; 122 | case 'nashorn': 123 | case 'javascript': 124 | case 'emcascript': 125 | case 'js': 126 | return '.js'; 127 | default: 128 | return '.unknown'; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/commands/extract-scripts-command.js: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as crypto from 'crypto'; 3 | 4 | import { ProgressLocation, window, workspace } from 'vscode'; 5 | 6 | // get a reference to the fetched source object 7 | import { getMaximoConfig, asyncForEach } from '../extension'; 8 | 9 | export default async function extractScriptsCommand(client) { 10 | let extractLoc = (await getMaximoConfig()).extractLocation; 11 | // if the extract location has not been specified use the workspace folder. 12 | if (typeof extractLoc === 'undefined' || !extractLoc) { 13 | if (workspace.workspaceFolders !== undefined) { 14 | extractLoc = workspace.workspaceFolders[0].uri.fsPath; 15 | } else { 16 | window.showErrorMessage( 17 | 'A working folder must be selected or an export folder configured before exporting automation scripts. ', 18 | { 19 | modal: true, 20 | } 21 | ); 22 | return; 23 | } 24 | } 25 | 26 | if (!fs.existsSync(extractLoc)) { 27 | window.showErrorMessage( 28 | `The script extract folder ${extractLoc} does not exist.`, 29 | { modal: true } 30 | ); 31 | return; 32 | } 33 | 34 | let scriptNames = await window.withProgress( 35 | { 36 | title: 'Getting script names', 37 | location: ProgressLocation.Notification, 38 | }, 39 | async (progress) => { 40 | return await client.getAllScriptNames(progress); 41 | } 42 | ); 43 | 44 | if (typeof scriptNames !== 'undefined' && scriptNames.length > 0) { 45 | await window 46 | .showInformationMessage( 47 | 'Do you want to extract ' + 48 | (scriptNames.length > 1 49 | ? 'the ' + scriptNames.length + ' automation scripts?' 50 | : ' the one automation script?'), 51 | { modal: true }, 52 | ...['Yes'] 53 | ) 54 | .then(async (response) => { 55 | if (response === 'Yes') { 56 | await window.withProgress( 57 | { 58 | title: 'Extracting Automation Script', 59 | location: ProgressLocation.Notification, 60 | cancellable: true, 61 | }, 62 | async (progress, cancelToken) => { 63 | let percent = Math.round( 64 | (1 / scriptNames.length) * 100 65 | ); 66 | 67 | let overwriteAll = false; 68 | let overwrite = false; 69 | 70 | await asyncForEach( 71 | scriptNames, 72 | async (scriptName) => { 73 | if (!cancelToken.isCancellationRequested) { 74 | progress.report({ 75 | increment: percent, 76 | message: `Extracting ${scriptName}`, 77 | }); 78 | let scriptInfo = await client.getScript( 79 | scriptName 80 | ); 81 | 82 | let fileExtension = getExtension( 83 | scriptInfo.scriptLanguage 84 | ); 85 | 86 | let outputFile = 87 | extractLoc + 88 | '/' + 89 | scriptName.toLowerCase() + 90 | fileExtension; 91 | 92 | // if the file doesn't exist then just write it out. 93 | if (!fs.existsSync(outputFile)) { 94 | fs.writeFileSync( 95 | outputFile, 96 | scriptInfo.script 97 | ); 98 | } else { 99 | let incomingHash = crypto 100 | .createHash('sha256') 101 | .update(scriptInfo.script) 102 | .digest('hex'); 103 | let fileHash = crypto 104 | .createHash('sha256') 105 | .update( 106 | fs.readFileSync(outputFile) 107 | ) 108 | .digest('hex'); 109 | 110 | if (fileHash !== incomingHash) { 111 | if (!overwriteAll) { 112 | await window 113 | .showInformationMessage( 114 | `The script ${scriptName.toLowerCase()}${fileExtension} exists. \nReplace?`, 115 | { modal: true }, 116 | ...[ 117 | 'Replace', 118 | 'Replace All', 119 | 'Skip', 120 | ] 121 | ) 122 | .then( 123 | async ( 124 | response 125 | ) => { 126 | if ( 127 | response === 128 | 'Replace' 129 | ) { 130 | overwrite = true; 131 | } else if ( 132 | response === 133 | 'Replace All' 134 | ) { 135 | overwriteAll = true; 136 | } else if ( 137 | response === 138 | 'Skip' 139 | ) { 140 | // do nothing 141 | overwrite = false; 142 | } else { 143 | // @ts-ignore 144 | cancelToken.cancel(); 145 | } 146 | } 147 | ); 148 | } 149 | if (overwriteAll || overwrite) { 150 | fs.writeFileSync( 151 | outputFile, 152 | scriptInfo.script 153 | ); 154 | overwrite = false; 155 | } 156 | } 157 | } 158 | 159 | if ( 160 | cancelToken.isCancellationRequested 161 | ) { 162 | return; 163 | } 164 | } 165 | } 166 | ); 167 | 168 | if (!cancelToken.isCancellationRequested) { 169 | window.showInformationMessage( 170 | 'Automation scripts extracted.', 171 | { modal: true } 172 | ); 173 | } 174 | } 175 | ); 176 | } 177 | }); 178 | } else { 179 | window.showErrorMessage('No scripts were found to extract.', { 180 | modal: true, 181 | }); 182 | } 183 | } 184 | 185 | function getExtension(scriptLanguage) { 186 | switch (scriptLanguage.toLowerCase()) { 187 | case 'python': 188 | case 'jython': 189 | return '.py'; 190 | case 'nashorn': 191 | case 'javascript': 192 | case 'emcascript': 193 | case 'js': 194 | return '.js'; 195 | default: 196 | return '.unknown'; 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/commands/select-environment.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable indent */ 2 | // @ts-nocheck 3 | import { ThemeIcon, window } from 'vscode'; 4 | import { workspace } from 'vscode'; 5 | import * as path from 'path'; 6 | import * as fs from 'fs'; 7 | import LocalConfiguration from '../config'; 8 | 9 | export default async function selectEnvironment( 10 | context, 11 | statusBar, 12 | getLocalConfig 13 | ) { 14 | let config = await (await getLocalConfig()).config; 15 | let items = []; 16 | 17 | if (Array.isArray(config)) { 18 | config.forEach((item) => { 19 | if (typeof item.name === 'string') { 20 | items.push({ 21 | label: item.name, 22 | description: 23 | typeof item.description === 'string' 24 | ? item.description 25 | : '', 26 | iconPath: item.selected ? new ThemeIcon('check') : null, 27 | }); 28 | } 29 | }); 30 | } 31 | 32 | const result = await window.showQuickPick(items, { 33 | placeHolder: 'Select a Maximo Environment', 34 | title: 'Select Maximo Environment', 35 | }); 36 | 37 | if (result) { 38 | statusBar.text = `${result.label}`; 39 | statusBar.tooltip = `${result.description}`; 40 | 41 | config.forEach((item) => { 42 | item.selected = 43 | item.name === result.label || item.host === result.label; 44 | }); 45 | 46 | if (workspace.workspaceFolders !== undefined) { 47 | let workspaceConfigPath = 48 | workspace.workspaceFolders[0].uri.fsPath + 49 | path.sep + 50 | '.devtools-config.json'; 51 | if (fs.existsSync(workspaceConfigPath)) { 52 | var secretStorage = context.secrets; 53 | let localConfig = new LocalConfiguration( 54 | workspaceConfigPath, 55 | secretStorage 56 | ); 57 | if (localConfig.configAvailable) { 58 | await localConfig.encrypt(config); 59 | } 60 | } 61 | } 62 | } 63 | 64 | // window.showInformationMessage(`Got: ${result}`); 65 | } 66 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import * as fs from 'fs'; 3 | import * as path from 'path'; 4 | // import { SecretStorage } from 'vscode'; 5 | import { Buffer } from 'node:buffer'; 6 | 7 | import * as crypto from 'crypto'; 8 | 9 | export default class LocalConfiguration { 10 | /** 11 | * @param {String} path 12 | * @param {SecretStorage} secretStorage 13 | */ 14 | constructor(path, secretStorage) { 15 | this.path = path; 16 | this.secretStorage = secretStorage; 17 | this.algorithm = 'aes-256-cbc'; 18 | } 19 | 20 | get configAvailable() { 21 | return fs.existsSync(this.path); 22 | } 23 | 24 | get config() { 25 | if (!this.configAvailable) { 26 | return {}; 27 | } else { 28 | return this.decrypt(JSON.parse(fs.readFileSync(this.path, 'utf8'))); 29 | } 30 | } 31 | 32 | async encryptIfRequired() { 33 | this.encrypt(JSON.parse(fs.readFileSync(this.path, 'utf8'))); 34 | let gitIgnorePath = path.dirname(this.path) + path.sep + '.gitignore'; 35 | if (fs.existsSync(gitIgnorePath)) { 36 | fs.readFile(gitIgnorePath, function (err, data) { 37 | if (err) throw err; 38 | if (data.indexOf('.devtools-config.json') < 0) { 39 | fs.appendFile( 40 | gitIgnorePath, 41 | '\n.devtools-config.json', 42 | function (err) { 43 | if (err) throw err; 44 | } 45 | ); 46 | } 47 | }); 48 | } else { 49 | fs.writeFileSync(gitIgnorePath, '.devtools-config.json'); 50 | } 51 | } 52 | 53 | async encrypt(config) { 54 | if (Array.isArray(config)) { 55 | config = await Promise.all( 56 | config.map(async (item) => { 57 | return await this._encrypt(item); 58 | }) 59 | ); 60 | } else { 61 | await this._encrypt(config); 62 | } 63 | 64 | fs.writeFileSync(this.path, JSON.stringify(config, null, 4)); 65 | } 66 | 67 | async _encrypt(config) { 68 | let encryptKey = await this.secretStorage.get('encryptKey'); 69 | 70 | if (!encryptKey) { 71 | encryptKey = 72 | new Buffer.from(crypto.randomBytes(16)).toString('hex') + 73 | new Buffer.from(crypto.randomBytes(32)).toString('hex'); 74 | await this.secretStorage.store('encryptKey', encryptKey); 75 | } 76 | 77 | const iv = Buffer.from(encryptKey.slice(0, 32), 'hex'); 78 | const key = Buffer.from(encryptKey.slice(32), 'hex'); 79 | 80 | if (config.password && !config.password.startsWith('{encrypted}')) { 81 | const cipher = crypto.createCipheriv(this.algorithm, key, iv); 82 | let encPassword = cipher.update(config.password, 'utf-8', 'hex'); 83 | encPassword += cipher.final('hex'); 84 | 85 | config.password = '{encrypted}' + encPassword; 86 | } 87 | if (config.apiKey && !config.apiKey.startsWith('{encrypted}')) { 88 | const cipher = crypto.createCipheriv(this.algorithm, key, iv); 89 | let encApiKey = cipher.update(config.apiKey, 'utf-8', 'hex'); 90 | encApiKey += cipher.final('hex'); 91 | 92 | config.apiKey = '{encrypted}' + encApiKey; 93 | } 94 | 95 | if ( 96 | config.proxyPassword && 97 | !config.proxyPassword.startsWith('{encrypted}') 98 | ) { 99 | const cipher = crypto.createCipheriv(this.algorithm, key, iv); 100 | let encProxyPassword = cipher.update( 101 | config.proxyPassword, 102 | 'utf-8', 103 | 'hex' 104 | ); 105 | encProxyPassword += cipher.final('hex'); 106 | 107 | config.proxyPassword = '{encrypted}' + encProxyPassword; 108 | } 109 | return config; 110 | } 111 | 112 | async decrypt(config) { 113 | if (Array.isArray(config)) { 114 | return await Promise.all( 115 | config.map(async (item) => { 116 | return await this._decrypt(item); 117 | }) 118 | ); 119 | } else { 120 | return await this._decrypt(config); 121 | } 122 | } 123 | 124 | async _decrypt(config) { 125 | let encryptKey = await this.secretStorage.get('encryptKey'); 126 | 127 | if (!encryptKey) { 128 | return config; 129 | } 130 | 131 | const iv = Buffer.from(encryptKey.slice(0, 32), 'hex'); 132 | const key = Buffer.from(encryptKey.slice(32), 'hex'); 133 | 134 | if (config.password && config.password.startsWith('{encrypted}')) { 135 | const decipher = crypto.createDecipheriv(this.algorithm, key, iv); 136 | let decryptedPassword = decipher.update( 137 | config.password.substring(11), 138 | 'hex', 139 | 'utf-8' 140 | ); 141 | decryptedPassword += decipher.final('utf8'); 142 | config.password = decryptedPassword; 143 | } 144 | 145 | if (config.apiKey && config.apiKey.startsWith('{encrypted}')) { 146 | const decipher = crypto.createDecipheriv(this.algorithm, key, iv); 147 | let decryptedApiKey = decipher.update( 148 | config.apiKey.substring(11), 149 | 'hex', 150 | 'utf-8' 151 | ); 152 | decryptedApiKey += decipher.final('utf8'); 153 | config.apiKey = decryptedApiKey; 154 | } 155 | 156 | if ( 157 | config.proxyPassword && 158 | config.proxyPassword.startsWith('{encrypted}') 159 | ) { 160 | const decipher = crypto.createDecipheriv(this.algorithm, key, iv); 161 | let decryptedProxyPassword = decipher.update( 162 | config.proxyPassword.substring(11), 163 | 'hex', 164 | 'utf-8' 165 | ); 166 | decryptedProxyPassword += decipher.final('utf8'); 167 | config.proxyPassword = decryptedProxyPassword; 168 | } 169 | 170 | return config; 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/maximo/errors.js: -------------------------------------------------------------------------------- 1 | export class MaximoError extends Error { 2 | constructor(message, reasonCode, statusCode) { 3 | super(); 4 | this.reasonCode = reasonCode; 5 | this.statusCode = statusCode; 6 | this.message = message; 7 | } 8 | } 9 | 10 | export class LoginFailedError extends MaximoError { 11 | constructor(message, reasonCode, statusCode) { 12 | super(message, reasonCode, statusCode); 13 | } 14 | } 15 | 16 | export class PasswordExpiredError extends MaximoError { 17 | constructor(message, reasonCode, statusCode) { 18 | super(message, reasonCode, statusCode); 19 | } 20 | } 21 | 22 | export class PasswordResetFailedError extends MaximoError { 23 | constructor(message, reasonCode, statusCode) { 24 | super(message, reasonCode, statusCode); 25 | } 26 | } 27 | 28 | export class MxAccessError extends MaximoError { 29 | constructor(message, reasonCode, statusCode) { 30 | super(message, reasonCode, statusCode); 31 | } 32 | } 33 | 34 | export class ResourceNotFoundError extends MaximoError { 35 | constructor(message, reasonCode, statusCode) { 36 | super(message, reasonCode, statusCode); 37 | } 38 | } 39 | 40 | export class InvalidApiKeyError extends MaximoError { 41 | constructor(message, reasonCode, statusCode) { 42 | super(message, reasonCode, statusCode); 43 | } 44 | } 45 | 46 | export class MxAdminLogoutError extends MaximoError { 47 | constructor(message, reasonCode, statusCode) { 48 | super(message, reasonCode, statusCode); 49 | } 50 | } 51 | 52 | export class MxDuplicateTransactionError extends MaximoError { 53 | constructor(message, reasonCode, statusCode, requestUri, transcationId) { 54 | super(message, reasonCode, statusCode); 55 | this.requestUri = requestUri; 56 | this.transcationId = transcationId; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/maximo/install.js: -------------------------------------------------------------------------------- 1 | async function install() {} 2 | -------------------------------------------------------------------------------- /src/maximo/maximo-config.js: -------------------------------------------------------------------------------- 1 | import { Buffer } from 'buffer'; 2 | 3 | export default class MaximoConfig { 4 | constructor({ 5 | username, 6 | password, 7 | host, 8 | port = 443, 9 | useSSL = true, 10 | context = 'maximo', 11 | allowUntrustedCerts = false, 12 | configurationTimeout = 5000 * 60000, // 5 minutes 13 | connectTimeout = 5000, 14 | responseTimeout = 30000, 15 | lean = true, 16 | ca, 17 | maxauthOnly = false, 18 | apiKey, 19 | extractLocation, 20 | extractLocationScreens, 21 | extractLocationForms, 22 | extractLocationReports, 23 | proxyHost, 24 | proxyPort = 3128, 25 | proxyUsername, 26 | proxyPassword, 27 | }) { 28 | this.username = username; 29 | this.password = password; 30 | this.host = host; 31 | this.port = port; 32 | this.useSSL = useSSL; 33 | this.configurationTimeout = configurationTimeout; 34 | this.context = context; 35 | this.allowUntrustedCerts = allowUntrustedCerts; 36 | this.connectTimeout = connectTimeout; 37 | this.responseTimeout = responseTimeout; 38 | this.lean = lean; 39 | this.ca = ca; 40 | this.maxauthOnly = maxauthOnly; 41 | this.apiKey = apiKey; 42 | this.extractLocation = extractLocation; 43 | this.extractLocationScreens = extractLocationScreens; 44 | this.extractLocationForms = extractLocationForms; 45 | this.extractLocationReports = extractLocationReports; 46 | this.proxyHost = proxyHost; 47 | this.proxyPort = proxyPort; 48 | this.proxyUsername = proxyUsername; 49 | this.proxyPassword = proxyPassword; 50 | } 51 | 52 | get maxauth() { 53 | return Buffer.from(this.username + ':' + this.password).toString( 54 | 'base64' 55 | ); 56 | } 57 | 58 | get baseURL() { 59 | return ( 60 | (this.useSSL ? 'https://' : 'http://') + 61 | this.host + 62 | ((this.port === 443 && this.useSSL) || 63 | (this.port === 80 && !this.useSSL) 64 | ? '' 65 | : ':' + this.port) + 66 | '/' + 67 | this.context + 68 | (this.apiKey ? '/api' : '/oslc') 69 | ); 70 | } 71 | 72 | get baseProxyURL() { 73 | if (!this.proxyHost) { 74 | return null; 75 | } else { 76 | return ( 77 | (this.useSSL ? 'https://' : 'http://') + 78 | this.proxyHost + 79 | ':' + 80 | this.proxyPort + 81 | '/' + 82 | this.context + 83 | (this.apiKey ? '/api' : '/oslc') 84 | ); 85 | } 86 | } 87 | 88 | get formLoginURL() { 89 | return ( 90 | (this.useSSL ? 'https://' : 'http://') + 91 | this.host + 92 | ((this.port === 443 && this.useSSL) || 93 | (this.port === 80 && !this.useSSL) 94 | ? '' 95 | : ':' + this.port) + 96 | '/' + 97 | this.context + 98 | '/j_security_check' 99 | ); 100 | } 101 | 102 | get proxyConfigured() { 103 | return ( 104 | this.proxyHost && 105 | this.proxyPort && 106 | this.proxyPort > 0 && 107 | this.proxyPort < 65536 108 | ); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/maximo/provider.js: -------------------------------------------------------------------------------- 1 | export default class ServerSourceProvider { 2 | constructor(sourceMap) { 3 | this.sourceMap = sourceMap; 4 | } 5 | provideTextDocumentContent(uri, token) { 6 | let source = this.sourceMap[uri.path]; 7 | this.sourceMap.delete(uri.path); 8 | return source; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/settings.js: -------------------------------------------------------------------------------- 1 | import { window, commands } from 'vscode'; 2 | import isValidHostname from 'is-valid-hostname'; 3 | 4 | export function validateSettings(maximoConfig) { 5 | const host = maximoConfig.host; 6 | const userName = maximoConfig.username; 7 | const port = maximoConfig.port; 8 | const apiKey = maximoConfig.apiKey; 9 | 10 | if (!host) { 11 | window 12 | .showInformationMessage( 13 | 'The Maximo host is missing, set it now?', 14 | { modal: true }, 15 | ...['Yes'] 16 | ) 17 | .then((response) => { 18 | if (response === 'Yes') { 19 | commands.executeCommand( 20 | 'workbench.action.openSettings', 21 | 'maximo' 22 | ); 23 | } 24 | }); 25 | return false; 26 | } 27 | 28 | if (!isValidHostname(host)) { 29 | window 30 | .showInformationMessage( 31 | 'The Maximo host is invalid, fix it now?', 32 | { modal: true }, 33 | ...['Yes'] 34 | ) 35 | .then((response) => { 36 | if (response === 'Yes') { 37 | commands.executeCommand( 38 | 'workbench.action.openSettings', 39 | 'maximo' 40 | ); 41 | } 42 | }); 43 | return false; 44 | } 45 | 46 | if (port < 0 || port > 65535) { 47 | window 48 | .showInformationMessage( 49 | 'The Maximo port must be between 0 and 65535, fix it now?', 50 | { modal: true }, 51 | ...['Yes'] 52 | ) 53 | .then((response) => { 54 | if (response === 'Yes') { 55 | commands.executeCommand( 56 | 'workbench.action.openSettings', 57 | 'maximo' 58 | ); 59 | } 60 | }); 61 | return false; 62 | } 63 | 64 | if (!userName && !apiKey) { 65 | window 66 | .showInformationMessage( 67 | 'The Maximo user name is missing and an API Key was not provided, set it now?', 68 | { modal: true }, 69 | ...['Yes'] 70 | ) 71 | .then((response) => { 72 | if (response === 'Yes') { 73 | commands.executeCommand( 74 | 'workbench.action.openSettings', 75 | 'maximo' 76 | ); 77 | } 78 | }); 79 | return false; 80 | } 81 | 82 | return true; 83 | } 84 | -------------------------------------------------------------------------------- /test/runTest.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const { runTests } = require('@vscode/test-electron'); 4 | 5 | async function main() { 6 | try { 7 | // The folder containing the Extension Manifest package.json 8 | // Passed to `--extensionDevelopmentPath` 9 | const extensionDevelopmentPath = path.resolve(__dirname, '../'); 10 | 11 | // The path to the extension test script 12 | // Passed to --extensionTestsPath 13 | const extensionTestsPath = path.resolve(__dirname, './suite/index'); 14 | 15 | // Download VS Code, unzip it and run the integration test 16 | await runTests({ extensionDevelopmentPath, extensionTestsPath }); 17 | } catch (err) { 18 | console.error('Failed to run tests'); 19 | process.exit(1); 20 | } 21 | } 22 | 23 | main(); 24 | -------------------------------------------------------------------------------- /test/suite/extension.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | 3 | // You can import and use all API from the 'vscode' module 4 | // as well as import your extension to test it 5 | const vscode = require('vscode'); 6 | // const myExtension = require('../extension'); 7 | 8 | suite('Extension Test Suite', () => { 9 | vscode.window.showInformationMessage('Start all tests.'); 10 | 11 | test('Sample test', () => { 12 | assert.strictEqual(-1, [1, 2, 3].indexOf(5)); 13 | assert.strictEqual(-1, [1, 2, 3].indexOf(0)); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /test/suite/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const Mocha = require('mocha'); 3 | const glob = require('glob'); 4 | 5 | function run() { 6 | // Create the mocha test 7 | const mocha = new Mocha({ 8 | ui: 'tdd', 9 | color: true 10 | }); 11 | 12 | const testsRoot = path.resolve(__dirname, '..'); 13 | 14 | return new Promise((c, e) => { 15 | glob('**/**.test.js', { cwd: testsRoot }, (err, files) => { 16 | if (err) { 17 | return e(err); 18 | } 19 | 20 | // Add files to the test suite 21 | files.forEach(f => mocha.addFile(path.resolve(testsRoot, f))); 22 | 23 | try { 24 | // Run the mocha test 25 | mocha.run(failures => { 26 | if (failures > 0) { 27 | e(new Error(`${failures} tests failed.`)); 28 | } else { 29 | c(); 30 | } 31 | }); 32 | } catch (err) { 33 | console.error(err); 34 | e(err); 35 | } 36 | }); 37 | }); 38 | } 39 | 40 | module.exports = { 41 | run 42 | }; 43 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | 3 | 'use strict'; 4 | 5 | const path = require('path'); 6 | const webpack = require('webpack'); 7 | // var nodeExternals = require('webpack-node-externals'); 8 | 9 | /**@type {import('webpack').Configuration}*/ 10 | const config = { 11 | target: 'node', // vscode extensions run in webworker context for VS Code web 📖 -> https://webpack.js.org/configuration/target/#target 12 | 13 | entry: './src/extension.js', // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/ 14 | output: { 15 | // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/ 16 | path: path.resolve(__dirname, 'dist'), 17 | filename: 'extension.js', 18 | libraryTarget: 'commonjs2', 19 | devtoolModuleFilenameTemplate: '../[resource-path]' 20 | }, 21 | devtool: 'source-map', 22 | externals: { 23 | vscode: 'commonjs vscode' // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/ 24 | }, // in order to ignore all modules in node_modules folder 25 | // externalsPresets: { 26 | // node: true // in order to ignore built-in modules like path, fs, etc. 27 | // }, 28 | resolve: { 29 | // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader 30 | mainFields: ['browser', 'module', 'main'], // look for `browser` entry point in imported node modules 31 | extensions: ['.js'], 32 | alias: { 33 | // provides alternate implementation for node module and source files 34 | }, 35 | fallback: { 36 | // Webpack 5 no longer polyfills Node.js core modules automatically. 37 | // see https://webpack.js.org/configuration/resolve/#resolvefallback 38 | // for the list of Node.js core module polyfills. 39 | } 40 | }, 41 | module: { 42 | rules: [ 43 | { 44 | test: /\.ts$/, 45 | exclude: /node_modules/, 46 | use: [ 47 | { 48 | loader: 'ts-loader', 49 | options: { 50 | compilerOptions: { 51 | "module": "es6" // override `tsconfig.json` so that TypeScript emits native JavaScript modules. 52 | } 53 | } 54 | } 55 | ] 56 | } 57 | ] 58 | } 59 | }; 60 | module.exports = config; --------------------------------------------------------------------------------