├── .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 |
3 |
4 |
5 | # Defold Build Tools
6 |
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 ];
--------------------------------------------------------------------------------