├── .eslintrc.json ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .prettierrc ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── ATTRIBUTION.md ├── CHANGELOG.md ├── LICENSE ├── README.md ├── images ├── header.png └── icon.png ├── package-lock.json ├── package.json ├── src ├── extension.ts ├── tasks │ ├── provider.ts │ └── terminal.ts ├── types │ └── index.ts └── util │ ├── notifications.ts │ └── output.ts ├── tsconfig.json ├── vsc-extension-quickstart.md └── webpack.config.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { 5 | "ecmaVersion": 6, 6 | "sourceType": "module", 7 | "project": "tsconfig.json" 8 | }, 9 | "plugins": [ 10 | "@typescript-eslint", 11 | "prettier" 12 | ], 13 | "extends": [ 14 | "eslint:recommended", 15 | "plugin:@typescript-eslint/recommended", 16 | "plugin:@typescript-eslint/recommended-requiring-type-checking", 17 | "plugin:prettier/recommended" 18 | ], 19 | "rules": { 20 | "@typescript-eslint/no-unused-vars": ["warn", {"args": "all", "argsIgnorePattern": "^_"}], 21 | "@typescript-eslint/naming-convention": "warn", 22 | "@typescript-eslint/semi": "warn", 23 | "@typescript-eslint/require-await": "off", 24 | "curly": ["warn", "multi-or-nest", "consistent"], 25 | "eqeqeq": "warn", 26 | "no-throw-literal": "warn", 27 | "semi": "off" 28 | }, 29 | "ignorePatterns": [ 30 | "out", 31 | "dist", 32 | "**/*.d.ts" 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | tags: ["v*"] 6 | branches: [main] 7 | pull_request: 8 | branches: [main] 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Setup Node 14 16 | uses: actions/setup-node@v1 17 | with: 18 | node-version: 14 19 | - name: Install dependencies 20 | run: npm ci 21 | - name: Run tests 22 | run: npm test 23 | 24 | package: 25 | runs-on: ubuntu-latest 26 | needs: build 27 | steps: 28 | - uses: actions/checkout@v2 29 | - name: Install dependencies 30 | run: npm ci 31 | - name: Package extension 32 | uses: lannonbr/vsce-action@master 33 | with: 34 | args: "package" 35 | - name: Get package name 36 | id: get_package_name 37 | run: | 38 | echo "::set-output name=name::$(echo defold-vscode-build-*.vsix)" 39 | - name: Upload artifact 40 | uses: actions/upload-artifact@v2-preview 41 | with: 42 | name: extension-package 43 | path: ${{ steps.get_package_name.outputs.name }} 44 | 45 | publish: 46 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') 47 | runs-on: ubuntu-latest 48 | needs: build 49 | steps: 50 | - uses: actions/checkout@v2 51 | - name: Install dependencies 52 | run: npm ci 53 | - name: Publish extension 54 | uses: lannonbr/vsce-action@master 55 | with: 56 | args: "publish -p ${{ secrets.VSCE_TOKEN }}" 57 | 58 | release: 59 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') 60 | runs-on: ubuntu-latest 61 | needs: package 62 | steps: 63 | - name: Create release 64 | id: create_release 65 | uses: actions/create-release@v1 66 | env: 67 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 68 | with: 69 | tag_name: ${{ github.ref }} 70 | release_name: ${{ github.ref }} 71 | - uses: actions/download-artifact@v2-preview 72 | with: 73 | name: extension-package 74 | - id: find_file_name 75 | run: echo "::set-output name=file::$(ls *.vsix)" 76 | - name: Upload release artifact 77 | uses: actions/upload-release-asset@v1 78 | env: 79 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 80 | with: 81 | upload_url: ${{ steps.create_release.outputs.upload_url }} 82 | asset_path: ${{ steps.find_file_name.outputs.file }} 83 | asset_name: ${{ steps.find_file_name.outputs.file }} 84 | asset_content_type: application/vsix 85 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | dist 3 | node_modules 4 | .vscode-test/ 5 | *.vsix 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": false, 3 | "tabWidth": 2, 4 | "singleQuote": true, 5 | "printWidth": 120, 6 | "trailingComma": "es5" 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "dbaeumer.vscode-eslint", 6 | "amodio.tsl-problem-matcher" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Run Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "args": [ 13 | "--extensionDevelopmentPath=${workspaceFolder}" 14 | ], 15 | "outFiles": [ 16 | "${workspaceFolder}/dist/**/*.js" 17 | ], 18 | "preLaunchTask": "${defaultBuildTask}" 19 | }, 20 | { 21 | "name": "Extension Tests", 22 | "type": "extensionHost", 23 | "request": "launch", 24 | "args": [ 25 | "--extensionDevelopmentPath=${workspaceFolder}", 26 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" 27 | ], 28 | "outFiles": [ 29 | "${workspaceFolder}/out/test/**/*.js" 30 | ], 31 | "preLaunchTask": "npm: test-watch" 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": true, // set this to true to hide the "out" folder with the compiled JS files 5 | "dist": true // set this to true to hide the "dist" folder with the compiled JS files 6 | }, 7 | "search.exclude": { 8 | "out": true, // set this to false to include "out" folder in search results 9 | "dist": true // set this to false to include "dist" folder in search results 10 | }, 11 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 12 | "typescript.tsc.autoDetect": "off", 13 | "eslint.validate": [ 14 | "typescript", 15 | ], 16 | "editor.codeActionsOnSave": { 17 | "source.fixAll.eslint": true, 18 | }, 19 | "cSpell.words": [ 20 | "defold" 21 | ] 22 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": [ 10 | "$ts-webpack-watch", 11 | "$tslint-webpack-watch" 12 | ], 13 | "isBackground": true, 14 | "presentation": { 15 | "reveal": "never" 16 | }, 17 | "group": { 18 | "kind": "build", 19 | "isDefault": true 20 | } 21 | }, 22 | { 23 | "type": "npm", 24 | "script": "test-watch", 25 | "problemMatcher": "$tsc-watch", 26 | "isBackground": true, 27 | "presentation": { 28 | "reveal": "never" 29 | }, 30 | "group": "build" 31 | } 32 | ] 33 | } -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/** 4 | node_modules/** 5 | src/** 6 | .gitignore 7 | .yarnrc 8 | vsc-extension-quickstart.md 9 | **/tsconfig.json 10 | **/.eslintrc.json 11 | **/*.map 12 | **/*.ts 13 | -------------------------------------------------------------------------------- /ATTRIBUTION.md: -------------------------------------------------------------------------------- 1 | ## defold name & logo 2 | 3 | The Defold name & logo (images/icon.png) are registered trademark (®) of the 4 | Defold Foundation used with express permission from the Defold Foundation and 5 | are subject to the terms listed at https://defold.com/logo-and-trademark/ 6 | 7 | --- 8 | 9 | ## defold-vscode-guide 10 | 11 | Parts of this work are inspired by and/or derived from 12 | https://github.com/astrochili/defold-vscode-guide. Thanks for the inspiration 13 | and help @astrochili. 14 | 15 | MIT License 16 | 17 | Copyright (c) 2021 Roman Silin 18 | 19 | Permission is hereby granted, free of charge, to any person obtaining a copy 20 | of this software and associated documentation files (the "Software"), to deal 21 | in the Software without restriction, including without limitation the rights 22 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 23 | copies of the Software, and to permit persons to whom the Software is 24 | furnished to do so, subject to the following conditions: 25 | 26 | The above copyright notice and this permission notice shall be included in all 27 | copies or substantial portions of the Software. 28 | 29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 30 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 31 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 32 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 33 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 34 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 35 | SOFTWARE. 36 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to the "defold-vscode-build" extension will be documented in this file. 4 | 5 | Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. 6 | 7 | ## [Unreleased] 8 | 9 | ### 0.1.6 - 2021-11-17 10 | - Fixes bug with custom task resolution 11 | 12 | ### 0.1.5 - 2021-10-15 13 | - Better editor path resolution 14 | - Copy dmEngine from local build before attempting to resolve from Editor package 15 | - Handle spaces in paths more gracefully 16 | - More diagnostics and error handling 17 | 18 | ### 0.1.4 - 2021-10-6 19 | - Update problem matcher to catch native extension errors from build 20 | 21 | ### 0.1.3 - 2021-10-6 22 | - Fix run task bug on Windows when project uses native extensions 23 | 24 | ### 0.1.2 - 2021-10-6 25 | - Fix run task on Windows 26 | 27 | ### 0.1.1 - 2021-10-05 28 | - Notifications to remind you to configure the extensions in settings 29 | - Better Default task provider defaults 30 | 31 | ### 0.1.0 - 2021-10-05 32 | - Initial release 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Justin Walsh (@thejustinwalsh) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | VS Code X Defold 3 |

4 | 5 | # Defold Build Tools 6 | Chat with us! 7 | > Build, Run & Package Defold projects from Visual Studio Code 8 | 9 | ## Features 10 | - `build`, `bundle`, `resolve`, `clean`, and `run` 11 | - problemMatchers for task output 12 | - colorized console output for enhanced readability 13 | - sourcemap support for sourcemaps emitted from [TSTL](https://github.com/TypeScriptToLua/TypeScriptToLua) 14 | 15 | ## Requirements 16 | 17 | Install the Defold editor and configure the `defold.editorPath` setting to point to the installation location of the editor. 18 | 19 | ## Quick Start 20 | 21 | Open the command pallette with `⌘ + shift + p` or `ctrl + shift + p` 22 | 23 | `> Tasks: Run Task` - Then selecting `defold`, followed by `build`, `bundle`, `resolve`, `clean`, or `run` will execute the single task with the default task configuration. 24 | 25 | `> Tasks: Configure Default Build Task` - Then selecting `defold`, followed by `build`, `bundle`, `resolve`, `clean`, or `run` will create or update a tasks.json file where you can provide further customization to the task. This will also bind the default task to the build hotkey `⌘ + shift + b` or `ctrl + shift + b` 26 | 27 | You can always fully define your own tasks using any of the tasks that the `defold` task provider provides. 28 | 29 | ```json 30 | { 31 | "type": "defold", 32 | "label": "build", 33 | "detail": "Build the defold game project", 34 | "action": "build", 35 | "configuration": "debug", 36 | "platform": "current", 37 | "group": { 38 | "kind": "build", 39 | "isDefault": true 40 | }, 41 | "presentation": { 42 | "echo": true, 43 | "reveal": "always", 44 | "focus": true, 45 | "panel": "dedicated", 46 | "showReuseMessage": false, 47 | "clear": false 48 | }, 49 | "problemMatcher": [ 50 | "$defold-build" 51 | ], 52 | "dependsOn": [ 53 | "compile" 54 | ], 55 | } 56 | ``` 57 | 58 | ## Extension Settings 59 | 60 | #### Defold 61 | 62 | * `defold.editorPath`: Path to the Defold Editor, will attempt to infer path if this is not set 63 | 64 | #### Build 65 | 66 | * `defold.build.email`: Email address for Bob to use when logging in 67 | * `defold.build.auth`: Auth token for Bob to use when logging in 68 | * `defold.build.textureCompression`: Use texture compression as specified in texture profiles 69 | * `defold.build.withSymbols`: Use symbols when building the project 70 | 71 | #### Bundle 72 | 73 | * `defold.bundle.liveUpdate`: Should LiveUpdate content be published 74 | 75 | #### iOS 76 | 77 | * `defold.bundle.ios.identity`: The name of the iOS signing identity to use when building the project 78 | * `defold.bundle.ios.mobileProvisioningProfilePath`: The path to the mobile provisioning profile to use when building the project 79 | 80 | #### Android 81 | 82 | * `defold.bundle.android.keystore`: The path to the Android keystore to use when building the project 83 | * `defold.bundle.android.keystorePass`: The password for the Android keystore to use when building the project 84 | * `defold.bundle.android.keystoreAlias`: The alias for the Android keystore to use when building the project 85 | * `defold.bundle.android.bundleFormat`: The Android bundle format to use when building the project 86 | 87 | ## Release Notes 88 | 89 | ### 0.1.6 - 2021-11-17 90 | - Fixes bug with custom task resolution 91 | 92 | ### 0.1.5 - 2021-10-15 93 | - Better editor path resolution 94 | - Copy dmEngine from local build before attempting to resolve from Editor package 95 | - Handle spaces in paths more gracefully 96 | - More diagnostics and error handling 97 | 98 | ### 0.1.4 - 2021-10-6 99 | - Update problem matcher to catch native extension errors from build 100 | 101 | ### 0.1.3 - 2021-10-6 102 | - Fix run task bug on Windows when project uses native extensions 103 | 104 | ### 0.1.2 - 2021-10-6 105 | - Fix run task on Windows 106 | 107 | ### 0.1.1 - 2021-10-05 108 | - Notifications to remind you to configure the extensions in settings 109 | - Better Default task provider defaults 110 | 111 | ### 0.1.0 - 2021-10-05 112 | - Initial release 113 | 114 |

115 | TypeScript ❤️ Defold 116 |

117 | -------------------------------------------------------------------------------- /images/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ts-defold/defold-vscode-build/b1befee89916839f31e7c0aa527bdbb3afc7761f/images/header.png -------------------------------------------------------------------------------- /images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ts-defold/defold-vscode-build/b1befee89916839f31e7c0aa527bdbb3afc7761f/images/icon.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "defold-vscode-build", 3 | "displayName": "Defold Build Tools", 4 | "description": "Build, Run & Package Defold Projects", 5 | "version": "0.1.6", 6 | "publisher": "ts-defold", 7 | "author": { 8 | "name": "Justin Walsh " 9 | }, 10 | "license": "MIT", 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/ts-defold/defold-vscode-build.git" 14 | }, 15 | "homepage": "https://github.com/ts-defold/defold-vscode-build/blob/main/README.md", 16 | "keywords": [ 17 | "typescript", 18 | "lua", 19 | "task-provider", 20 | "problem-matcher", 21 | "defold", 22 | "bob", 23 | "build" 24 | ], 25 | "engines": { 26 | "vscode": "^1.60.0" 27 | }, 28 | "categories": [ 29 | "Programming Languages", 30 | "Formatters", 31 | "Other" 32 | ], 33 | "icon": "images/icon.png", 34 | "galleryBanner": { 35 | "color": "#1e2226", 36 | "theme": "dark" 37 | }, 38 | "activationEvents": [ 39 | "onCommand:workbench.action.tasks.runTask", 40 | "workspaceContains:**/game.project" 41 | ], 42 | "main": "./dist/extension.js", 43 | "contributes": { 44 | "configuration": { 45 | "title": "Defold", 46 | "properties": { 47 | "defold.editorPath": { 48 | "type": "string", 49 | "default": "", 50 | "scope": "machine-overridable", 51 | "description": "Full path to the Defold Editor or the Defold Editor installation directory" 52 | }, 53 | "defold.build.email": { 54 | "type": "string", 55 | "default": "someone@acme.com", 56 | "description": "Email address for Bob to use when logging in." 57 | }, 58 | "defold.build.auth": { 59 | "type": "string", 60 | "default": "authtoken!", 61 | "description": "Auth token for Bob to use when logging in." 62 | }, 63 | "defold.build.textureCompression": { 64 | "type": "boolean", 65 | "default": true, 66 | "description": "Use texture compression as specified in texture profiles." 67 | }, 68 | "defold.build.withSymbols": { 69 | "type": "boolean", 70 | "default": true, 71 | "description": "Use symbols when building the project." 72 | }, 73 | "defold.bundle.liveUpdate": { 74 | "type": "boolean", 75 | "default": false, 76 | "description": "Should LiveUpdate content be published." 77 | }, 78 | "defold.bundle.ios.identity": { 79 | "type": "string", 80 | "default": "", 81 | "description": "The name of the iOS signing identity to use when building the project." 82 | }, 83 | "defold.bundle.ios.mobileProvisioningProfilePath": { 84 | "type": "string", 85 | "default": "", 86 | "description": "The path to the mobile provisioning profile to use when building the project." 87 | }, 88 | "defold.bundle.android.keystore": { 89 | "type": "string", 90 | "default": "", 91 | "description": "The path to the Android keystore to use when building the project." 92 | }, 93 | "defold.bundle.android.keystorePass": { 94 | "type": "string", 95 | "default": "", 96 | "description": "The password for the Android keystore to use when building the project." 97 | }, 98 | "defold.bundle.android.keystoreAlias": { 99 | "type": "string", 100 | "default": "", 101 | "description": "The alias for the Android keystore to use when building the project." 102 | }, 103 | "defold.bundle.android.bundleFormat": { 104 | "type": "string", 105 | "default": "apk", 106 | "enum": [ 107 | "apk", 108 | "aab" 109 | ], 110 | "enumDescriptions": [ 111 | "Android Package Format (APK)", 112 | "Android App Bundle Format (AAB)" 113 | ], 114 | "description": "The Android bundle format to use when building the project." 115 | } 116 | } 117 | }, 118 | "taskDefinitions": [ 119 | { 120 | "type": "defold", 121 | "required": [ 122 | "action" 123 | ], 124 | "properties": { 125 | "action": { 126 | "type": "string", 127 | "description": "The defold build task to execute", 128 | "enum": [ 129 | "build", 130 | "bundle", 131 | "clean", 132 | "resolve", 133 | "run" 134 | ], 135 | "enumDescriptions": [ 136 | "Build the project for running, debugging or testing", 137 | "Bundle the project for a specific platform", 138 | "Clean the project output", 139 | "Resolve the project dependencies", 140 | "Run the project" 141 | ] 142 | }, 143 | "configuration": { 144 | "type": "string", 145 | "description": "The configuration to use when building the project", 146 | "default": "debug", 147 | "enum": [ 148 | "debug", 149 | "release" 150 | ], 151 | "enumDescriptions": [ 152 | "Build the project in debug mode", 153 | "Build the project in release mode" 154 | ] 155 | }, 156 | "platform": { 157 | "type": "string", 158 | "description": "The target platform to build for", 159 | "default": "current", 160 | "enum": [ 161 | "current", 162 | "android", 163 | "ios", 164 | "macOS", 165 | "windows", 166 | "linux", 167 | "html5" 168 | ] 169 | } 170 | } 171 | } 172 | ], 173 | "problemPatterns": [ 174 | { 175 | "name": "defold-build-diagnostic", 176 | "regexp": "^(ERROR|WARNING|INFO):? ([a-zA-Z0-9\\\\/\\-\\.]+):(\\d+)(.*)$", 177 | "severity": 1, 178 | "file": 2, 179 | "line": 3, 180 | "column": -1, 181 | "message": 4 182 | }, 183 | { 184 | "name": "defold-run-diagnostic", 185 | "regexp": "^((ERROR|WARNING|INFO):([A-Z]+):)\\s?(?:([a-zA-Z0-9/\\.]+):(\\d+):)? (.*)$", 186 | "severity": 2, 187 | "file": 4, 188 | "line": 5, 189 | "column": -1, 190 | "message": 6 191 | } 192 | ], 193 | "problemMatchers": [ 194 | { 195 | "name": "defold-build", 196 | "owner": "defold", 197 | "source": "build", 198 | "applyTo": "allDocuments", 199 | "fileLocation": [ 200 | "relative", 201 | "${cwd}" 202 | ], 203 | "pattern": "$defold-build-diagnostic" 204 | }, 205 | { 206 | "name": "defold-run", 207 | "owner": "defold", 208 | "source": "build", 209 | "applyTo": "allDocuments", 210 | "fileLocation": [ 211 | "relative", 212 | "${cwd}" 213 | ], 214 | "pattern": "$defold-run-diagnostic" 215 | } 216 | ] 217 | }, 218 | "scripts": { 219 | "vscode:prepublish": "npm run package", 220 | "compile": "webpack", 221 | "watch": "webpack --watch", 222 | "package": "webpack --mode production --devtool hidden-source-map", 223 | "lint": "eslint src --ext ts", 224 | "test": "npm run compile && npm run lint" 225 | }, 226 | "devDependencies": { 227 | "@types/chalk": "^2.2.0", 228 | "@types/glob": "^7.1.4", 229 | "@types/mocha": "^9.0.0", 230 | "@types/node": "14.x", 231 | "@types/vscode": "^1.60.0", 232 | "@typescript-eslint/eslint-plugin": "^4.31.1", 233 | "@typescript-eslint/parser": "^4.31.1", 234 | "@vscode/test-electron": "^1.6.2", 235 | "eslint": "^7.32.0", 236 | "eslint-config-prettier": "^8.3.0", 237 | "eslint-plugin-prettier": "^4.0.0", 238 | "glob": "^7.1.7", 239 | "mocha": "^9.1.1", 240 | "prettier": "^2.4.0", 241 | "ts-loader": "^9.2.5", 242 | "typescript": "^4.4.3", 243 | "webpack": "^5.52.1", 244 | "webpack-cli": "^4.8.0" 245 | }, 246 | "dependencies": { 247 | "chalk": "^4.1.2", 248 | "source-map-js": "^0.6.2" 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { editorPathInfo } from './util/notifications'; 3 | import output from './util/output'; 4 | import { TaskProvider } from './tasks/provider'; 5 | 6 | let taskProvider: vscode.Disposable | undefined; 7 | 8 | export function activate(_context: vscode.ExtensionContext): void { 9 | const workspaceRoot = 10 | vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0 11 | ? vscode.workspace.workspaceFolders[0].uri.fsPath 12 | : undefined; 13 | if (!workspaceRoot) return; 14 | 15 | // Resolve the editor path, and ask the user to provide it, if it's not found 16 | const settings = vscode.workspace.getConfiguration('defold'); 17 | const editorPath = settings.get('editorPath'); 18 | if (!editorPath) editorPathInfo(); 19 | 20 | // Register task provider if we are in a workspace with a game.project file 21 | vscode.workspace.findFiles('**/game.project', '**/node_modules/**', 1).then( 22 | (files) => { 23 | if (files.length > 0) { 24 | if (!taskProvider) 25 | taskProvider = vscode.tasks.registerTaskProvider('defold', new TaskProvider(workspaceRoot, files[0].fsPath)); 26 | } 27 | }, 28 | (_err) => { 29 | output().appendLine('Could not find game.project'); 30 | } 31 | ); 32 | } 33 | 34 | export function deactivate(): void { 35 | if (taskProvider) taskProvider.dispose(); 36 | } 37 | -------------------------------------------------------------------------------- /src/tasks/provider.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | import output from '../util/output'; 4 | import { DefoldTerminal } from './terminal'; 5 | import type { DefoldBuildTaskDefinition } from '../types'; 6 | 7 | export class TaskProvider implements vscode.TaskProvider { 8 | // eslint-disable-next-line @typescript-eslint/naming-convention 9 | static Type = 'defold'; 10 | private tasks: vscode.Task[] | undefined; 11 | 12 | // We use a CustomExecution task when state needs to be shared accross runs of the task or when 13 | // the task requires use of some VS Code API to run. 14 | // If you don't need to share state between runs and if you don't need to execute VS Code API in your task, 15 | // then a simple ShellExecution or ProcessExecution should be enough. 16 | // Since our build has this shared state, the CustomExecution is used below. 17 | private sharedState: string | undefined; 18 | 19 | constructor(private workspaceRoot: string, private project: string) { 20 | //* NOTES: 21 | //* Ensure we have an editor path set in the configuration, or attempt organic detection 22 | //* Parse the config file (Contents/Resources/config) for JDK path, defold jar, and vmargs 23 | //* Build up path to Bob the builder: `/path/to/jdk/bin/java -cp /path/to/defold/jar com.dynamo.bob.Bob` 24 | //* Look for a game.project file in the workspace root or any subfolder 25 | //* Provide tasks for ['clean', 'build', 'bundle', 'resolve'] 26 | //* Run build through PsudeoTerminal and apply sourcemaps to errors / warnings 27 | 28 | const config = vscode.workspace.getConfiguration('defold'); 29 | output().appendLine(`Workspace root: ${this.workspaceRoot}`); 30 | output().appendLine(`Config: ${JSON.stringify(config)}`); 31 | } 32 | 33 | /** 34 | * This is called by vscode when a list of tasks is requested from the command panel 35 | */ 36 | public async provideTasks(): Promise { 37 | if (this.tasks !== undefined) return this.tasks; 38 | 39 | const detail = { 40 | build: 'Build the project for running, debugging or testing', 41 | bundle: 'Bundle the project for a specific platform', 42 | clean: 'Clean the project output', 43 | resolve: 'Resolve the project dependencies', 44 | run: 'Run the project', 45 | }; 46 | this.tasks = ['build', 'bundle', 'clean', 'resolve', 'run'].map((flavor) => { 47 | const definition: DefoldBuildTaskDefinition = { 48 | action: flavor as DefoldBuildTaskDefinition['action'], 49 | type: TaskProvider.Type, 50 | configuration: 'debug', 51 | platform: 'current', 52 | }; 53 | const task = new vscode.Task( 54 | definition, 55 | vscode.TaskScope.Workspace, 56 | flavor, 57 | TaskProvider.Type, 58 | this.createExecution(definition), 59 | flavor === 'run' ? '$defold-run' : '$defold-build' 60 | ); 61 | task.detail = detail[flavor as keyof typeof detail]; 62 | task.presentationOptions = { 63 | reveal: vscode.TaskRevealKind.Always, 64 | echo: true, 65 | focus: true, 66 | panel: vscode.TaskPanelKind.Dedicated, 67 | showReuseMessage: false, 68 | clear: false, 69 | }; 70 | return task; 71 | }); 72 | 73 | return this.tasks; 74 | } 75 | 76 | /** 77 | * This is called by vscode when a task is run from tasks.json 78 | * * This must return the task.definition that is passed in or it will not match 79 | */ 80 | public resolveTask(task: vscode.Task): vscode.Task | undefined { 81 | const definition = task.definition as DefoldBuildTaskDefinition; 82 | const t = new vscode.Task( 83 | definition, 84 | vscode.TaskScope.Workspace, 85 | definition.action, 86 | TaskProvider.Type, 87 | this.createExecution(task.definition as DefoldBuildTaskDefinition), 88 | definition.action === 'run' ? '$defold-run' : '$defold-build' 89 | ); 90 | return t; 91 | } 92 | 93 | private createExecution(definition: DefoldBuildTaskDefinition): vscode.CustomExecution { 94 | return new vscode.CustomExecution(async (resolvedDefinition): Promise => { 95 | return new DefoldTerminal(this.workspaceRoot, this.project, { ...resolvedDefinition, ...definition }); 96 | }); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/tasks/terminal.ts: -------------------------------------------------------------------------------- 1 | import { dirname, join, basename, relative, extname, sep } from 'path'; 2 | import { ChildProcessWithoutNullStreams, spawn, execSync } from 'child_process'; 3 | import { mkdirSync, existsSync, copyFileSync, rmSync, chmodSync, readFileSync, readdirSync } from 'fs'; 4 | import { platform, homedir } from 'os'; 5 | import * as _chalk from 'chalk'; 6 | import * as readline from 'readline'; 7 | import * as vscode from 'vscode'; 8 | import { SourceMapConsumer } from 'source-map-js'; 9 | import type { DefoldBuildTaskDefinition, DefoldTaskEnv, ExtManifest } from '../types'; 10 | import { editorPathError } from '../util/notifications'; 11 | import output from '../util/output'; 12 | 13 | // eslint-disable-next-line @typescript-eslint/no-var-requires 14 | const manifest = require('../../package.json') as ExtManifest; 15 | 16 | const HOST: Record = { 17 | darwin: 'macOS', 18 | win32: 'windows', 19 | cygwin: 'windows', 20 | linux: 'linux', 21 | aix: 'linux', 22 | freebsd: 'linux', 23 | sunos: 'linux', 24 | openbsd: 'linux', 25 | netbsd: 'linux', 26 | android: 'android', 27 | }; 28 | 29 | const PLATFORMS: Record = { 30 | current: '', 31 | android: 'armv7-android', 32 | ios: 'armv7-darwin', 33 | macOS: 'x86_64-darwin', 34 | windows: 'x86_64-win32', 35 | linux: 'x86_64-linux', 36 | html5: 'js-web', 37 | }; 38 | 39 | const OUTPUTS: Record = { 40 | current: '', 41 | android: 'armv7-android', 42 | ios: 'armv7-ios', 43 | macOS: 'x86_64-osx', 44 | windows: 'x86_64-win32', 45 | linux: 'x86_64-linux', 46 | html5: 'js-web', 47 | }; 48 | 49 | function getDefoldTaskEnv(): DefoldTaskEnv { 50 | // Resolve the editor path from the configuration settings 51 | const settings = vscode.workspace.getConfiguration('defold'); 52 | let editorPath = settings.get('editorPath'); 53 | if (!editorPath) { 54 | output().appendLine('The `defold.editorPath` key is empty in the user and workspace settings.'); 55 | return null; 56 | } 57 | 58 | // Resolve ~ in path 59 | if (editorPath && editorPath.startsWith('~')) 60 | editorPath = join(process.env.HOME || homedir() || '', editorPath.slice(1)); 61 | 62 | // Ensure we root the incoming path 63 | if (editorPath.endsWith(sep)) dirname(join(editorPath, '.')); 64 | 65 | // Resolve editor path per platform 66 | switch (platform()) { 67 | case 'win32': { 68 | if (editorPath && extname(editorPath) === '.exe') editorPath = dirname(editorPath); 69 | break; 70 | } 71 | case 'darwin': { 72 | if (editorPath && !editorPath.endsWith('/Contents/Resources')) 73 | editorPath = join(editorPath, 'Contents/Resources'); 74 | break; 75 | } 76 | } 77 | output().appendLine(`Resolved editor path: ${editorPath}`); 78 | 79 | // Check to see if the directory provided is the right shape 80 | let [hasDefold, hasConfig] = ['', '']; 81 | try { 82 | readdirSync(editorPath).forEach((file) => { 83 | if (file.toLowerCase() === 'config') hasConfig = file; 84 | if (file.toLowerCase() === 'packages') { 85 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 86 | readdirSync(join(editorPath!, file)).forEach((file) => { 87 | if (/defold.*\.jar$/i.exec(file) !== null) hasDefold = file; 88 | }); 89 | } 90 | }); 91 | } catch (e) { 92 | const error = e as Error; 93 | output().appendLine(`Unable to read the editor path...`); 94 | output().appendLine(`\t${error.message}`); 95 | return null; 96 | } 97 | 98 | if (!hasDefold || !hasConfig) { 99 | output().appendLine(`Editor path is not the correct shape...`); 100 | output().appendLine(`\thasDefold: "${hasDefold}", hasConfig: "${hasConfig}"`); 101 | return null; 102 | } 103 | 104 | // Parse the Defold Editor config file for the java, jdk, and defold jar 105 | const editorConfigPath = join(editorPath, hasConfig); 106 | const editorConfig = readFileSync(editorConfigPath, 'utf8'); 107 | const lines = editorConfig.split('\n'); 108 | const config: Record = {}; 109 | for (const line of lines) { 110 | const parts = line.split(/\s*=\s*/); 111 | if (parts.length === 2) config[parts[0]] = parts[1]; 112 | } 113 | 114 | let env: DefoldTaskEnv = null; 115 | try { 116 | /* eslint-disable @typescript-eslint/no-non-null-assertion */ 117 | env = { 118 | editorPath, 119 | version: config['version']!, 120 | editorSha1: config['editor_sha1']!, 121 | jdk: join(editorPath, config['jdk']!.replace('${bootstrap.resourcespath}', config['resourcespath'])), 122 | java: config['java']!.replace( 123 | '${launcher.jdk}', 124 | join(editorPath, config['jdk']!.replace('${bootstrap.resourcespath}', config['resourcespath'])) 125 | ), 126 | jar: join( 127 | editorPath, 128 | config['jar']!.replace('${bootstrap.resourcespath}', config['resourcespath']).replace( 129 | '${build.editor_sha1}', 130 | config['editor_sha1'] 131 | ) 132 | ), 133 | }; 134 | /* eslint-enable @typescript-eslint/no-non-null-assertion */ 135 | } catch (e) { 136 | const error = e as Error; 137 | output().appendLine(`Failed to parse editor config file...`); 138 | output().appendLine(`\t${error.message}`); 139 | return null; 140 | } 141 | 142 | return env; 143 | } 144 | 145 | export class DefoldTerminal implements vscode.Pseudoterminal { 146 | private writeEmitter = new vscode.EventEmitter(); 147 | onDidWrite: vscode.Event = this.writeEmitter.event; 148 | 149 | private closeEmitter = new vscode.EventEmitter(); 150 | onDidClose?: vscode.Event = this.closeEmitter.event; 151 | 152 | private process: ChildProcessWithoutNullStreams | null = null; 153 | private sourceMaps: Record = {}; 154 | private chalk = new _chalk.Instance({ level: 3 }); 155 | 156 | constructor( 157 | private workspaceRoot: string, 158 | private project: string, 159 | private definition: DefoldBuildTaskDefinition, 160 | private env = getDefoldTaskEnv() 161 | ) {} 162 | 163 | open(_initialDimensions: vscode.TerminalDimensions | undefined): void { 164 | this.env = this.env ?? getDefoldTaskEnv(); 165 | if (!this.env) { 166 | editorPathError(); 167 | this.writeEmitter.fire(this.chalk.red('Could not find a valid path to the Defold Editor.') + '\r\n'); 168 | this.closeEmitter.fire(1); 169 | return; 170 | } 171 | 172 | //* https://defold.com/manuals/bob/#usage 173 | const config = vscode.workspace.getConfiguration('defold'); 174 | const projectDir = dirname(this.project); 175 | const target = this.definition.platform === 'current' ? HOST[platform()] : this.definition.platform; 176 | const java = `${this.env.java}`; 177 | const required = [ 178 | `-cp`, 179 | `${this.env.jar}`, 180 | `com.dynamo.bob.Bob`, 181 | `-i`, 182 | `"${projectDir}"`, 183 | `-r`, 184 | `"${projectDir}"`, 185 | `--exclude-build-folder`, 186 | `.git, build`, 187 | ]; 188 | 189 | let exec = java; 190 | let options: string[] = []; 191 | let commands: string[] = []; 192 | let cwd = this.workspaceRoot; 193 | 194 | switch (this.definition.action) { 195 | case 'build': 196 | { 197 | options = [ 198 | ...required, 199 | `-e`, 200 | `${config.get('build.email') ?? ''}`, 201 | `-u`, 202 | `${config.get('build.auth') ?? ''}`, 203 | `-tc`, 204 | `${config.get('build.textureCompression', false) ? 'true' : 'false'}`, 205 | `--variant`, 206 | `${this.definition.configuration}`, 207 | ]; 208 | if (config.get('build.withSymbols', false)) options.push(`--with-symbols`); 209 | if (this.definition.configuration === 'release') options.push(`--strip-executable`); 210 | 211 | commands = [`resolve`, `build`]; 212 | } 213 | break; 214 | case 'bundle': 215 | { 216 | const out = join(this.workspaceRoot, 'bundle', OUTPUTS[target]); 217 | try { 218 | mkdirSync(out, { recursive: true }); 219 | } catch (e) { 220 | /* ignore */ 221 | } 222 | 223 | options = [ 224 | ...required, 225 | `-e`, 226 | `${config.get('build.email') ?? ''}`, 227 | `-u`, 228 | `${config.get('build.auth') ?? ''}`, 229 | `-a`, 230 | `-p`, 231 | `${PLATFORMS[target]}`, 232 | `-bo`, 233 | `"${out}"`, 234 | `-brhtml`, 235 | `"${join(out, 'build-report.html')}"`, 236 | `--variant`, 237 | `${this.definition.configuration}`, 238 | ]; 239 | if (config.get('build.withSymbols', false)) options.push(`--with-symbols`); 240 | if (this.definition.configuration === 'release') options.push(`--strip-executable`); 241 | if (config.get('bundle.liveUpdate', false)) options.push(`-l`, `yes`); 242 | 243 | // Mobile bundle options 244 | if (target === 'ios') { 245 | const identity = config.get('bundle.ios.identity', ''); 246 | const mobileProvisioningProfilePath = config.get('bundle.ios.mobileProvisioningProfilePath', ''); 247 | if (identity) options.push(`--identity`, identity); 248 | if (mobileProvisioningProfilePath) options.push(`-mp`, `"${mobileProvisioningProfilePath}"`); 249 | } else if (target === 'android') { 250 | const keystore = config.get('bundle.android.keystore', ''); 251 | const keystorePassword = config.get('bundle.android.keystorePass', ''); 252 | const keystoreAlias = config.get('bundle.android.keystoreAlias', ''); 253 | const bundleFormat = config.get('bundle.android.bundleFormat', 'apk'); 254 | if (keystore) options.push(`--keystore`, `"${keystore}"`); 255 | if (keystorePassword) options.push(`--keystore-pass`, keystorePassword); 256 | if (keystoreAlias) options.push(`--keystore-alias`, keystoreAlias); 257 | if (bundleFormat) options.push(`--bundle-format`, bundleFormat); 258 | } 259 | 260 | commands = [`resolve`, `distclean`, `build`, `bundle`]; 261 | } 262 | break; 263 | case 'clean': 264 | { 265 | options = [...required]; 266 | commands = [`distclean`]; 267 | } 268 | break; 269 | case 'resolve': 270 | { 271 | options = [ 272 | ...required, 273 | `-e`, 274 | `${config.get('build.email') ?? ''}`, 275 | `-u`, 276 | `${config.get('build.auth') ?? ''}`, 277 | ]; 278 | commands = [`resolve`]; 279 | } 280 | break; 281 | case 'run': 282 | { 283 | cwd = join(projectDir, 'build', 'default'); 284 | exec = join(cwd, platform() === 'win32' ? 'dmengine.exe' : 'dmengine'); 285 | options = []; 286 | commands = ['./game.projectc']; 287 | 288 | if (!existsSync(join(cwd, 'game.projectc'))) { 289 | this.writeEmitter.fire( 290 | this.chalk.yellow(`Missing 'game.projectc'. Did you forget to build before running?`) + '\r\n' 291 | ); 292 | this.closeEmitter.fire(1); 293 | return; 294 | } 295 | } 296 | break; 297 | } 298 | 299 | // Run Prelaunch deps 300 | this.preLaunch(); 301 | 302 | // Execute the command 303 | // TODO: ENV variables - https://github.com/defold/defold/blob/ef879961c127c1b1e533b87ce60423387f1ef190/editor/src/clj/editor/engine.clj#L269 304 | this.exec(exec, [...options, ...commands], cwd); 305 | } 306 | 307 | close(): void { 308 | this.process?.kill(); 309 | } 310 | 311 | private preLaunch() { 312 | output().appendLine(`Pre-Launch...`); 313 | 314 | switch (this.definition.action) { 315 | case 'run': 316 | this.ensureEngine(); 317 | break; 318 | } 319 | } 320 | 321 | private ensureEngine() { 322 | if (!this.env) return; 323 | 324 | const projectDir = dirname(this.project); 325 | const jar = join(this.env.jdk, 'bin', 'jar'); 326 | 327 | let deps: string[] = []; 328 | switch (platform()) { 329 | case 'darwin': 330 | deps = ['_unpack/x86_64-darwin/bin/dmengine']; 331 | break; 332 | case 'win32': 333 | deps = [ 334 | '_unpack/x86_64-win32/bin/dmengine.exe', 335 | `_unpack/x86_64-win32/bin/OpenAL32.dll`, 336 | `_unpack/x86_64-win32/bin/wrap_oal.dll`, 337 | ]; 338 | break; 339 | default: 340 | deps = ['_unpack/x86_64-linux/bin/dmengine']; 341 | break; 342 | } 343 | 344 | output().appendLine(`Copying Dependencies...`); 345 | 346 | // Copy the prebuilt dmengine and dependencies to the project directory first 347 | const hostBuildDir = join(projectDir, 'build', OUTPUTS[HOST[platform()]]); 348 | try { 349 | readdirSync(hostBuildDir).forEach((file) => { 350 | const depIndex = deps.findIndex((dep) => basename(dep) === file); 351 | 352 | // Filter files to copy 353 | let target = ''; 354 | if (depIndex !== -1) { 355 | target = file; 356 | deps.splice(depIndex, 1); 357 | } else if (['.pdb'].includes(extname(file))) { 358 | // TODO: copy dSYM folder 359 | target = file; 360 | } 361 | 362 | // Copy file 363 | if (target) { 364 | const src = join(hostBuildDir, file); 365 | const dest = join(projectDir, 'build', 'default', file); 366 | copyFileSync(src, dest); 367 | if (file.startsWith('dmengine')) chmodSync(dest, '755'); 368 | output().appendLine(`-> ${file}`); 369 | } 370 | }); 371 | } catch (e) { 372 | const error = e as Error; 373 | output().appendLine(`Failed to copy dependencies...`); 374 | output().appendLine(`\t${error.message}`); 375 | } 376 | 377 | // Extract remaining dependencies from the engine archive 378 | const out = join(projectDir, 'build', 'default'); 379 | const path = deps[0]; 380 | const archive = this.env.jar; 381 | 382 | const required = deps.filter((dep) => !existsSync(join(out, basename(dep)))); 383 | if (required.length > 0) { 384 | required.forEach((dep) => { 385 | execSync(`${jar} -xf "${archive}" "${dep}"`, { cwd: out }); 386 | copyFileSync(join(out, dep), join(out, basename(dep))); 387 | output().appendLine(`-> ${basename(dep)}`); 388 | }); 389 | 390 | rmSync(join(out, '_unpack'), { recursive: true, force: true }); 391 | chmodSync(join(out, basename(path)), '755'); 392 | } 393 | } 394 | 395 | private exec(command: string, args: string[], cwd?: string): void { 396 | // Spawn the incoming process 397 | output().appendLine(`Execute: ${command} ${args.join(' ')}`); 398 | this.process = spawn(command, args, { cwd: cwd ?? this.workspaceRoot }); 399 | 400 | // Handle the process output 401 | const stdout = readline.createInterface({ input: this.process.stdout, historySize: 0 }); 402 | stdout.on('line', (line) => this.decorateAndEmit('stdout', line.trim())); 403 | 404 | // Handle the process error 405 | const stderr = readline.createInterface({ input: this.process.stderr, historySize: 0 }); 406 | stderr.on('line', (line) => this.decorateAndEmit('stderr', line.trim())); 407 | 408 | // Handle the process exit 409 | this.process.on('close', (code) => { 410 | this.closeEmitter.fire(code ?? 0); 411 | }); 412 | } 413 | 414 | private decorateAndEmit(src: 'stdout' | 'stderr', line: string) { 415 | const write = (line: string) => this.writeEmitter.fire(line + '\r\n'); 416 | 417 | for (const problemPattern of manifest.contributes.problemPatterns) { 418 | const regex = new RegExp(problemPattern.regexp); 419 | const match = regex.exec(line); 420 | if (match) { 421 | // Apply diagnostics to our output if it matches a problemPattern 422 | switch (problemPattern.name) { 423 | case 'defold-run-diagnostic': 424 | case 'defold-build-diagnostic': { 425 | const severity = match[problemPattern.severity]; 426 | switch (severity.toLowerCase()) { 427 | case 'error': { 428 | const lineNum = match[problemPattern.line]; 429 | const file = match[problemPattern.file]; 430 | const filePath = join(dirname(this.project), file); 431 | 432 | // Apply sourcemaps and path remapping 433 | let remapped = false; 434 | if (parseInt(lineNum) > 0 && file) { 435 | const sourceMapPath = `${filePath}.map`; 436 | if (existsSync(sourceMapPath)) { 437 | let sourceMap = this.sourceMaps[filePath]; 438 | if (!sourceMap) { 439 | sourceMap = this.sourceMaps[filePath] = new SourceMapConsumer( 440 | JSON.parse(readFileSync(sourceMapPath).toString()) 441 | ); 442 | } 443 | 444 | const res = sourceMap.originalPositionFor({ 445 | line: parseInt(lineNum), 446 | column: 0, 447 | }); 448 | if (res.source) { 449 | line = line.replace( 450 | `${file}:${lineNum}`, 451 | `${relative(this.workspaceRoot, res.source)}:${res.line}` 452 | ); 453 | remapped = true; 454 | } 455 | } 456 | } 457 | if (!remapped) line = line.replace(file, relative(this.workspaceRoot, filePath)); 458 | 459 | write(this.chalk.red(match[1]) + this.chalk.white(line.split(match[1])[1])); 460 | break; 461 | } 462 | case 'warning': 463 | write(this.chalk.yellow(match[1]) + this.chalk.white(line.split(match[1])[1])); 464 | break; 465 | case 'info': 466 | write(this.chalk.blueBright(match[1]) + this.chalk.white(line.split(match[1])[1])); 467 | break; 468 | } 469 | return; 470 | } 471 | } 472 | } 473 | } 474 | 475 | // default 476 | write(this.definition.action !== 'run' && src === 'stderr' ? this.chalk.red(line) : this.chalk.white(line)); 477 | } 478 | } 479 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | import type { TaskDefinition } from 'vscode'; 2 | 3 | export type DefoldTaskEnv = { 4 | editorPath: string; 5 | version: string; 6 | editorSha1: string; 7 | jdk: string; 8 | java: string; 9 | jar: string; 10 | } | null; 11 | 12 | export interface DefoldBuildTaskDefinition extends TaskDefinition { 13 | action: 'build' | 'bundle' | 'clean' | 'resolve' | 'run'; 14 | configuration: 'debug' | 'release'; 15 | platform: 'current' | 'android' | 'ios' | 'macOS' | 'windows' | 'linux' | 'html5'; 16 | } 17 | 18 | export type ExtManifest = { 19 | contributes: { 20 | problemPatterns: { 21 | name: string; 22 | regexp: string; 23 | severity: number; 24 | file: number; 25 | line: number; 26 | message: number; 27 | }[]; 28 | }; 29 | }; 30 | -------------------------------------------------------------------------------- /src/util/notifications.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | export function editorPathInfo(): void { 4 | void vscode.window 5 | .showInformationMessage('Please configure the path to the Defold Editor in your user or workspace settings', { 6 | title: 'Open Settings', 7 | id: 'settings', 8 | }) 9 | .then((result) => { 10 | if (result?.id === 'settings') 11 | void vscode.commands.executeCommand('workbench.action.openSettings', 'defold.editorPath'); 12 | }); 13 | } 14 | 15 | export function editorPathError(): void { 16 | void vscode.window 17 | .showErrorMessage( 18 | 'The Defold Editor path can not be determined! Check the path to make sure it exists and is configured in your user or workspace settings.', 19 | { 20 | title: 'Open Settings', 21 | id: 'settings', 22 | } 23 | ) 24 | .then((result) => { 25 | if (result?.id === 'settings') 26 | void vscode.commands.executeCommand('workbench.action.openSettings', 'defold.editorPath'); 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /src/util/output.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | let _channel: vscode.OutputChannel; 4 | 5 | export default function get(): vscode.OutputChannel { 6 | if (!_channel) _channel = vscode.window.createOutputChannel('Defold Build'); 7 | 8 | return _channel; 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "ES2020", 5 | "lib": [ 6 | "ES2020" 7 | ], 8 | "rootDir": "src", 9 | "strict": true, /* enable all strict type-checking options */ 10 | /* Additional Checks */ 11 | "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 12 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 13 | "noUnusedParameters": true, /* Report errors on unused parameters. */ 14 | "sourceMap": true, 15 | }, 16 | "exclude": [ 17 | "node_modules", 18 | ".vscode-test" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /vsc-extension-quickstart.md: -------------------------------------------------------------------------------- 1 | # Welcome to your VS Code Extension 2 | 3 | ## What's in the folder 4 | 5 | * This folder contains all of the files necessary for your extension. 6 | * `package.json` - this is the manifest file in which you declare your extension and command. 7 | * The sample plugin registers a command and defines its title and command name. With this information VS Code can show the command in the command palette. It doesn’t yet need to load the plugin. 8 | * `src/extension.ts` - this is the main file where you will provide the implementation of your command. 9 | * The file exports one function, `activate`, which is called the very first time your extension is activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`. 10 | * We pass the function containing the implementation of the command as the second parameter to `registerCommand`. 11 | 12 | ## Get up and running straight away 13 | 14 | * Press `F5` to open a new window with your extension loaded. 15 | * Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`. 16 | * Set breakpoints in your code inside `src/extension.ts` to debug your extension. 17 | * Find output from your extension in the debug console. 18 | 19 | ## Make changes 20 | 21 | * You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`. 22 | * You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes. 23 | 24 | 25 | ## Explore the API 26 | 27 | * You can open the full set of our API when you open the file `node_modules/@types/vscode/index.d.ts`. 28 | 29 | ## Run tests 30 | 31 | * Open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Extension Tests`. 32 | * Press `F5` to run the tests in a new window with your extension loaded. 33 | * See the output of the test result in the debug console. 34 | * Make changes to `src/test/suite/extension.test.ts` or create new test files inside the `test/suite` folder. 35 | * The provided test runner will only consider files matching the name pattern `**.test.ts`. 36 | * You can create folders inside the `test` folder to structure your tests any way you want. 37 | 38 | ## Go further 39 | 40 | * Reduce the extension size and improve the startup time by [bundling your extension](https://code.visualstudio.com/api/working-with-extensions/bundling-extension). 41 | * [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VSCode extension marketplace. 42 | * Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration). 43 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | 3 | 'use strict'; 4 | 5 | const path = require('path'); 6 | 7 | //@ts-check 8 | /** @typedef {import('webpack').Configuration} WebpackConfig **/ 9 | 10 | /** @type WebpackConfig */ 11 | const extensionConfig = { 12 | target: 'node', // vscode extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/ 13 | mode: 'none', // this leaves the source code as close as possible to the original (when packaging we set this to 'production') 14 | 15 | entry: './src/extension.ts', // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/ 16 | output: { 17 | // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/ 18 | path: path.resolve(__dirname, 'dist'), 19 | filename: 'extension.js', 20 | libraryTarget: 'commonjs2' 21 | }, 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 | // modules added here also need to be added in the .vsceignore file 25 | }, 26 | resolve: { 27 | // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader 28 | extensions: ['.ts', '.js'] 29 | }, 30 | module: { 31 | rules: [ 32 | { 33 | test: /\.ts$/, 34 | exclude: /node_modules/, 35 | use: [ 36 | { 37 | loader: 'ts-loader' 38 | } 39 | ] 40 | } 41 | ] 42 | }, 43 | devtool: 'nosources-source-map' 44 | }; 45 | module.exports = [ extensionConfig ]; --------------------------------------------------------------------------------