* added prettier
* formatted project

Co-authored-by: JiglioNero <smouk.chayz@gmail.com>
Co-authored-by: Samriel <andrejlobanovic@gmail.com>
This commit is contained in:
AndreiLobanovich 2022-06-22 15:32:48 +03:00 коммит произвёл GitHub
Родитель 69e89ba81c
Коммит 30ddb91683
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
67 изменённых файлов: 8364 добавлений и 3538 удалений

Просмотреть файл

@ -1,62 +1,210 @@
const prettierConfig = require("./package.json").prettier;
const isFix = process.argv.includes("--fix");
module.exports = {
ignorePatterns: [
"**/*.d.ts",
"**/*.js",
],
parser: "@typescript-eslint/parser",
extends: [
"plugin:@typescript-eslint/recommended"
],
parserOptions: {
ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features
sourceType: "module", // Allows for the use of imports
root: true,
ignorePatterns: ["**/*.d.ts", "**/*.js"],
env: {
node: true,
browser: false,
es2020: true,
},
plugins: [
"header"
parserOptions: {
ecmaVersion: 2020,
parser: require.resolve("@typescript-eslint/parser"),
project: "./tsconfig.json",
sourceType: "module",
tsconfigRootDir: __dirname,
},
plugins: ["@typescript-eslint", "prettier", "import", "promise", "unicorn", "header"],
extends: [
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking",
"plugin:promise/recommended",
"plugin:import/warnings",
"plugin:import/errors",
"plugin:import/typescript",
"prettier",
"prettier/@typescript-eslint",
],
settings: {
"import/resolver": {
[require.resolve("eslint-import-resolver-typescript")]: {},
[require.resolve("eslint-import-resolver-node")]: {},
},
},
overrides: [],
rules: {
// before adding new rules - https://github.com/prettier/eslint-plugin-prettier/issues/65
"quotes": "off",
"semi": "off",
"@typescript-eslint/semi": ["error"],
"no-void": "off",
"no-promise-executor-return": "warn",
"@typescript-eslint/quotes": ["error", "double"],
"@typescript-eslint/no-use-before-define": "warn",
"@typescript-eslint/await-thenable": "warn",
"@typescript-eslint/dot-notation": "warn",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/interface-name-prefix": "off",
"@typescript-eslint/prefer-namespace-keyword": "off",
"@typescript-eslint/restrict-plus-operands": "off",
"@typescript-eslint/explicit-module-boundary-types": [
"warn",
{
allowArgumentsExplicitlyTypedAsAny: true,
},
],
"@typescript-eslint/lines-between-class-members": "off",
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-inferrable-types": "off",
"@typescript-eslint/consistent-type-assertions": "off",
"@typescript-eslint/no-empty-function": "warn",
"@typescript-eslint/no-empty-interface": "warn",
"@typescript-eslint/no-var-requires": "off",
"@typescript-eslint/no-inferrable-types": [
"warn",
{
ignoreParameters: true,
ignoreProperties: true,
},
],
"@typescript-eslint/no-floating-promises": "off",
"@typescript-eslint/triple-slash-reference": "off",
"@typescript-eslint/restrict-template-expressions": "off",
"@typescript-eslint/no-var-requires": "off",
"@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/no-misused-promises": "off",
"@typescript-eslint/no-namespace": "off",
"@typescript-eslint/member-delimiter-style": ["error", {
multiline: {
delimiter: 'comma',
requireLast: true,
"@typescript-eslint/no-shadow": "off",
"@typescript-eslint/no-unnecessary-type-assertion": "off",
"@typescript-eslint/no-unsafe-assignment": "off",
"@typescript-eslint/no-unsafe-call": "off",
"@typescript-eslint/unbound-method": "off",
"@typescript-eslint/no-unsafe-member-access": "off",
"@typescript-eslint/no-unsafe-return": "off",
"@typescript-eslint/no-use-before-define": [
// function hoisting is a common, accepted pattern
"error",
{
classes: true,
functions: false,
typedefs: true,
variables: true,
},
singleline: {
delimiter: 'comma',
requireLast: false,
},
overrides: {
interface: {
multiline: {
delimiter: "semi",
requireLast: true
}
}
}
}],
"eol-last": "error",
"prefer-const": "off",
"no-trailing-spaces": "error",
],
"@typescript-eslint/prefer-regexp-exec": "off",
"@typescript-eslint/require-await": "off",
"class-methods-use-this": "off",
"consistent-return": "off",
"header/header": [
"error",
"line",
[" Copyright (c) Microsoft Corporation. All rights reserved.", " Licensed under the MIT license. See LICENSE file in the project root for details."],
[
" Copyright (c) Microsoft Corporation. All rights reserved.",
" Licensed under the MIT license. See LICENSE file in the project root for details.",
],
],
// Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs
// e.g. "@typescript-eslint/explicit-function-return-type": "off",
"import/extensions": [
"error",
"always",
{
js: "never",
jsx: "never",
mjs: "never",
ts: "never",
tsx: "never",
},
],
"import/newline-after-import": "warn",
"import/no-cycle": "off",
"import/no-extraneous-dependencies": "off",
"import/no-mutable-exports": "off",
"import/no-require": "off",
"import/no-unresolved": "off",
"import/no-useless-path-segments": "warn",
"import/order": "warn",
"import/prefer-default-export": "off",
"linebreak-style": "off",
"lines-between-class-members": "off",
"max-classes-per-file": "off",
"no-async-promise-executor": "warn",
"no-await-in-loop": "warn",
"no-else-return": "warn",
"no-empty-function": "off",
"no-extra-boolean-cast": "warn",
"no-lonely-if": "warn",
"no-nested-ternary": "warn",
"no-param-reassign": "warn",
"no-plusplus": "off",
"no-promise-executor-return": "error",
"no-restricted-globals": "warn",
"no-restricted-syntax": [
"error",
{
message:
"for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array.",
selector: "ForInStatement",
},
{
message:
"Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand.",
selector: "LabeledStatement",
},
{
message:
"`with` is disallowed in strict mode because it makes code impossible to predict and optimize.",
selector: "WithStatement",
},
],
"no-shadow": "off",
"no-undef-init": "error",
"no-underscore-dangle": "off",
"no-unneeded-ternary": "warn",
"no-useless-computed-key": "error",
"no-useless-escape": "warn",
"no-useless-return": "off",
"no-void": [
"error",
{
allowAsStatement: true,
},
],
"object-shorthand": "warn",
"prefer-destructuring": "off",
"prefer-template": "error",
"prettier/prettier": ["error", prettierConfig],
"promise/always-return": "off",
"promise/catch-or-return": "off",
"promise/param-names": "off",
"promise/valid-params": "warn",
"spaced-comment": [
"error",
"always",
{
markers: ["/"],
},
],
"unicorn/better-regex": "warn",
"unicorn/filename-case": [
"warn",
{
cases: {
camelCase: true, // pascalCase: true,
},
ignore: [/rn-extension\.ts/],
},
],
"unicorn/no-array-reduce": "warn",
"unicorn/no-for-loop": isFix ? "off" : "warn",
"unicorn/no-instanceof-array": "warn",
"unicorn/no-new-array": "warn",
"unicorn/no-new-buffer": "warn",
"unicorn/prefer-array-find": "warn",
"unicorn/prefer-array-index-of": "warn",
"unicorn/prefer-array-some": "warn",
"unicorn/prefer-includes": "warn",
"unicorn/prefer-ternary": isFix ? "off" : "warn",
"use-isnan": "warn",
"valid-typeof": "warn",
yoda: "warn",
},
globals: {
process: "readonly",
},
};

Просмотреть файл

@ -41,36 +41,36 @@ You can modify these configurations or add new ones to the list. Just don't add
You can use other fields in these configurations as well. Here's the complete list:
| Name | Description | Defaults |
| ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `port` | The port number that the debugger uses to connect to a device or emulator.<br>**Type:** `number` | 9222 |
| `platform` | The target platform to run for (either `ios`, `android`, `browser` or `serve`; other platforms are not currently supported).<br>**Type:** `string` | n/a |
| Name | Description | Defaults |
| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `port` | The port number that the debugger uses to connect to a device or emulator.<br>**Type:** `number` | 9222 |
| `platform` | The target platform to run for (either `ios`, `android`, `browser` or `serve`; other platforms are not currently supported).<br>**Type:** `string` | n/a |
| `target` | Target to run on. Possible values: `emulator`, `device`, `<name or id of your target emulator or device>`,. For simulation in the browser, you can use `chrome`, `edge`. If the value is `emulator` the quick pick window will be expanded with the names of the available Android virtual devices (for iOS virtual targets the list of available system versions of the target will be shown first, and after that another quick pick window will be expanded with the list of iOS simulators names with the selected version of the system), then the target value in `launch.json` will be changed to the AVD name/iOS Simulator udid of the selected virtual device. If you have only one virtual device available, it will be selected automatically.<br>_Note: If you're using Android emulator targets, please, make sure the `emulator` utility is added to `PATH`._<br>**Type:** `string` | emulator |
| `trace` | Trace may be set to `true` to print diagnostic logs of the extension to the console and write diagnostic logs of the Javascript debugger to the disk.<br>**Type:** `boolean` | true |
| `sourceMaps` | Set this field to `true` if you want the debugger to use javascript sourcemaps (if they exist).<br>**Type:** `boolean` | false |
| `sourceMapPathOverrides` | A set of mappings for rewriting the locations of source files from what the sourcemap says, to their locations on disk. <br>**Type:** `object` | {<br>`"webpack:///./*": "${cwd}/*",`<br>`"webpack:///src/*": "${cwd}/*",`<br>`"webpack:///*": "*",`<br>`"webpack:///./~/*": "${cwd}/node_modules/*"`,<br>`"./*": "${cwd}/*"`<br>} |
| `webkitRangeMin`, `webkitRangeMax` | Combines to specify the port range that you want the debugger to use to find the specific device or simulator described in the configuration.<br>**Type:** `number` | 9223, 9322 |
| `attachAttempts` | The maximum number of times that you want the debugger to attempt to attach to a running iOS app.<br>**Type:** `number` | 5 |
| `attachDelay` | The time in milliseconds between each attempt to attach to a running iOS application.<br>**Type:** `number` | 1000 |
| `attachTimeout` | Time in milliseconds to wait before the debugger is attached to the debug session.<br>**Type:** `number` | 10000 |
| `iosDebugProxyPort` | The port number that you want the debugger to use when it launches iOS applications on a device.<br>**Type:** `number` | 9221 |
| `ionicLiveReload` | Set to true to enable Ionic live reload.<br>**Type:** `boolean` | false |
| `devServerAddress` | For Ionic live reload scenario specify the IP address the device can use to contact the Ionic server.<br>**Type:** `string` | n/a |
| `devServerPort` | For Ionic live reload scenario specify the port the device can use to contact the Ionic server.<br>**Type:** `number` | n/a |
| `devServerTimeout` | Timeout in milliseconds for starting the Ionic dev server when serving to the browser or running with Ionic live reload enabled.<br>**Type:** `number` | 20000 |
| `simulatePort` | Port to use for connecting to the local Cordova Simulate server.<br>**Type:** `number` | 8000 |
| `livereload` | When simulating in the browser, determines whether live reload is enabled.<br>**Type:** `boolean` | true |
| `forcePrepare` | When simulating in the browser, determines whether a file change triggers a cordova prepare before live reloading.<br>**Type:** `boolean` | false |
| `corsProxy` | When simulating in the browser, determines whether XHR requests are proxied to appear as though they originate from the same domain as the target.<br>**Type:** `boolean` | true |
| `livereloadDelay` | When simulating in the browser, set the delay in milliseconds between saving of a modified file and the application page reloading.<br>**Type:** `number` | 200 |
| `simulateTempDir` | The directory where temporary browser simulation files are hosted.<br>**Type:** `string` | `${workspaceRoot}`/.vscode/simulation |
| `runArguments` | Run arguments (array) to be passed to `cordova run/build <platform>` or `ionic serve` command (Override all other configuration params).<br>**Type:** `array` | n/a |
| `cordovaExecutable` | The path to the local Cordova/Ionic executable. For example:<ul><li>macOS - `./node_modules/.bin/cordova`</li><li>Windows - `.\\node_modules\\.bin\\cordova.cmd`</li></ul>**Type:** `string` | n/a |
| `env` | Environment variables passed to the program.<br>**Type:** `object` | n/a |
| `envFile` | Absolute path to a file containing environment variable definitions.<br>**Type:** `string` | `${workspaceFolder}/.env` |
| `skipFiles` | An array of file or folder names, or path globs, to skip when debugging.<br>**Type:** `array` | [ ] |
| `pathMapping` | A mapping of URLs/paths to local folders, to resolve scripts in app webroot to scripts on disk.<br>**Type:** `object` | {<br>`"/": "${workspaceFolder}"`<br>} |
| `runtimeVersion` | If `nvm` (or `nvm-windows`) or `nvs` is used for managing Node.js versions, this attribute can be used to select a specific version of Node.js.<br>**Type:** `string` | n/a |
| `trace` | Trace may be set to `true` to print diagnostic logs of the extension to the console and write diagnostic logs of the Javascript debugger to the disk.<br>**Type:** `boolean` | true |
| `sourceMaps` | Set this field to `true` if you want the debugger to use javascript sourcemaps (if they exist).<br>**Type:** `boolean` | false |
| `sourceMapPathOverrides` | A set of mappings for rewriting the locations of source files from what the sourcemap says, to their locations on disk. <br>**Type:** `object` | {<br>`"webpack:///./*": "${cwd}/*",`<br>`"webpack:///src/*": "${cwd}/*",`<br>`"webpack:///*": "*",`<br>`"webpack:///./~/*": "${cwd}/node_modules/*"`,<br>`"./*": "${cwd}/*"`<br>} |
| `webkitRangeMin`, `webkitRangeMax` | Combines to specify the port range that you want the debugger to use to find the specific device or simulator described in the configuration.<br>**Type:** `number` | 9223, 9322 |
| `attachAttempts` | The maximum number of times that you want the debugger to attempt to attach to a running iOS app.<br>**Type:** `number` | 5 |
| `attachDelay` | The time in milliseconds between each attempt to attach to a running iOS application.<br>**Type:** `number` | 1000 |
| `attachTimeout` | Time in milliseconds to wait before the debugger is attached to the debug session.<br>**Type:** `number` | 10000 |
| `iosDebugProxyPort` | The port number that you want the debugger to use when it launches iOS applications on a device.<br>**Type:** `number` | 9221 |
| `ionicLiveReload` | Set to true to enable Ionic live reload.<br>**Type:** `boolean` | false |
| `devServerAddress` | For Ionic live reload scenario specify the IP address the device can use to contact the Ionic server.<br>**Type:** `string` | n/a |
| `devServerPort` | For Ionic live reload scenario specify the port the device can use to contact the Ionic server.<br>**Type:** `number` | n/a |
| `devServerTimeout` | Timeout in milliseconds for starting the Ionic dev server when serving to the browser or running with Ionic live reload enabled.<br>**Type:** `number` | 20000 |
| `simulatePort` | Port to use for connecting to the local Cordova Simulate server.<br>**Type:** `number` | 8000 |
| `livereload` | When simulating in the browser, determines whether live reload is enabled.<br>**Type:** `boolean` | true |
| `forcePrepare` | When simulating in the browser, determines whether a file change triggers a cordova prepare before live reloading.<br>**Type:** `boolean` | false |
| `corsProxy` | When simulating in the browser, determines whether XHR requests are proxied to appear as though they originate from the same domain as the target.<br>**Type:** `boolean` | true |
| `livereloadDelay` | When simulating in the browser, set the delay in milliseconds between saving of a modified file and the application page reloading.<br>**Type:** `number` | 200 |
| `simulateTempDir` | The directory where temporary browser simulation files are hosted.<br>**Type:** `string` | `${workspaceRoot}`/.vscode/simulation |
| `runArguments` | Run arguments (array) to be passed to `cordova run/build <platform>` or `ionic serve` command (Override all other configuration params).<br>**Type:** `array` | n/a |
| `cordovaExecutable` | The path to the local Cordova/Ionic executable. For example:<ul><li>macOS - `./node_modules/.bin/cordova`</li><li>Windows - `.\\node_modules\\.bin\\cordova.cmd`</li></ul>**Type:** `string` | n/a |
| `env` | Environment variables passed to the program.<br>**Type:** `object` | n/a |
| `envFile` | Absolute path to a file containing environment variable definitions.<br>**Type:** `string` | `${workspaceFolder}/.env` |
| `skipFiles` | An array of file or folder names, or path globs, to skip when debugging.<br>**Type:** `array` | [ ] |
| `pathMapping` | A mapping of URLs/paths to local folders, to resolve scripts in app webroot to scripts on disk.<br>**Type:** `object` | {<br>`"/": "${workspaceFolder}"`<br>} |
| `runtimeVersion` | If `nvm` (or `nvm-windows`) or `nvs` is used for managing Node.js versions, this attribute can be used to select a specific version of Node.js.<br>**Type:** `string` | n/a |
## Debug your Cordova-based project

Просмотреть файл

@ -12,19 +12,19 @@ If you believe you have found a security vulnerability in any Microsoft-owned re
Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report).
If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey).
If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey).
You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc).
You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc).
Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
* Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
* Full paths of source file(s) related to the manifestation of the issue
* The location of the affected source code (tag/branch/commit or direct URL)
* Any special configuration required to reproduce the issue
* Step-by-step instructions to reproduce the issue
* Proof-of-concept or exploit code (if possible)
* Impact of the issue, including how an attacker might exploit the issue
- Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
- Full paths of source file(s) related to the manifestation of the issue
- The location of the affected source code (tag/branch/commit or direct URL)
- Any special configuration required to reproduce the issue
- Step-by-step instructions to reproduce the issue
- Proof-of-concept or exploit code (if possible)
- Impact of the issue, including how an attacker might exploit the issue
This information will help us triage your report more quickly.

Разница между файлами не показана из-за своего большого размера Загрузить разницу

2831
package-lock.json сгенерированный

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -643,13 +643,33 @@
"test": "node ./test/runTest.js",
"vscode:prepublish": "gulp"
},
"prettier": {
"trailingComma": "all",
"arrowParens": "avoid",
"printWidth": 100,
"tabWidth": 4,
"endOfLine": "auto",
"overrides": [
{
"files": [
"*.md"
],
"options": {
"tabWidth": 2,
"printWidth": 80
}
}
]
},
"dependencies": {
"@mixer/parallel-prettier": "^2.0.2",
"elementtree": "^0.1.6",
"execa": "^4.0.0",
"gulp-mocha": "^8.0.0",
"ip": "^1.1.5",
"plist": "^3.0.5",
"semver": "^6.3.0",
"socket.io-client": "^2.4.0",
"socket.io-client": "2.4.0",
"uuid": "^8.3.1",
"vscode-cdp-proxy": "^0.2.0",
"vscode-debugadapter": "^1.39.1",
@ -672,21 +692,30 @@
"@types/uuid": "^8.3.0",
"@types/vscode": "1.40.0",
"@typescript-eslint/eslint-plugin": "^4.2.0",
"@typescript-eslint/parser": "^4.2.0",
"@typescript-eslint/parser": "^4.33.0",
"cordova-simulate": "^1.1.3",
"del": "^2.2.2",
"devtools-protocol": "0.0.760817",
"eslint": "^7.32.0",
"eslint-config-prettier": "^7.2.0",
"eslint-import-resolver-node": "^0.3.4",
"eslint-import-resolver-typescript": "^2.7.1",
"eslint-plugin-header": "^3.1.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-promise": "^4.3.1",
"eslint-plugin-unicorn": "^28.0.2",
"event-stream": "3.3.4",
"fancy-log": "^1.3.3",
"glob": "^7.2.0",
"gulp": "^4.0.2",
"gulp-filter": "^6.0.0",
"gulp-preprocess": "^4.0.2",
"gulp-sourcemaps": "^2.6.5",
"gulp-typescript": "^5.0.1",
"minimist": "^1.2.6",
"mocha": "^9.2.0",
"prettier": "2.6.2",
"rimraf": "^2.7.1",
"should": "^13.2.1",
"sinon": "^9.1.0",

Просмотреть файл

@ -2,16 +2,22 @@
// Licensed under the MIT license. See LICENSE file in the project root for details.
import * as nls from "vscode-nls";
nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
nls.config({
messageFormat: nls.MessageFormat.bundle,
bundleFormat: nls.BundleFormat.standalone,
})();
const localize = nls.loadMessageBundle();
export class ConfigurationReader {
public static readArray(value: any): Array<any> {
if (this.isArray(value)) {
return value;
} else {
throw localize("ExpectedAnArrayCouldntReadValue", "Expected an array. Couldn't read {0}", value);
}
throw localize(
"ExpectedAnArrayCouldntReadValue",
"Expected an array. Couldn't read {0}",
value,
);
}
private static isArray(value: any): boolean {

Просмотреть файл

@ -6,9 +6,10 @@ let customRequire: (packageName: string) => any;
try {
// Export `__non_webpack_require__` in Webpack environments to make sure it doesn't bundle modules loaded via this method
customRequire = (global as any).__non_webpack_require__ === "function"
? (global as any).__non_webpack_require__
: eval("require"); // tslint:disable-line:no-eval
customRequire =
(global as any).__non_webpack_require__ === "function"
? (global as any).__non_webpack_require__
: eval("require"); // tslint:disable-line:no-eval
} catch {
// Use a noop in case both `__non_webpack_require__` and `require` does not exist
customRequire = () => {}; // eslint-disable-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-empty-function

Просмотреть файл

@ -59,7 +59,7 @@ export class ChildProcess {
},
);
});
resolveRes({ process: process, outcome: outcome });
resolveRes({ process, outcome });
});
}
@ -84,7 +84,7 @@ export class ChildProcess {
showStdOutputsOnError: boolean = false,
): ISpawnResult {
const spawnedProcess = this.childProcess.spawn(command, args, options);
let outcome: Promise<void> = new Promise((resolve, reject) => {
const outcome: Promise<void> = new Promise((resolve, reject) => {
spawnedProcess.once("error", (error: any) => {
reject(error);
});
@ -104,7 +104,7 @@ export class ChildProcess {
if (code === 0) {
resolve();
} else {
const commandWithArgs = command + " " + args.join(" ");
const commandWithArgs = `${command} ${args.join(" ")}`;
if (showStdOutputsOnError) {
let details = "";
if (stdoutChunks.length > 0) {
@ -126,11 +126,11 @@ export class ChildProcess {
});
});
return {
spawnedProcess: spawnedProcess,
spawnedProcess,
stdin: spawnedProcess.stdin,
stdout: spawnedProcess.stdout,
stderr: spawnedProcess.stderr,
outcome: outcome,
outcome,
};
}
}

Просмотреть файл

@ -1,8 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
export interface SimulationInfo {
appHostUrl: string;
simHostUrl: string;
urlRoot: string;
}
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
export interface SimulationInfo {
appHostUrl: string;
simHostUrl: string;
urlRoot: string;
}

Просмотреть файл

@ -5,9 +5,10 @@ import * as fs from "fs";
import * as path from "path";
import { SimulateOptions } from "cordova-simulate";
import * as vscode from "vscode";
import * as semver from "semver";
import * as nls from "vscode-nls";
import { CordovaProjectHelper } from "./utils/cordovaProjectHelper";
import { CordovaCommandHelper } from "./utils/cordovaCommandHelper";
import * as semver from "semver";
import { Telemetry } from "./utils/telemetry";
import { TelemetryHelper } from "./utils/telemetryHelper";
import { TsdHelper } from "./utils/tsdHelper";
@ -19,16 +20,18 @@ import { CordovaDebugConfigProvider } from "./extension/debugConfigurationProvid
import { CordovaWorkspaceManager } from "./extension/cordovaWorkspaceManager";
import customRequire from "./common/customRequire";
import { findFileInFolderHierarchy } from "./utils/extensionHelper";
import * as nls from "vscode-nls";
nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
nls.config({
messageFormat: nls.MessageFormat.bundle,
bundleFormat: nls.BundleFormat.standalone,
})();
const localize = nls.loadMessageBundle();
let PLUGIN_TYPE_DEFS_FILENAME = "pluginTypings.json";
let PLUGIN_TYPE_DEFS_PATH = findFileInFolderHierarchy(__dirname, PLUGIN_TYPE_DEFS_FILENAME);
let CORDOVA_TYPINGS_QUERYSTRING = "cordova";
let JSCONFIG_FILENAME = "jsconfig.json";
let TSCONFIG_FILENAME = "tsconfig.json";
const PLUGIN_TYPE_DEFS_FILENAME = "pluginTypings.json";
const PLUGIN_TYPE_DEFS_PATH = findFileInFolderHierarchy(__dirname, PLUGIN_TYPE_DEFS_FILENAME);
const CORDOVA_TYPINGS_QUERYSTRING = "cordova";
const JSCONFIG_FILENAME = "jsconfig.json";
const TSCONFIG_FILENAME = "tsconfig.json";
let EXTENSION_CONTEXT: vscode.ExtensionContext;
/**
@ -40,21 +43,32 @@ let COUNT_WORKSPACE_FOLDERS = 9000;
export function activate(context: vscode.ExtensionContext): void {
// Asynchronously enable telemetry
Telemetry.init("cordova-tools", customRequire(findFileInFolderHierarchy(__dirname, "package.json")).version, { isExtensionProcess: true, projectRoot: "" });
Telemetry.init(
"cordova-tools",
customRequire(findFileInFolderHierarchy(__dirname, "package.json")).version,
{ isExtensionProcess: true, projectRoot: "" },
);
EXTENSION_CONTEXT = context;
let activateExtensionEvent = TelemetryHelper.createTelemetryActivity("activate");
const activateExtensionEvent = TelemetryHelper.createTelemetryActivity("activate");
try {
EXTENSION_CONTEXT.subscriptions.push(vscode.workspace.onDidChangeWorkspaceFolders((event) => onChangeWorkspaceFolders(event)));
EXTENSION_CONTEXT.subscriptions.push(
vscode.workspace.onDidChangeWorkspaceFolders(event => onChangeWorkspaceFolders(event)),
);
const configProvider = new CordovaDebugConfigProvider();
EXTENSION_CONTEXT.subscriptions.push(vscode.debug.registerDebugConfigurationProvider("cordova", configProvider));
EXTENSION_CONTEXT.subscriptions.push(
vscode.debug.registerDebugConfigurationProvider("cordova", configProvider),
);
const cordovaFactory = new CordovaSessionManager();
EXTENSION_CONTEXT.subscriptions.push(vscode.debug.registerDebugAdapterDescriptorFactory("cordova", cordovaFactory));
EXTENSION_CONTEXT.subscriptions.push(
vscode.debug.registerDebugAdapterDescriptorFactory("cordova", cordovaFactory),
);
const workspaceFolders: ReadonlyArray<vscode.WorkspaceFolder> | undefined = vscode.workspace.workspaceFolders;
const workspaceFolders: ReadonlyArray<vscode.WorkspaceFolder> | undefined =
vscode.workspace.workspaceFolders;
if (workspaceFolders) {
registerCordovaCommands(cordovaFactory);
@ -62,7 +76,8 @@ export function activate(context: vscode.ExtensionContext): void {
onFolderAdded(folder);
});
}
activateExtensionEvent.properties["cordova.workspaceFoldersCount"] = workspaceFolders.length;
activateExtensionEvent.properties["cordova.workspaceFoldersCount"] =
workspaceFolders.length;
Telemetry.send(activateExtensionEvent);
} catch (e) {
activateExtensionEvent.properties["cordova.error"] = true;
@ -77,13 +92,13 @@ export function deactivate(): void {
function onChangeWorkspaceFolders(event: vscode.WorkspaceFoldersChangeEvent) {
if (event.removed.length) {
event.removed.forEach((folder) => {
event.removed.forEach(folder => {
onFolderRemoved(folder);
});
}
if (event.added.length) {
event.added.forEach((folder) => {
event.added.forEach(folder => {
onFolderAdded(folder);
});
}
@ -96,22 +111,29 @@ export function createAdditionalWorkspaceFolder(folderPath: string): vscode.Work
name: path.basename(folderPath),
index: COUNT_WORKSPACE_FOLDERS + 1,
};
} else return null;
}
return null;
}
export function onFolderAdded(folder: vscode.WorkspaceFolder): void {
let workspaceRoot = folder.uri.fsPath;
const workspaceRoot = folder.uri.fsPath;
if (!CordovaProjectHelper.isCordovaProject(workspaceRoot)) {
vscode.window.showWarningMessage(localize("ExtensionRequiresWorkspaceRootToBeCordovaProjectRoot", "VS Code Cordova Tools extension requires the workspace root to be your Cordova project's root. The project '{0}' won't be available for debugging.", workspaceRoot));
vscode.window.showWarningMessage(
localize(
"ExtensionRequiresWorkspaceRootToBeCordovaProjectRoot",
"VS Code Cordova Tools extension requires the workspace root to be your Cordova project's root. The project '{0}' won't be available for debugging.",
workspaceRoot,
),
);
return;
}
// Send project type to telemetry for each workspace folder
let cordovaProjectTypeEvent = TelemetryHelper.createTelemetryEvent("cordova.projectType");
const cordovaProjectTypeEvent = TelemetryHelper.createTelemetryEvent("cordova.projectType");
TelemetryHelper.determineProjectTypes(workspaceRoot)
.then((projType) => {
cordovaProjectTypeEvent.properties["projectType"] =
.then(projType => {
cordovaProjectTypeEvent.properties.projectType =
TelemetryHelper.prepareProjectTypesTelemetry(projType);
})
.finally(() => {
@ -123,18 +145,25 @@ export function onFolderAdded(folder: vscode.WorkspaceFolder): void {
// setup a file system watcher to watch changes to plugins in the Cordova project
// Note that watching plugins/fetch.json file would suffice
let watcher = vscode.workspace.createFileSystemWatcher("**/plugins/fetch.json", false /*ignoreCreateEvents*/, false /*ignoreChangeEvents*/, false /*ignoreDeleteEvents*/);
const watcher = vscode.workspace.createFileSystemWatcher(
"**/plugins/fetch.json",
false /* ignoreCreateEvents*/,
false /* ignoreChangeEvents*/,
false /* ignoreDeleteEvents*/,
);
watcher.onDidChange(() => updatePluginTypeDefinitions(workspaceRoot));
watcher.onDidDelete(() => updatePluginTypeDefinitions(workspaceRoot));
watcher.onDidCreate(() => updatePluginTypeDefinitions(workspaceRoot));
EXTENSION_CONTEXT.subscriptions.push(watcher);
let simulator: PluginSimulator = new PluginSimulator();
let workspaceManager: CordovaWorkspaceManager = new CordovaWorkspaceManager(simulator, folder);
const simulator: PluginSimulator = new PluginSimulator();
const workspaceManager: CordovaWorkspaceManager = new CordovaWorkspaceManager(
simulator,
folder,
);
ProjectsStorage.addFolder(folder, workspaceManager);
COUNT_WORKSPACE_FOLDERS ++;
COUNT_WORKSPACE_FOLDERS++;
// extensionServer takes care of disposing the simulator instance
// context.subscriptions.push(extensionServer);
@ -145,12 +174,20 @@ export function onFolderAdded(folder: vscode.WorkspaceFolder): void {
EXTENSION_CONTEXT.subscriptions.push(
vscode.languages.registerCompletionItemProvider(
IonicCompletionProvider.JS_DOCUMENT_SELECTOR,
new IonicCompletionProvider(path.join(findFileInFolderHierarchy(__dirname, "snippets"), "ionicJs.json"))));
new IonicCompletionProvider(
path.join(findFileInFolderHierarchy(__dirname, "snippets"), "ionicJs.json"),
),
),
);
EXTENSION_CONTEXT.subscriptions.push(
vscode.languages.registerCompletionItemProvider(
IonicCompletionProvider.HTML_DOCUMENT_SELECTOR,
new IonicCompletionProvider(path.join(findFileInFolderHierarchy(__dirname, "snippets"), "ionicHtml.json"))));
new IonicCompletionProvider(
path.join(findFileInFolderHierarchy(__dirname, "snippets"), "ionicHtml.json"),
),
),
);
}
// Install Ionic type definitions if necessary
@ -165,43 +202,59 @@ export function onFolderAdded(folder: vscode.WorkspaceFolder): void {
path.join("ionic", "ionic.d.ts"),
]);
}
TsdHelper.installTypings(CordovaProjectHelper.getOrCreateTypingsTargetPath(workspaceRoot), ionicTypings, workspaceRoot);
TsdHelper.installTypings(
CordovaProjectHelper.getOrCreateTypingsTargetPath(workspaceRoot),
ionicTypings,
workspaceRoot,
);
}
let pluginTypings = getPluginTypingsJson();
const pluginTypings = getPluginTypingsJson();
if (!pluginTypings) {
return;
}
// Skip adding typings for cordova in case of Typescript or Ionic (except v1) projects
// to avoid conflicts between typings we install and user-installed ones.
if (!CordovaProjectHelper.isTypescriptProject(workspaceRoot) &&
if (
!CordovaProjectHelper.isTypescriptProject(workspaceRoot) &&
!(ionicMajorVersion && ionicMajorVersion > 1)
) {
// Install the type defintion files for Cordova
TsdHelper.installTypings(CordovaProjectHelper.getOrCreateTypingsTargetPath(workspaceRoot),
[pluginTypings[CORDOVA_TYPINGS_QUERYSTRING].typingFile], workspaceRoot);
TsdHelper.installTypings(
CordovaProjectHelper.getOrCreateTypingsTargetPath(workspaceRoot),
[pluginTypings[CORDOVA_TYPINGS_QUERYSTRING].typingFile],
workspaceRoot,
);
}
// Install type definition files for the currently installed plugins
updatePluginTypeDefinitions(workspaceRoot);
let pluginFilePath = path.join(workspaceRoot, ".vscode", "plugins.json");
const pluginFilePath = path.join(workspaceRoot, ".vscode", "plugins.json");
if (fs.existsSync(pluginFilePath)) {
fs.unlinkSync(pluginFilePath);
}
TelemetryHelper.sendPluginsList(workspaceRoot, CordovaProjectHelper.getInstalledPlugins(workspaceRoot));
TelemetryHelper.sendPluginsList(
workspaceRoot,
CordovaProjectHelper.getInstalledPlugins(workspaceRoot),
);
// In VSCode 0.10.10+, if the root doesn't contain jsconfig.json or tsconfig.json, intellisense won't work for files without /// typing references, so add a jsconfig.json here if necessary
let jsconfigPath: string = path.join(workspaceRoot, JSCONFIG_FILENAME);
let tsconfigPath: string = path.join(workspaceRoot, TSCONFIG_FILENAME);
const jsconfigPath: string = path.join(workspaceRoot, JSCONFIG_FILENAME);
const tsconfigPath: string = path.join(workspaceRoot, TSCONFIG_FILENAME);
Promise.all([CordovaProjectHelper.exists(jsconfigPath), CordovaProjectHelper.exists(tsconfigPath)]).then(([jsExists, tsExists]) => {
Promise.all([
CordovaProjectHelper.exists(jsconfigPath),
CordovaProjectHelper.exists(tsconfigPath),
]).then(([jsExists, tsExists]) => {
if (!jsExists && !tsExists) {
fs.promises.writeFile(jsconfigPath, "{}").then(() => {
// Any open file must be reloaded to enable intellisense on them, so inform the user
vscode.window.showInformationMessage("A 'jsconfig.json' file was created to enable IntelliSense. You may need to reload your open JS file(s).");
vscode.window.showInformationMessage(
"A 'jsconfig.json' file was created to enable IntelliSense. You may need to reload your open JS file(s).",
);
});
}
});
@ -209,7 +262,9 @@ export function onFolderAdded(folder: vscode.WorkspaceFolder): void {
function onFolderRemoved(folder: vscode.WorkspaceFolder): void {
Object.keys(ProjectsStorage.projectsCache).forEach(path => {
if (CordovaProjectHelper.checkPathBelongsToHierarchy(folder.uri.fsPath.toLowerCase(), path)) {
if (
CordovaProjectHelper.checkPathBelongsToHierarchy(folder.uri.fsPath.toLowerCase(), path)
) {
ProjectsStorage.delFolder(path);
}
});
@ -220,54 +275,87 @@ function getPluginTypingsJson(): any {
return customRequire(PLUGIN_TYPE_DEFS_PATH);
}
console.error(localize("CordovaPluginTypeDeclarationMappingFileIsMissing", "Cordova plugin type declaration mapping file 'pluginTypings.json' is missing from the extension folder."));
console.error(
localize(
"CordovaPluginTypeDeclarationMappingFileIsMissing",
"Cordova plugin type declaration mapping file 'pluginTypings.json' is missing from the extension folder.",
),
);
return null;
}
function getNewTypeDefinitions(installedPlugins: string[]): string[] {
let pluginTypings = getPluginTypingsJson();
const pluginTypings = getPluginTypingsJson();
if (!pluginTypings) {
return;
}
return installedPlugins.filter(pluginName => !!pluginTypings[pluginName])
return installedPlugins
.filter(pluginName => !!pluginTypings[pluginName])
.map(pluginName => pluginTypings[pluginName].typingFile);
}
function addPluginTypeDefinitions(projectRoot: string, installedPlugins: string[], currentTypeDefs: string[]): void {
let pluginTypings = getPluginTypingsJson();
function addPluginTypeDefinitions(
projectRoot: string,
installedPlugins: string[],
currentTypeDefs: string[],
): void {
const pluginTypings = getPluginTypingsJson();
if (!pluginTypings) {
return;
}
let typingsToAdd = installedPlugins.filter((pluginName: string) => {
if (pluginTypings[pluginName]) {
return currentTypeDefs.indexOf(pluginTypings[pluginName].typingFile) < 0;
}
const typingsToAdd = installedPlugins
.filter((pluginName: string) => {
if (pluginTypings[pluginName]) {
return !currentTypeDefs.includes(pluginTypings[pluginName].typingFile);
}
// If we do not know the plugin, collect it anonymously for future prioritisation
let unknownPluginEvent = TelemetryHelper.createTelemetryEvent("unknownPlugin");
unknownPluginEvent.setPiiProperty("plugin", pluginName);
Telemetry.send(unknownPluginEvent);
return false;
}).map((pluginName: string) => {
return pluginTypings[pluginName].typingFile;
});
// If we do not know the plugin, collect it anonymously for future prioritisation
const unknownPluginEvent = TelemetryHelper.createTelemetryEvent("unknownPlugin");
unknownPluginEvent.setPiiProperty("plugin", pluginName);
Telemetry.send(unknownPluginEvent);
return false;
})
.map((pluginName: string) => {
return pluginTypings[pluginName].typingFile;
});
TsdHelper.installTypings(CordovaProjectHelper.getOrCreateTypingsTargetPath(projectRoot),
typingsToAdd, projectRoot);
TsdHelper.installTypings(
CordovaProjectHelper.getOrCreateTypingsTargetPath(projectRoot),
typingsToAdd,
projectRoot,
);
}
function removePluginTypeDefinitions(projectRoot: string, currentTypeDefs: string[], newTypeDefs: string[]): void {
function removePluginTypeDefinitions(
projectRoot: string,
currentTypeDefs: string[],
newTypeDefs: string[],
): void {
// Find the type definition files that need to be removed
let typeDefsToRemove = currentTypeDefs
.filter((typeDef: string) => newTypeDefs.indexOf(typeDef) < 0);
const typeDefsToRemove = currentTypeDefs.filter(
(typeDef: string) => !newTypeDefs.includes(typeDef),
);
TsdHelper.removeTypings(CordovaProjectHelper.getOrCreateTypingsTargetPath(projectRoot), typeDefsToRemove, projectRoot);
TsdHelper.removeTypings(
CordovaProjectHelper.getOrCreateTypingsTargetPath(projectRoot),
typeDefsToRemove,
projectRoot,
);
}
function getRelativeTypeDefinitionFilePath(projectRoot: string, parentPath: string, typeDefinitionFile: string) {
return path.relative(CordovaProjectHelper.getOrCreateTypingsTargetPath(projectRoot), path.resolve(parentPath, typeDefinitionFile)).replace(/\\/g, "\/");
function getRelativeTypeDefinitionFilePath(
projectRoot: string,
parentPath: string,
typeDefinitionFile: string,
) {
return path
.relative(
CordovaProjectHelper.getOrCreateTypingsTargetPath(projectRoot),
path.resolve(parentPath, typeDefinitionFile),
)
.replace(/\\/g, "/");
}
function updatePluginTypeDefinitions(cordovaProjectRoot: string): void {
@ -290,22 +378,24 @@ function updatePluginTypeDefinitions(cordovaProjectRoot: string): void {
let installedNpmModules: string[] = [];
try {
installedNpmModules = fs.readdirSync(nodeModulesDir);
} catch (e) { }
} catch (e) {}
const pluginTypingsJson = getPluginTypingsJson() || {};
installedPlugins = installedPlugins.filter(pluginId => {
// plugins with `forceInstallTypings` flag don't have typings on NPM yet,
// so we still need to install these even if they present in 'node_modules'
const forceInstallTypings = pluginTypingsJson[pluginId] &&
pluginTypingsJson[pluginId].forceInstallTypings;
const forceInstallTypings =
pluginTypingsJson[pluginId] && pluginTypingsJson[pluginId].forceInstallTypings;
return forceInstallTypings || installedNpmModules.indexOf(pluginId) === -1;
return forceInstallTypings || !installedNpmModules.includes(pluginId);
});
}
let newTypeDefs = getNewTypeDefinitions(installedPlugins);
let cordovaPluginTypesFolder = CordovaProjectHelper.getCordovaPluginTypeDefsPath(cordovaProjectRoot);
let ionicPluginTypesFolder = CordovaProjectHelper.getIonicPluginTypeDefsPath(cordovaProjectRoot);
const newTypeDefs = getNewTypeDefinitions(installedPlugins);
const cordovaPluginTypesFolder =
CordovaProjectHelper.getCordovaPluginTypeDefsPath(cordovaProjectRoot);
const ionicPluginTypesFolder =
CordovaProjectHelper.getIonicPluginTypeDefsPath(cordovaProjectRoot);
if (!CordovaProjectHelper.existsSync(cordovaPluginTypesFolder)) {
addPluginTypeDefinitions(cordovaProjectRoot, installedPlugins, []);
@ -320,7 +410,13 @@ function updatePluginTypeDefinitions(cordovaProjectRoot: string): void {
// ignore
}
if (cordovaTypeDefs) {
currentTypeDefs = cordovaTypeDefs.map(typeDef => getRelativeTypeDefinitionFilePath(cordovaProjectRoot, cordovaPluginTypesFolder, typeDef));
currentTypeDefs = cordovaTypeDefs.map(typeDef =>
getRelativeTypeDefinitionFilePath(
cordovaProjectRoot,
cordovaPluginTypesFolder,
typeDef,
),
);
}
// Now read the type definitions of Ionic plugins
@ -330,7 +426,15 @@ function updatePluginTypeDefinitions(cordovaProjectRoot: string): void {
}
if (ionicTypeDefs) {
currentTypeDefs.concat(ionicTypeDefs.map(typeDef => getRelativeTypeDefinitionFilePath(cordovaProjectRoot, ionicPluginTypesFolder, typeDef)));
currentTypeDefs.concat(
ionicTypeDefs.map(typeDef =>
getRelativeTypeDefinitionFilePath(
cordovaProjectRoot,
ionicPluginTypesFolder,
typeDef,
),
),
);
}
addPluginTypeDefinitions(cordovaProjectRoot, installedPlugins, currentTypeDefs);
@ -340,72 +444,129 @@ function updatePluginTypeDefinitions(cordovaProjectRoot: string): void {
}
/* Launches a simulate command and records telemetry for it */
function launchSimulateCommand(cordovaProjectRoot: string, options: SimulateOptions): Promise<void> {
return TelemetryHelper.generate("simulateCommand", (generator) => {
return TelemetryHelper.determineProjectTypes(cordovaProjectRoot)
.then((projectType) => {
generator.add("simulateOptions", {
function launchSimulateCommand(
cordovaProjectRoot: string,
options: SimulateOptions,
): Promise<void> {
return TelemetryHelper.generate("simulateCommand", generator => {
return TelemetryHelper.determineProjectTypes(cordovaProjectRoot).then(projectType => {
generator.add(
"simulateOptions",
{
platform: options.platform,
target: options.target,
livereload: options.livereload,
forceprepare: options.forceprepare,
corsproxy: options.corsproxy,
}, false);
generator.add("projectType", TelemetryHelper.prepareProjectTypesTelemetry(projectType), false);
// visibleTextEditors is null proof (returns empty array if no editors visible)
generator.add("visibleTextEditorsCount", vscode.window.visibleTextEditors.length, false);
return projectType;
});
}).then((projectType) => {
},
false,
);
generator.add(
"projectType",
TelemetryHelper.prepareProjectTypesTelemetry(projectType),
false,
);
// visibleTextEditors is null proof (returns empty array if no editors visible)
generator.add(
"visibleTextEditorsCount",
vscode.window.visibleTextEditors.length,
false,
);
return projectType;
});
}).then(projectType => {
const uri = vscode.Uri.file(cordovaProjectRoot);
const workspaceFolder = <vscode.WorkspaceFolder>vscode.workspace.getWorkspaceFolder(uri);
return ProjectsStorage.getFolder(workspaceFolder).pluginSimulator.simulate(cordovaProjectRoot, options, projectType);
return ProjectsStorage.getFolder(workspaceFolder).pluginSimulator.simulate(
cordovaProjectRoot,
options,
projectType,
);
});
}
function registerCordovaCommands(cordovaSessionManager: CordovaSessionManager): void {
EXTENSION_CONTEXT.subscriptions.push(vscode.commands.registerCommand("cordova.restart", () => commandWrapper(CordovaCommandHelper.restartCordovaDebugging, [cordovaSessionManager])));
EXTENSION_CONTEXT.subscriptions.push(vscode.commands.registerCommand("cordova.prepare", () => commandWrapper(CordovaCommandHelper.executeCordovaCommand, ["prepare"])));
EXTENSION_CONTEXT.subscriptions.push(vscode.commands.registerCommand("cordova.build", () => commandWrapper(CordovaCommandHelper.executeCordovaCommand, ["build"])));
EXTENSION_CONTEXT.subscriptions.push(vscode.commands.registerCommand("cordova.run", () => commandWrapper(CordovaCommandHelper.executeCordovaCommand, ["run"])));
EXTENSION_CONTEXT.subscriptions.push(vscode.commands.registerCommand("ionic.prepare", () => commandWrapper(CordovaCommandHelper.executeCordovaCommand, ["prepare", true])));
EXTENSION_CONTEXT.subscriptions.push(vscode.commands.registerCommand("ionic.build", () => commandWrapper(CordovaCommandHelper.executeCordovaCommand, ["build", true])));
EXTENSION_CONTEXT.subscriptions.push(vscode.commands.registerCommand("ionic.run", () => commandWrapper(CordovaCommandHelper.executeCordovaCommand, ["run", true])));
EXTENSION_CONTEXT.subscriptions.push(vscode.commands.registerCommand("cordova.simulate.android", () => {
return selectProject()
.then((project) => {
return launchSimulateCommand(project.workspaceRoot.uri.fsPath, { dir: project.workspaceRoot.uri.fsPath, target: "chrome", platform: "android", lang: vscode.env.language });
EXTENSION_CONTEXT.subscriptions.push(
vscode.commands.registerCommand("cordova.restart", () =>
commandWrapper(CordovaCommandHelper.restartCordovaDebugging, [cordovaSessionManager]),
),
);
EXTENSION_CONTEXT.subscriptions.push(
vscode.commands.registerCommand("cordova.prepare", () =>
commandWrapper(CordovaCommandHelper.executeCordovaCommand, ["prepare"]),
),
);
EXTENSION_CONTEXT.subscriptions.push(
vscode.commands.registerCommand("cordova.build", () =>
commandWrapper(CordovaCommandHelper.executeCordovaCommand, ["build"]),
),
);
EXTENSION_CONTEXT.subscriptions.push(
vscode.commands.registerCommand("cordova.run", () =>
commandWrapper(CordovaCommandHelper.executeCordovaCommand, ["run"]),
),
);
EXTENSION_CONTEXT.subscriptions.push(
vscode.commands.registerCommand("ionic.prepare", () =>
commandWrapper(CordovaCommandHelper.executeCordovaCommand, ["prepare", true]),
),
);
EXTENSION_CONTEXT.subscriptions.push(
vscode.commands.registerCommand("ionic.build", () =>
commandWrapper(CordovaCommandHelper.executeCordovaCommand, ["build", true]),
),
);
EXTENSION_CONTEXT.subscriptions.push(
vscode.commands.registerCommand("ionic.run", () =>
commandWrapper(CordovaCommandHelper.executeCordovaCommand, ["run", true]),
),
);
EXTENSION_CONTEXT.subscriptions.push(
vscode.commands.registerCommand("cordova.simulate.android", () => {
return selectProject().then(project => {
return launchSimulateCommand(project.workspaceRoot.uri.fsPath, {
dir: project.workspaceRoot.uri.fsPath,
target: "chrome",
platform: "android",
lang: vscode.env.language,
});
});
}));
EXTENSION_CONTEXT.subscriptions.push(vscode.commands.registerCommand("cordova.simulate.ios", () => {
return selectProject()
.then((project) => {
return launchSimulateCommand(project.workspaceRoot.uri.fsPath, { dir: project.workspaceRoot.uri.fsPath, target: "chrome", platform: "ios", lang: vscode.env.language });
}),
);
EXTENSION_CONTEXT.subscriptions.push(
vscode.commands.registerCommand("cordova.simulate.ios", () => {
return selectProject().then(project => {
return launchSimulateCommand(project.workspaceRoot.uri.fsPath, {
dir: project.workspaceRoot.uri.fsPath,
target: "chrome",
platform: "ios",
lang: vscode.env.language,
});
});
}));
}),
);
}
function selectProject(): Promise<CordovaWorkspaceManager> {
let keys = Object.keys(ProjectsStorage.projectsCache);
const keys = Object.keys(ProjectsStorage.projectsCache);
if (keys.length > 1) {
return new Promise((resolve, reject) => {
vscode.window.showQuickPick(keys)
.then((selected) => {
if (selected) {
resolve(ProjectsStorage.projectsCache[selected]);
}
}, reject);
vscode.window.showQuickPick(keys).then(selected => {
if (selected) {
resolve(ProjectsStorage.projectsCache[selected]);
}
}, reject);
});
} else if (keys.length === 1) {
return Promise.resolve(ProjectsStorage.projectsCache[keys[0]]);
} else {
return Promise.reject(new Error(localize("NoCordovaProjectIsFound", "No Cordova project is found")));
}
return Promise.reject(
new Error(localize("NoCordovaProjectIsFound", "No Cordova project is found")),
);
}
function commandWrapper(fn, args) {
return selectProject()
.then((project) => {
return fn(project.workspaceRoot.uri.fsPath, ...args);
});
return selectProject().then(project => {
return fn(project.workspaceRoot.uri.fsPath, ...args);
});
}

Просмотреть файл

@ -12,7 +12,7 @@ import { HandlerOptions } from "./abstraction/CDPMessageHandlerBase";
export class CDPMessageHandlerCreator {
public static generateHandlerOptions(args: ICordovaAttachRequestArgs): HandlerOptions {
return ({
return {
platform: args.platform,
debugRequest: args.request,
ionicLiveReload: args.ionicLiveReload,
@ -21,29 +21,46 @@ export class CDPMessageHandlerCreator {
simulatePort: args.simulatePort,
iOSAppPackagePath: args.iOSAppPackagePath,
iOSVersion: args.iOSVersion,
});
};
}
public static create(
sourcemapPathTransformer: SourcemapPathTransformer,
projectType: ProjectType,
args: ICordovaAttachRequestArgs,
isChrome: boolean
): ChromeCordovaCDPMessageHandler | ChromeIonicCDPMessageHandler | SafariCordovaCDPMessageHandler | SafariIonicCDPMessageHandler {
isChrome: boolean,
):
| ChromeCordovaCDPMessageHandler
| ChromeIonicCDPMessageHandler
| SafariCordovaCDPMessageHandler
| SafariIonicCDPMessageHandler {
const handlerOptions = CDPMessageHandlerCreator.generateHandlerOptions(args);
if (isChrome) {
if (projectType.isIonic) {
return new ChromeIonicCDPMessageHandler(sourcemapPathTransformer, projectType, handlerOptions);
} else {
return new ChromeCordovaCDPMessageHandler(sourcemapPathTransformer, projectType, handlerOptions);
}
} else {
if (projectType.isIonic) {
return new SafariIonicCDPMessageHandler(sourcemapPathTransformer, projectType, handlerOptions);
} else {
return new SafariCordovaCDPMessageHandler(sourcemapPathTransformer, projectType, handlerOptions);
return new ChromeIonicCDPMessageHandler(
sourcemapPathTransformer,
projectType,
handlerOptions,
);
}
return new ChromeCordovaCDPMessageHandler(
sourcemapPathTransformer,
projectType,
handlerOptions,
);
}
if (projectType.isIonic) {
return new SafariIonicCDPMessageHandler(
sourcemapPathTransformer,
projectType,
handlerOptions,
);
}
return new SafariCordovaCDPMessageHandler(
sourcemapPathTransformer,
projectType,
handlerOptions,
);
}
}

Просмотреть файл

@ -1,12 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
import {
IProtocolCommand,
IProtocolSuccess,
IProtocolError,
Connection
} from "vscode-cdp-proxy";
import { IProtocolCommand, IProtocolSuccess, IProtocolError, Connection } from "vscode-cdp-proxy";
import { SourcemapPathTransformer } from "../../sourcemapPathTransformer";
import { ProjectType } from "../../../../utils/cordovaProjectHelper";
@ -29,9 +24,9 @@ export interface ExecutionContext {
origin: string;
name: string;
auxData?: {
isDefault: boolean,
type?: "default" | "page",
frameId?: string,
isDefault: boolean;
type?: "default" | "page";
frameId?: string;
};
}
@ -60,7 +55,7 @@ export abstract class CDPMessageHandlerBase {
constructor(
sourcemapPathTransformer: SourcemapPathTransformer,
projectType: ProjectType,
options: HandlerOptions
options: HandlerOptions,
) {
this.sourcemapPathTransformer = sourcemapPathTransformer;
this.projectType = projectType;

Просмотреть файл

@ -1,9 +1,9 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
import { CDPMessageHandlerBase, HandlerOptions } from "./CDPMessageHandlerBase";
import { SourcemapPathTransformer } from "../../sourcemapPathTransformer";
import { ProjectType } from "../../../../utils/cordovaProjectHelper";
import { CDPMessageHandlerBase, HandlerOptions } from "./CDPMessageHandlerBase";
export abstract class ChromeCDPMessageHandlerBase extends CDPMessageHandlerBase {
protected sourcemapsProtocol?: string;
@ -11,7 +11,7 @@ export abstract class ChromeCDPMessageHandlerBase extends CDPMessageHandlerBase
constructor(
sourcemapPathTransformer: SourcemapPathTransformer,
projectType: ProjectType,
options: HandlerOptions
options: HandlerOptions,
) {
super(sourcemapPathTransformer, projectType, options);
}
@ -21,15 +21,16 @@ export abstract class ChromeCDPMessageHandlerBase extends CDPMessageHandlerBase
protected abstract fixSourcemapLocation(reqParams: any, androidAssetURL?: boolean): any;
protected fixSourcemapRegexp(reqParams: any): any {
const regExp = process.platform === "win32" ?
/.*\\\\\[wW\]\[wW\]\[wW\]\\\\(.*?\\.\[jJ\]\[sS\])/g :
/.*\\\/www\\\/(.*?\.js)/g;
let foundStrings = regExp.exec(reqParams.urlRegex);
const regExp =
process.platform === "win32"
? /.*\\{2}(?:\[wW]){3}\\{2}(.*?\\.\[jJ]\[sS])/g
: /.*\\\/www\\\/(.*?\.js)/g;
const foundStrings = regExp.exec(reqParams.urlRegex);
if (foundStrings && foundStrings[1]) {
const uriPart = foundStrings[1].split("\\\\").join("\\/");
reqParams.urlRegex = `${
this.sourcemapsProtocol || "https?"
}:\\/\\/${this.applicationServerAddress}${this.applicationPortPart}\\/${uriPart}`;
reqParams.urlRegex = `${this.sourcemapsProtocol || "https?"}:\\/\\/${
this.applicationServerAddress
}${this.applicationPortPart}\\/${uriPart}`;
}
return reqParams;
}
@ -43,8 +44,7 @@ export abstract class ChromeCDPMessageHandlerBase extends CDPMessageHandlerBase
return true;
}
return false;
} else {
return urlRegExp.test(url);
}
return urlRegExp.test(url);
}
}

Просмотреть файл

@ -1,14 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
import {
CDPMessageHandlerBase,
ExecutionContext,
HandlerOptions
} from "./CDPMessageHandlerBase";
import { SourcemapPathTransformer } from "../../sourcemapPathTransformer";
import { ProjectType } from "../../../../utils/cordovaProjectHelper";
import { CDP_API_NAMES } from "../CDPAPINames";
import { CDPMessageHandlerBase, ExecutionContext, HandlerOptions } from "./CDPMessageHandlerBase";
export abstract class SafariCDPMessageHandlerBase extends CDPMessageHandlerBase {
protected targetId: string;
@ -20,7 +16,7 @@ export abstract class SafariCDPMessageHandlerBase extends CDPMessageHandlerBase
constructor(
sourcemapPathTransformer: SourcemapPathTransformer,
projectType: ProjectType,
options: HandlerOptions
options: HandlerOptions,
) {
super(sourcemapPathTransformer, projectType, options);
this.targetId = "";
@ -38,8 +34,12 @@ export abstract class SafariCDPMessageHandlerBase extends CDPMessageHandlerBase
params: {
type: event.params.message.type,
timestamp: event.params.message.timestamp,
args: event.params.message.parameters || [{ type: "string", value: event.params.message.text }],
stackTrace: { callFrames: event.params.message.stack || event.params.message.stackTrace },
args: event.params.message.parameters || [
{ type: "string", value: event.params.message.text },
],
stackTrace: {
callFrames: event.params.message.stack || event.params.message.stackTrace,
},
executionContextId: 1,
},
};
@ -60,7 +60,9 @@ export abstract class SafariCDPMessageHandlerBase extends CDPMessageHandlerBase
protected configureTargetForIWDPCommunication(): void {
try {
this.sendCustomRequestToAppTarget(CDP_API_NAMES.CONSOLE_ENABLE, {});
this.sendCustomRequestToAppTarget(CDP_API_NAMES.DEBUGGER_SET_BREAKPOINTS_ACTIVE, { active: true });
this.sendCustomRequestToAppTarget(CDP_API_NAMES.DEBUGGER_SET_BREAKPOINTS_ACTIVE, {
active: true,
});
} catch (err) {
// Specifically ignore a fail here since it's only for backcompat
}
@ -74,18 +76,26 @@ export abstract class SafariCDPMessageHandlerBase extends CDPMessageHandlerBase
auxData: {
isDefault: true,
type: "page",
frameId: this.targetId
}
frameId: this.targetId,
},
};
try {
this.sendCustomRequestToDebuggerTarget(CDP_API_NAMES.EXECUTION_CONTEXT_CREATED, { context }, false);
this.sendCustomRequestToDebuggerTarget(
CDP_API_NAMES.EXECUTION_CONTEXT_CREATED,
{ context },
false,
);
} catch (err) {
throw Error("Could not create Execution context");
}
}
protected sendCustomRequestToDebuggerTarget(method: string, params: any = {}, addMessageId: boolean = true): void {
let request: any = {
protected sendCustomRequestToDebuggerTarget(
method: string,
params: any = {},
addMessageId: boolean = true,
): void {
const request: any = {
method,
params,
};

Просмотреть файл

@ -4,7 +4,7 @@
import {
ProcessedCDPMessage,
DispatchDirection,
HandlerOptions
HandlerOptions,
} from "../abstraction/CDPMessageHandlerBase";
import { ChromeCDPMessageHandlerBase } from "../abstraction/chromeCDPMessageHandlerBase";
import { SourcemapPathTransformer } from "../../sourcemapPathTransformer";
@ -17,7 +17,7 @@ export class ChromeCordovaCDPMessageHandler extends ChromeCDPMessageHandlerBase
constructor(
sourcemapPathTransformer: SourcemapPathTransformer,
projectType: ProjectType,
options: HandlerOptions
options: HandlerOptions,
) {
super(sourcemapPathTransformer, projectType, options);
@ -43,10 +43,7 @@ export class ChromeCordovaCDPMessageHandler extends ChromeCDPMessageHandlerBase
public processApplicationCDPMessage(event: any): ProcessedCDPMessage {
const dispatchDirection = DispatchDirection.FORWARD;
if (
event.method === CDP_API_NAMES.DEBUGGER_SCRIPT_PARSED &&
event.params.url
) {
if (event.method === CDP_API_NAMES.DEBUGGER_SCRIPT_PARSED && event.params.url) {
if (this.verifySourceMapUrl(event.params.url)) {
event.params = this.fixSourcemapLocation(event.params);
} else if (event.params.url.includes("android_asset")) {
@ -61,14 +58,16 @@ export class ChromeCordovaCDPMessageHandler extends ChromeCDPMessageHandlerBase
}
protected fixSourcemapLocation(reqParams: any, androidAssetURL?: boolean): any {
let absoluteSourcePath = androidAssetURL ?
this.sourcemapPathTransformer.getClientPathFromFileBasedUrlWithAndroidAsset(reqParams.url) :
this.sourcemapPathTransformer.getClientPathFromHttpBasedUrl(reqParams.url);
const absoluteSourcePath = androidAssetURL
? this.sourcemapPathTransformer.getClientPathFromFileBasedUrlWithAndroidAsset(
reqParams.url,
)
: this.sourcemapPathTransformer.getClientPathFromHttpBasedUrl(reqParams.url);
if (absoluteSourcePath) {
if (process.platform === "win32") {
reqParams.url = "file:///" + absoluteSourcePath.split("\\").join("/"); // transform to URL standard
reqParams.url = `file:///${absoluteSourcePath.split("\\").join("/")}`; // transform to URL standard
} else {
reqParams.url = "file://" + absoluteSourcePath;
reqParams.url = `file://${absoluteSourcePath}`;
}
}
return reqParams;

Просмотреть файл

@ -5,7 +5,7 @@ import * as url from "url";
import {
ProcessedCDPMessage,
DispatchDirection,
HandlerOptions
HandlerOptions,
} from "../abstraction/CDPMessageHandlerBase";
import { ChromeCDPMessageHandlerBase } from "../abstraction/chromeCDPMessageHandlerBase";
import { SourcemapPathTransformer } from "../../sourcemapPathTransformer";
@ -17,7 +17,7 @@ export class ChromeIonicCDPMessageHandler extends ChromeCDPMessageHandlerBase {
constructor(
sourcemapPathTransformer: SourcemapPathTransformer,
projectType: ProjectType,
options: HandlerOptions
options: HandlerOptions,
) {
super(sourcemapPathTransformer, projectType, options);
@ -43,10 +43,7 @@ export class ChromeIonicCDPMessageHandler extends ChromeCDPMessageHandlerBase {
public processApplicationCDPMessage(event: any): ProcessedCDPMessage {
const dispatchDirection = DispatchDirection.FORWARD;
if (
event.method === CDP_API_NAMES.DEBUGGER_SCRIPT_PARSED &&
event.params.url
) {
if (event.method === CDP_API_NAMES.DEBUGGER_SCRIPT_PARSED && event.params.url) {
this.tryToGetIonicDevServerPortFromURL(event.params.url);
if (this.verifySourceMapUrl(event.params.url)) {
event.params = this.fixSourcemapLocation(event.params);
@ -62,16 +59,23 @@ export class ChromeIonicCDPMessageHandler extends ChromeCDPMessageHandlerBase {
}
protected fixSourcemapLocation(reqParams: any, androidAssetURL?: boolean): any {
let absoluteSourcePath = androidAssetURL ?
this.sourcemapPathTransformer.getClientPathFromFileBasedUrlWithAndroidAsset(reqParams.url) :
this.sourcemapPathTransformer.getClientPathFromHttpBasedUrl(reqParams.url);
const absoluteSourcePath = androidAssetURL
? this.sourcemapPathTransformer.getClientPathFromFileBasedUrlWithAndroidAsset(
reqParams.url,
)
: this.sourcemapPathTransformer.getClientPathFromHttpBasedUrl(reqParams.url);
if (absoluteSourcePath) {
if (process.platform === "win32") {
reqParams.url = "file:///" + absoluteSourcePath.split("\\").join("/"); // transform to URL standard
reqParams.url = `file:///${absoluteSourcePath.split("\\").join("/")}`; // transform to URL standard
} else {
reqParams.url = "file://" + absoluteSourcePath;
reqParams.url = `file://${absoluteSourcePath}`;
}
} else if (!(this.platform === PlatformType.Serve || (this.ionicLiveReload && this.debugRequestType === "launch"))) {
} else if (
!(
this.platform === PlatformType.Serve ||
(this.ionicLiveReload && this.debugRequestType === "launch")
)
) {
reqParams.url = "";
}
return reqParams;

Просмотреть файл

@ -5,7 +5,7 @@ import * as semver from "semver";
import {
ProcessedCDPMessage,
DispatchDirection,
HandlerOptions
HandlerOptions,
} from "../abstraction/CDPMessageHandlerBase";
import { SafariCDPMessageHandlerBase } from "../abstraction/safariCDPMessageHandlerBase";
import { SourcemapPathTransformer } from "../../sourcemapPathTransformer";
@ -16,7 +16,7 @@ export class SafariCordovaCDPMessageHandler extends SafariCDPMessageHandlerBase
constructor(
sourcemapPathTransformer: SourcemapPathTransformer,
projectType: ProjectType,
options: HandlerOptions
options: HandlerOptions,
) {
super(sourcemapPathTransformer, projectType, options);
}
@ -27,7 +27,7 @@ export class SafariCordovaCDPMessageHandler extends SafariCDPMessageHandlerBase
if (options.iOSAppPackagePath) {
this.iOSAppPackagePath = options.iOSAppPackagePath;
} else {
throw new Error("\".app\" file isn't found");
throw new Error('".app" file isn\'t found'); // eslint-disable-line
}
}
@ -75,8 +75,9 @@ export class SafariCordovaCDPMessageHandler extends SafariCDPMessageHandlerBase
}
if (
event.method === CDP_API_NAMES.DEBUGGER_SCRIPT_PARSED && event.params.url
&& event.params.url.startsWith(`file://${this.iOSAppPackagePath}`)
event.method === CDP_API_NAMES.DEBUGGER_SCRIPT_PARSED &&
event.params.url &&
event.params.url.startsWith(`file://${this.iOSAppPackagePath}`)
) {
event.params = this.fixSourcemapLocation(event.params);
}
@ -86,7 +87,7 @@ export class SafariCordovaCDPMessageHandler extends SafariCDPMessageHandlerBase
}
if (event.result && event.result.properties) {
event.result = { result: event.result.properties};
event.result = { result: event.result.properties };
}
return {
@ -97,9 +98,11 @@ export class SafariCordovaCDPMessageHandler extends SafariCDPMessageHandlerBase
}
protected fixSourcemapLocation(reqParams: any): any {
const absoluteSourcePath = this.sourcemapPathTransformer.getClientPathFromFileBasedUrl(reqParams.url);
const absoluteSourcePath = this.sourcemapPathTransformer.getClientPathFromFileBasedUrl(
reqParams.url,
);
reqParams.url = absoluteSourcePath ? "file://" + absoluteSourcePath : "";
reqParams.url = absoluteSourcePath ? `file://${absoluteSourcePath}` : "";
return reqParams;
}
@ -108,7 +111,11 @@ export class SafariCordovaCDPMessageHandler extends SafariCDPMessageHandlerBase
const foundStrings = regExp.exec(reqParams.urlRegex);
if (foundStrings && foundStrings[1]) {
const uriPart = foundStrings[1].split("\\\\").join("\\/");
const fixedRemotePath = (this.iOSAppPackagePath.split("\/").join("\\/")).split(".").join("\\.");
const fixedRemotePath = this.iOSAppPackagePath
.split("/")
.join("\\/")
.split(".")
.join("\\.");
reqParams.urlRegex = `file:\\/\\/${fixedRemotePath}\\/www\\/${uriPart}`;
}
return reqParams;

Просмотреть файл

@ -5,7 +5,7 @@ import * as semver from "semver";
import {
ProcessedCDPMessage,
DispatchDirection,
HandlerOptions
HandlerOptions,
} from "../abstraction/CDPMessageHandlerBase";
import { SafariCDPMessageHandlerBase } from "../abstraction/safariCDPMessageHandlerBase";
import { SourcemapPathTransformer } from "../../sourcemapPathTransformer";
@ -18,7 +18,7 @@ export class SafariIonicCDPMessageHandler extends SafariCDPMessageHandlerBase {
constructor(
sourcemapPathTransformer: SourcemapPathTransformer,
projectType: ProjectType,
options: HandlerOptions
options: HandlerOptions,
) {
super(sourcemapPathTransformer, projectType, options);
this.Ionic3EvaluateErrorMessage = "process not defined";
@ -38,7 +38,10 @@ export class SafariIonicCDPMessageHandler extends SafariCDPMessageHandlerBase {
public processDebuggerCDPMessage(event: any): ProcessedCDPMessage {
const dispatchDirection = DispatchDirection.FORWARD;
if (event.method === CDP_API_NAMES.DEBUGGER_SET_BREAKPOINT_BY_URL && !this.ionicLiveReload) {
if (
event.method === CDP_API_NAMES.DEBUGGER_SET_BREAKPOINT_BY_URL &&
!this.ionicLiveReload
) {
event.params = this.fixSourcemapRegexp(event.params);
}
@ -80,11 +83,10 @@ export class SafariIonicCDPMessageHandler extends SafariCDPMessageHandlerBase {
}
if (
event.method === CDP_API_NAMES.DEBUGGER_SCRIPT_PARSED && event.params.url
&& (
event.params.url.startsWith(`ionic://${this.applicationServerAddress}`)
|| event.params.url.startsWith(`file://${this.iOSAppPackagePath}`)
)
event.method === CDP_API_NAMES.DEBUGGER_SCRIPT_PARSED &&
event.params.url &&
(event.params.url.startsWith(`ionic://${this.applicationServerAddress}`) ||
event.params.url.startsWith(`file://${this.iOSAppPackagePath}`))
) {
event.params = this.fixSourcemapLocation(event.params);
}
@ -108,10 +110,12 @@ export class SafariIonicCDPMessageHandler extends SafariCDPMessageHandlerBase {
}
protected fixSourcemapLocation(reqParams: any): any {
const absoluteSourcePath = this.sourcemapPathTransformer.getClientPathFromHttpBasedUrl(reqParams.url);
const absoluteSourcePath = this.sourcemapPathTransformer.getClientPathFromHttpBasedUrl(
reqParams.url,
);
if (absoluteSourcePath) {
reqParams.url = "file://" + absoluteSourcePath;
reqParams.url = `file://${absoluteSourcePath}`;
} else if (!(this.ionicLiveReload && this.debugRequestType === "launch")) {
reqParams.url = "";
}

Просмотреть файл

@ -1,27 +1,25 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
import {
Connection,
Server,
WebSocketTransport
} from "vscode-cdp-proxy";
import * as semver from "semver";
import { IncomingMessage } from "http";
import { OutputChannelLogger } from "../../utils/log/outputChannelLogger";
import { DebuggerEndpointHelper } from "./debuggerEndpointHelper";
import { LogLevel } from "../../utils/log/logHelper";
import { Connection, Server, WebSocketTransport } from "vscode-cdp-proxy";
import * as semver from "semver";
import { CancellationToken, EventEmitter } from "vscode";
import { SourcemapPathTransformer } from "./sourcemapPathTransformer";
import { OutputChannelLogger } from "../../utils/log/outputChannelLogger";
import { LogLevel } from "../../utils/log/logHelper";
import { PlatformType } from "../cordovaDebugSession";
import { ProjectType } from "../../utils/cordovaProjectHelper";
import { SimulateHelper } from "../../utils/simulateHelper";
import { CDPMessageHandlerBase, DispatchDirection } from "./CDPMessageHandlers/abstraction/CDPMessageHandlerBase";
import { CDPMessageHandlerCreator } from "./CDPMessageHandlers/CDPMessageHandlerCreator";
import { ICordovaAttachRequestArgs } from "../requestArgs";
import { DebuggerEndpointHelper } from "./debuggerEndpointHelper";
import { SourcemapPathTransformer } from "./sourcemapPathTransformer";
import {
CDPMessageHandlerBase,
DispatchDirection,
} from "./CDPMessageHandlers/abstraction/CDPMessageHandlerBase";
import { CDPMessageHandlerCreator } from "./CDPMessageHandlers/CDPMessageHandlerCreator";
export class CordovaCDPProxy {
private readonly PROXY_LOG_TAGS = {
DEBUGGER_COMMAND: "Command Debugger To Target",
APPLICATION_COMMAND: "Command Target To Debugger",
@ -55,7 +53,7 @@ export class CordovaCDPProxy {
sourcemapPathTransformer: SourcemapPathTransformer,
projectType: ProjectType,
args: ICordovaAttachRequestArgs,
logLevel: LogLevel = LogLevel.None
logLevel: LogLevel = LogLevel.None,
) {
this.port = port;
this.hostAddress = hostAddress;
@ -66,10 +64,20 @@ export class CordovaCDPProxy {
this.isSimulate = SimulateHelper.isSimulate(args);
if (args.platform === PlatformType.IOS && !this.isSimulate) {
this.CDPMessageHandler = CDPMessageHandlerCreator.create(sourcemapPathTransformer, projectType, args, false);
this.CDPMessageHandler = CDPMessageHandlerCreator.create(
sourcemapPathTransformer,
projectType,
args,
false,
);
this.communicationPreparationsDone = false;
} else {
this.CDPMessageHandler = CDPMessageHandlerCreator.create(sourcemapPathTransformer, projectType, args, true);
this.CDPMessageHandler = CDPMessageHandlerCreator.create(
sourcemapPathTransformer,
projectType,
args,
true,
);
this.communicationPreparationsDone = true;
}
}
@ -77,11 +85,10 @@ export class CordovaCDPProxy {
public createServer(logLevel: LogLevel, cancellationToken: CancellationToken): Promise<void> {
this.cancellationToken = cancellationToken;
this.logLevel = logLevel;
return Server.create({ port: this.port, host: this.hostAddress })
.then((server: Server) => {
this.server = server;
this.server.onConnection(this.onConnectionHandler.bind(this));
});
return Server.create({ port: this.port, host: this.hostAddress }).then((server: Server) => {
this.server = server;
this.server.onConnection(this.onConnectionHandler.bind(this));
});
}
public async stopServer(): Promise<void> {
@ -110,16 +117,18 @@ export class CordovaCDPProxy {
this.browserInspectUri = browserInspectUri;
}
public configureCDPMessageHandlerAccordingToProcessedAttachArgs(args: ICordovaAttachRequestArgs): void {
public configureCDPMessageHandlerAccordingToProcessedAttachArgs(
args: ICordovaAttachRequestArgs,
): void {
if (
args.iOSVersion
&& !this.communicationPreparationsDone
&& semver.lt(args.iOSVersion, "12.2.0")
args.iOSVersion &&
!this.communicationPreparationsDone &&
semver.lt(args.iOSVersion, "12.2.0")
) {
this.communicationPreparationsDone = true;
}
this.CDPMessageHandler.configureHandlerAfterAttachmentPreparation(
CDPMessageHandlerCreator.generateHandlerOptions(args)
CDPMessageHandlerCreator.generateHandlerOptions(args),
);
}
@ -127,7 +136,10 @@ export class CordovaCDPProxy {
return this.simPageTarget?.api;
}
private async onConnectionHandler([debuggerTarget]: [Connection, IncomingMessage]): Promise<void> {
private async onConnectionHandler([debuggerTarget]: [
Connection,
IncomingMessage,
]): Promise<void> {
this.debuggerTarget = debuggerTarget;
this.debuggerTarget.pause(); // don't listen for events until the target is ready
@ -137,10 +149,12 @@ export class CordovaCDPProxy {
this.browserInspectUri = await this.debuggerEndpointHelper.retryGetWSEndpoint(
`http://localhost:${this.applicationTargetPort}`,
20,
this.cancellationToken
this.cancellationToken,
);
} else {
this.browserInspectUri = await this.debuggerEndpointHelper.getWSEndpoint(`http://localhost:${this.applicationTargetPort}`);
this.browserInspectUri = await this.debuggerEndpointHelper.getWSEndpoint(
`http://localhost:${this.applicationTargetPort}`,
);
}
}
if (this.isSimulate) {
@ -148,11 +162,16 @@ export class CordovaCDPProxy {
// the application page endpoint, since each page is processed in a separate process.
// But the application page endpoint does not handle "Target" domain requests, that's why we store both browser
// and app page connections.
const simPageInspectUri = await this.debuggerEndpointHelper.getWSEndpoint(`http://localhost:${this.applicationTargetPort}`, this.isSimulate);
const simPageInspectUri = await this.debuggerEndpointHelper.getWSEndpoint(
`http://localhost:${this.applicationTargetPort}`,
this.isSimulate,
);
this.simPageTarget = new Connection(await WebSocketTransport.create(simPageInspectUri));
}
this.applicationTarget = new Connection(await WebSocketTransport.create(this.browserInspectUri));
this.applicationTarget = new Connection(
await WebSocketTransport.create(this.browserInspectUri),
);
this.setDebuggerTargetUnpausedTimeout();
this.applicationTarget.onError(this.onApplicationTargetError.bind(this));
@ -175,7 +194,11 @@ export class CordovaCDPProxy {
}
private handleDebuggerTargetCommand(event: any) {
this.logger.logWithCustomTag(this.PROXY_LOG_TAGS.DEBUGGER_COMMAND, JSON.stringify(event, null , 2), this.logLevel);
this.logger.logWithCustomTag(
this.PROXY_LOG_TAGS.DEBUGGER_COMMAND,
JSON.stringify(event, null, 2),
this.logLevel,
);
const processedMessage = this.CDPMessageHandler.processDebuggerCDPMessage(event);
if (processedMessage.dispatchDirection === DispatchDirection.BACK) {
@ -186,7 +209,11 @@ export class CordovaCDPProxy {
}
private handleApplicationTargetCommand(event: any) {
this.logger.logWithCustomTag(this.PROXY_LOG_TAGS.APPLICATION_COMMAND, JSON.stringify(event, null , 2), this.logLevel);
this.logger.logWithCustomTag(
this.PROXY_LOG_TAGS.APPLICATION_COMMAND,
JSON.stringify(event, null, 2),
this.logLevel,
);
const processedMessage = this.CDPMessageHandler.processApplicationCDPMessage(event);
if (processedMessage.communicationPreparationsDone) {
@ -202,7 +229,11 @@ export class CordovaCDPProxy {
}
private handleDebuggerTargetReply(event: any) {
this.logger.logWithCustomTag(this.PROXY_LOG_TAGS.DEBUGGER_REPLY, JSON.stringify(event, null , 2), this.logLevel);
this.logger.logWithCustomTag(
this.PROXY_LOG_TAGS.DEBUGGER_REPLY,
JSON.stringify(event, null, 2),
this.logLevel,
);
const processedMessage = this.CDPMessageHandler.processDebuggerCDPMessage(event);
if (processedMessage.dispatchDirection === DispatchDirection.BACK) {
@ -213,7 +244,11 @@ export class CordovaCDPProxy {
}
private handleApplicationTargetReply(event: any) {
this.logger.logWithCustomTag(this.PROXY_LOG_TAGS.APPLICATION_REPLY, JSON.stringify(event, null , 2), this.logLevel);
this.logger.logWithCustomTag(
this.PROXY_LOG_TAGS.APPLICATION_REPLY,
JSON.stringify(event, null, 2),
this.logLevel,
);
const processedMessage = this.CDPMessageHandler.processApplicationCDPMessage(event);
if (processedMessage.dispatchDirection === DispatchDirection.BACK) {
@ -236,7 +271,7 @@ export class CordovaCDPProxy {
}
private async onDebuggerTargetClosed() {
this.CDPMessageHandler.processDebuggerCDPMessage({method: "close"});
this.CDPMessageHandler.processDebuggerCDPMessage({ method: "close" });
this.debuggerTarget = null;
this.communicationPreparationsDone = false;
this.browserInspectUri = "";

Просмотреть файл

@ -3,13 +3,19 @@
import * as URL from "url";
import * as ipModule from "ip";
const dns = require("dns").promises;
import * as http from "http";
import * as https from "https";
import { CancellationToken } from "vscode";
import { delay } from "../../utils/extensionHelper";
import * as nls from "vscode-nls";
nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
import { delay } from "../../utils/extensionHelper";
nls.config({
messageFormat: nls.MessageFormat.bundle,
bundleFormat: nls.BundleFormat.standalone,
})();
const localize = nls.loadMessageBundle();
export class DebuggerEndpointHelper {
@ -30,14 +36,20 @@ export class DebuggerEndpointHelper {
public async retryGetWSEndpoint(
browserURL: string,
attemptNumber: number,
cancellationToken: CancellationToken
cancellationToken: CancellationToken,
): Promise<string> {
try {
return await this.getWSEndpoint(browserURL);
} catch (err) {
if (attemptNumber < 1 || cancellationToken.isCancellationRequested) {
const internalError = new Error(localize("CouldNotConnectToTheDebugTarget", "Could not connect to the debug target at {0}: {1}", browserURL, err.message));
const internalError = new Error(
localize(
"CouldNotConnectToTheDebugTarget",
"Could not connect to the debug target at {0}: {1}",
browserURL,
err.message,
),
);
if (cancellationToken.isCancellationRequested) {
throw new Error(localize("OperationCancelled", "Operation canceled"));
@ -57,9 +69,9 @@ export class DebuggerEndpointHelper {
*/
public async getWSEndpoint(browserURL: string, isSimulate: boolean = false): Promise<string> {
if (!isSimulate) {
const jsonVersion = await this.fetchJson<{ webSocketDebuggerUrl?: string }>(
URL.resolve(browserURL, "/json/version")
);
const jsonVersion = await this.fetchJson<{
webSocketDebuggerUrl?: string;
}>(URL.resolve(browserURL, "/json/version"));
if (jsonVersion.webSocketDebuggerUrl) {
return jsonVersion.webSocketDebuggerUrl;
}
@ -68,13 +80,15 @@ export class DebuggerEndpointHelper {
// Chrome its top-level debugg on /json/version, while Node does not.
// Request both and return whichever one got us a string.
const jsonList = await this.fetchJson<{ webSocketDebuggerUrl: string }[]>(
URL.resolve(browserURL, "/json/list")
URL.resolve(browserURL, "/json/list"),
);
if (jsonList.length) {
return jsonList[0].webSocketDebuggerUrl;
}
throw new Error(localize("CouldNotFindAnyDebuggableTarget", "Could not find any debuggable target"));
throw new Error(
localize("CouldNotFindAnyDebuggableTarget", "Could not find any debuggable target"),
);
}
/**
@ -101,7 +115,6 @@ export class DebuggerEndpointHelper {
}
const request = driver.get(url, requestOptions, response => {
let data = "";
response.setEncoding("utf8");
response.on("data", (chunk: string) => (data += chunk));
@ -122,7 +135,7 @@ export class DebuggerEndpointHelper {
try {
const url = new URL.URL(address);
// replace brackets in ipv6 addresses:
ipOrHostname = url.hostname.replace(/^\[|\]$/g, "");
ipOrHostname = url.hostname.replace(/^\[|]$/g, "");
} catch {
ipOrHostname = address;
}

Просмотреть файл

@ -6,8 +6,7 @@ import * as fs from "fs";
import * as url from "url";
import { ICordovaAttachRequestArgs } from "../requestArgs";
import { PlatformType } from "../cordovaDebugSession";
import { CordovaProjectHelper } from "../../utils/cordovaProjectHelper";
import { ProjectType } from "../../utils/cordovaProjectHelper";
import { CordovaProjectHelper, ProjectType } from "../../utils/cordovaProjectHelper";
export class SourcemapPathTransformer {
private _cordovaRoot: string;
@ -35,12 +34,11 @@ export class SourcemapPathTransformer {
public getClientPathFromFileBasedUrl(sourceUrl: string): string {
const regExp = new RegExp("file:\\/\\/\\/.*\\.app(?:\\/www)*(\\/.*\\.(js|html))", "g");
let foundStrings = regExp.exec(sourceUrl);
const foundStrings = regExp.exec(sourceUrl);
if (foundStrings && foundStrings[1]) {
return this.getClientPath(foundStrings[1]);
} else {
return this.getClientPath("/");
}
return this.getClientPath("/");
}
public getClientPathFromHttpBasedUrl(sourceUrl: string): string {
@ -54,7 +52,7 @@ export class SourcemapPathTransformer {
}
public getClientPath(relativeSourcePath: string): string {
let wwwRoot = path.join(this._cordovaRoot, "www");
const wwwRoot = path.join(this._cordovaRoot, "www");
// Given an absolute file:/// (such as from the iOS simulator) vscode-chrome-debug's
// default behavior is to use that exact file, if it exists. We don't want that,
@ -62,15 +60,16 @@ export class SourcemapPathTransformer {
// A simple workaround for this is to convert file:// paths to bogus http:// paths
let defaultPath = "";
let foldersForSearch = [this._webRoot, this._cordovaRoot, wwwRoot];
const foldersForSearch = [this._webRoot, this._cordovaRoot, wwwRoot];
if (this._projectTypes.ionicMajorVersion >= 4) {
// We don't need to connect ts files with js in www folder
// because Ionic4 `serve` and `ionic cordova run` with livereload option enabled
// don't use www directory anymore. If www directory is fulfilled and livereload is used then
// source maps could be messed up.
if ((this._platform === PlatformType.Serve || this._ionicLiveReload)
&& this._debugRequestType === "launch"
if (
(this._platform === PlatformType.Serve || this._ionicLiveReload) &&
this._debugRequestType === "launch"
) {
foldersForSearch.pop();
}
@ -78,7 +77,7 @@ export class SourcemapPathTransformer {
// Find the mapped local file. Try looking first in the user-specified webRoot, then in the project root, and then in the www folder
for (const searchFolder of foldersForSearch) {
let mappedPath = this.targetUrlToClientPath(relativeSourcePath, searchFolder);
const mappedPath = this.targetUrlToClientPath(relativeSourcePath, searchFolder);
if (mappedPath) {
defaultPath = mappedPath;
@ -88,8 +87,8 @@ export class SourcemapPathTransformer {
if (defaultPath.toLowerCase().indexOf(wwwRoot.toLowerCase()) === 0) {
// If the path appears to be in www, check to see if it exists in /merges/<platform>/<relative path>
let relativePath = path.relative(wwwRoot, defaultPath);
let mergesPath = path.join(this._cordovaRoot, "merges", this._platform, relativePath);
const relativePath = path.relative(wwwRoot, defaultPath);
const mergesPath = path.join(this._cordovaRoot, "merges", this._platform, relativePath);
if (fs.existsSync(mergesPath)) {
// This file is overriden by a merge: Use that one
return mergesPath;
@ -100,11 +99,10 @@ export class SourcemapPathTransformer {
private targetUrlToClientPath(sourceUrlPath: string, searchFolder: string): string {
const rest = sourceUrlPath.substring(1);
const absoluteSourcePath = rest ?
CordovaProjectHelper.properJoin(searchFolder, rest) :
searchFolder;
const absoluteSourcePath = rest
? CordovaProjectHelper.properJoin(searchFolder, rest)
: searchFolder;
return CordovaProjectHelper.existsSync(absoluteSourcePath) ?
absoluteSourcePath : "";
return CordovaProjectHelper.existsSync(absoluteSourcePath) ? absoluteSourcePath : "";
}
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -1,277 +1,390 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
"use strict";
import * as child_process from "child_process";
import * as fs from "fs";
import * as path from "path";
import * as pl from "plist";
import * as xcode from "xcode";
import { delay } from "../utils/extensionHelper";
import { ChildProcess } from "../common/node/childProcess";
import * as nls from "vscode-nls";
import { IOSTarget } from "../utils/ios/iOSTargetManager";
import { isDirectory } from "../common/utils";
import { PlistBuddy } from "../utils/ios/PlistBuddy";
nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
const localize = nls.loadMessageBundle();
export class CordovaIosDeviceLauncher {
private static nativeDebuggerProxyInstance: child_process.ChildProcess;
private static webDebuggerProxyInstance: child_process.ChildProcess;
public static cleanup(): void {
if (CordovaIosDeviceLauncher.nativeDebuggerProxyInstance) {
CordovaIosDeviceLauncher.nativeDebuggerProxyInstance.kill("SIGHUP");
CordovaIosDeviceLauncher.nativeDebuggerProxyInstance = null;
}
if (CordovaIosDeviceLauncher.webDebuggerProxyInstance) {
CordovaIosDeviceLauncher.webDebuggerProxyInstance.kill();
CordovaIosDeviceLauncher.webDebuggerProxyInstance = null;
}
}
public static getBundleIdentifier(projectRoot: string): Promise<string> {
return fs.promises.readdir(path.join(projectRoot, "platforms", "ios")).then((files: string[]) => {
let xcodeprojfiles = files.filter((file: string) => /\.xcodeproj$/.test(file));
if (xcodeprojfiles.length === 0) {
throw new Error(localize("UnableToFindXCodeProjFile", "Unable to find xcodeproj file"));
}
let xcodeprojfile = xcodeprojfiles[0];
let projectName = /^(.*)\.xcodeproj/.exec(xcodeprojfile)[1];
let filepath = path.join(projectRoot, "platforms", "ios", projectName, projectName + "-Info.plist");
let plist = pl.parse(fs.readFileSync(filepath, "utf8"));
// Since Cordova 9, no plain value is used, so we need to take it from project.pbxproj
if (plist.CFBundleIdentifier === "$(PRODUCT_BUNDLE_IDENTIFIER)") {
return this.getBundleIdentifierFromPbxproj(path.join(projectRoot, "platforms", "ios", xcodeprojfile));
} else {
return plist.CFBundleIdentifier;
}
});
}
public static startDebugProxy(proxyPort: number): Promise<child_process.ChildProcess> {
if (CordovaIosDeviceLauncher.nativeDebuggerProxyInstance) {
CordovaIosDeviceLauncher.nativeDebuggerProxyInstance.kill("SIGHUP"); // idevicedebugserver does not exit from SIGTERM
CordovaIosDeviceLauncher.nativeDebuggerProxyInstance = null;
}
return CordovaIosDeviceLauncher.mountDeveloperImage().then(() => {
return new Promise((resolve, reject) => {
CordovaIosDeviceLauncher.nativeDebuggerProxyInstance = child_process.spawn("idevicedebugserverproxy", [proxyPort.toString()]);
CordovaIosDeviceLauncher.nativeDebuggerProxyInstance.on("error", function (err: any): void {
reject(err);
});
// Allow 200ms for the spawn to error out, ~125ms isn't uncommon for some failures
return delay(200).then(() => resolve(CordovaIosDeviceLauncher.nativeDebuggerProxyInstance));
});
});
}
public static startWebkitDebugProxy(proxyPort: number, proxyRangeStart: number, proxyRangeEnd: number, iOSTarget: IOSTarget): Promise<void> {
if (CordovaIosDeviceLauncher.webDebuggerProxyInstance) {
CordovaIosDeviceLauncher.webDebuggerProxyInstance.kill();
CordovaIosDeviceLauncher.webDebuggerProxyInstance = null;
}
return new Promise(async (resolve, reject) => {
let iwdpArgs = ["-c", `null:${proxyPort},:${proxyRangeStart}-${proxyRangeEnd}`];
if (iOSTarget.isVirtualTarget) {
const webInspectorSocketPath = await CordovaIosDeviceLauncher.getWebInspectorSocket(iOSTarget.id);
if (!webInspectorSocketPath) {
throw new Error(`Couldn't find a web inspector socket for the simulator udid ${iOSTarget.id}`);
}
iwdpArgs.push("-s", `unix:${webInspectorSocketPath}`);
}
CordovaIosDeviceLauncher.webDebuggerProxyInstance = child_process.spawn("ios_webkit_debug_proxy", iwdpArgs);
CordovaIosDeviceLauncher.webDebuggerProxyInstance.on("error", function () {
reject(new Error(localize("UnableToStartIosWebkitDebugProxy", "Unable to start ios_webkit_debug_proxy.")));
});
// Allow some time for the spawned process to error out
return delay(300).then(() => resolve(void 0));
});
}
public static getPathOnDevice(packageId: string): Promise<string> {
const cp = new ChildProcess();
return cp.execToString("ideviceinstaller -l -o xml > /tmp/$$.ideviceinstaller && echo /tmp/$$.ideviceinstaller")
.catch(function (err: any): any {
if (err.code === "ENOENT") {
throw new Error(localize("UnableToStartiDeviceInstaller", "Unable to find ideviceinstaller."));
}
throw err;
}).then((stdout: string) => {
// First find the path of the app on the device
let filename: string = stdout.trim();
if (!/^\/tmp\/[0-9]+\.ideviceinstaller$/.test(filename)) {
throw new Error(localize("UnableToListInstalledApplicationsOnDevice", "Unable to list installed applications on device"));
}
let list: any[] = pl.parse(fs.readFileSync(filename, "utf8"));
fs.unlinkSync(filename);
for (let i: number = 0; i < list.length; ++i) {
if (list[i].CFBundleIdentifier === packageId) {
let path: string = list[i].Path;
return path;
}
}
throw new Error(localize("ApplicationNotInstalledOnTheDevice", "Application not installed on the device"));
});
}
public static encodePath(packagePath: string): string {
// Encode the path by converting each character value to hex
return packagePath.split("").map((c: string) => c.charCodeAt(0).toString(16)).join("").toUpperCase();
}
public static async getPathOnSimulator(packageId: string, deviceDataPath: string): Promise<string> {
const appsContainer = path.join(deviceDataPath, "Containers", "Bundle", "Application");
if (!fs.existsSync(appsContainer)) {
throw new Error(`Could not detect installed apps on the simulator: the path ${appsContainer} doesn't exist`);
}
const plistBuddy = new PlistBuddy();
let appFolders = (await fs.promises.readdir(appsContainer))
.map(appId => path.join(appsContainer, appId))
.filter(appDir => isDirectory(appDir));
let appFiles: string[] = [];
for (const appFolder of appFolders) {
appFiles.push(
...((await fs.promises.readdir(appFolder))
.filter(file => file.endsWith(".app"))
.map(file => path.join(appFolder, file)))
);
}
for (const appFile of appFiles) {
try {
const bundleIdentifie = await plistBuddy.readPlistProperty(
path.join(appFile, "Info.plist"),
":CFBundleIdentifier",
);
if (bundleIdentifie === packageId) {
return appFile;
}
} catch { }
}
throw new Error(localize("ApplicationNotInstalledOnTheSimulator", "Application not installed on the simulator"));
}
private static getBundleIdentifierFromPbxproj(xcodeprojFilePath: string): Promise<string> {
const pbxprojFilePath = path.join(xcodeprojFilePath, "project.pbxproj");
const pbxproj = xcode.project(pbxprojFilePath).parseSync();
const target = pbxproj.getFirstTarget();
const configListUUID = target.firstTarget.buildConfigurationList;
const configListsMap = pbxproj.pbxXCConfigurationList();
const targetConfigs = configListsMap[configListUUID].buildConfigurations;
const targetConfigUUID = targetConfigs[0].value; // 0 is "Debug, 1 is Release" - usually they have the same associated bundleId, it's highly unlikely someone would change it
const allConfigs = pbxproj.pbxXCBuildConfigurationSection();
const bundleId = allConfigs[targetConfigUUID].buildSettings.PRODUCT_BUNDLE_IDENTIFIER;
return Promise.resolve(bundleId);
}
private static async getWebInspectorSocket(simId: string): Promise<string | null> {
const cp = new ChildProcess();
// lsof -aUc launchd_sim
// gives a set of records like:
// launchd_s 69760 isaac 3u unix 0x57aa4fceea3937f3 0t0 /private/tmp/com.apple.CoreSimulator.SimDevice.D7082A5C-34B5-475C-994E-A21534423B9E/syslogsock
// launchd_s 69760 isaac 5u unix 0x57aa4fceea395f03 0t0 /private/tmp/com.apple.launchd.2B2u8CkN8S/Listeners
// launchd_s 69760 isaac 6u unix 0x57aa4fceea39372b 0t0 ->0x57aa4fceea3937f3
// launchd_s 69760 isaac 8u unix 0x57aa4fceea39598b 0t0 /private/tmp/com.apple.launchd.2j5k1TMh6i/com.apple.webinspectord_sim.socket
// launchd_s 69760 isaac 9u unix 0x57aa4fceea394c43 0t0 /private/tmp/com.apple.launchd.4zm9JO9KEs/com.apple.testmanagerd.unix-domain.socket
// launchd_s 69760 isaac 10u unix 0x57aa4fceea395f03 0t0 /private/tmp/com.apple.launchd.2B2u8CkN8S/Listeners
// launchd_s 69760 isaac 11u unix 0x57aa4fceea39598b 0t0 /private/tmp/com.apple.launchd.2j5k1TMh6i/com.apple.webinspectord_sim.socket
// launchd_s 69760 isaac 12u unix 0x57aa4fceea394c43 0t0 /private/tmp/com.apple.launchd.4zm9JO9KEs/com.apple.testmanagerd.unix-domain.socket
// these _appear_ to always be grouped together (so, the records for the particular sim are all in a group, before the next sim, etc.)
// so starting from the correct UDID, we ought to be able to pull the next record with `com.apple.webinspectord_sim.socket` to get the correct socket
const result = await cp.execToString("lsof -aUc launchd_sim");
for (const record of result.split("com.apple.CoreSimulator.SimDevice.")) {
if (!record.includes(simId)) {
continue;
}
const webinspectorSocketRegexp = /\s+(\S+com\.apple\.webinspectord_sim\.socket)/;
const match = webinspectorSocketRegexp.exec(record);
if (!match) {
return null;
}
return match[1]; // /private/tmp/com.apple.launchd.ZY99hRfFoa/com.apple.webinspectord_sim.socket
}
return null;
}
private static mountDeveloperImage(): Promise<void> {
return CordovaIosDeviceLauncher.getDiskImage()
.then((path: string) => {
let imagemounter: child_process.ChildProcess = child_process.spawn("ideviceimagemounter", [path, path + ".signature"]);
return new Promise((resolve, reject) => {
let stdout: string = "";
imagemounter.stdout.on("data", function (data: any): void {
stdout += data.toString();
});
imagemounter.on("close", function (code: number): void {
if (code !== 0) {
if (stdout.indexOf("Error:") !== -1) {
resolve(); // Technically failed, but likely caused by the image already being mounted.
} else if (stdout.indexOf("No device found, is it plugged in?") !== -1) {
reject(localize("UnableToFindDevice", "Unable to find device. Is the device plugged in?"));
}
reject(localize("UnableToMountDeveloperDiskImage", "Unable to mount developer disk image."));
} else {
resolve();
}
});
imagemounter.on("error", function (err: any): void {
reject(err);
});
});
});
}
private static getDiskImage(): Promise<string> {
const cp = new ChildProcess();
// Attempt to find the OS version of the iDevice, e.g. 7.1
let versionInfo: Promise<string> = cp.execToString("ideviceinfo -s -k ProductVersion")
.then(stdout => {
// Versions for DeveloperDiskImage seem to be X.Y, while some device versions are X.Y.Z
return /^(\d+\.\d+)(?:\.\d+)?$/gm.exec(stdout.trim())[1];
})
.catch(err => {
throw new Error(localize("UnableToGetDeviceOSVersion", "Unable to get device OS version. Details: {0}", err.message));
});
// Attempt to find the path where developer resources exist.
let pathInfo: Promise<string> = cp.execToString("xcrun -sdk iphoneos --show-sdk-platform-path").then(stdout => {
let sdkpath: string = stdout.trim();
return sdkpath;
});
// Attempt to find the developer disk image for the appropriate
return Promise.all([versionInfo, pathInfo]).then(([version, sdkpath]) => {
let find: child_process.ChildProcess = child_process.spawn("find", [sdkpath, "-path", "*" + version + "*", "-name", "DeveloperDiskImage.dmg"]);
return new Promise<string>((resolve, reject) => {
find.stdout.on("data", function (data: any): void {
let dataStr: string = data.toString();
let path: string = dataStr.split("\n")[0].trim();
if (!path) {
reject(localize("UnableToFindDeveloperDiskImage", "Unable to find developer disk image."));
} else {
resolve(path);
}
});
find.on("close", function (): void {
reject(localize("UnableToFindDeveloperDiskImage", "Unable to find developer disk image."));
});
});
});
}
}
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
"use strict";
import * as child_process from "child_process";
import * as fs from "fs";
import * as path from "path";
import * as pl from "plist";
import * as xcode from "xcode";
import * as nls from "vscode-nls";
import { delay } from "../utils/extensionHelper";
import { ChildProcess } from "../common/node/childProcess";
import { IOSTarget } from "../utils/ios/iOSTargetManager";
import { isDirectory } from "../common/utils";
import { PlistBuddy } from "../utils/ios/PlistBuddy";
nls.config({
messageFormat: nls.MessageFormat.bundle,
bundleFormat: nls.BundleFormat.standalone,
})();
const localize = nls.loadMessageBundle();
export class CordovaIosDeviceLauncher {
private static nativeDebuggerProxyInstance: child_process.ChildProcess;
private static webDebuggerProxyInstance: child_process.ChildProcess;
public static cleanup(): void {
if (CordovaIosDeviceLauncher.nativeDebuggerProxyInstance) {
CordovaIosDeviceLauncher.nativeDebuggerProxyInstance.kill("SIGHUP");
CordovaIosDeviceLauncher.nativeDebuggerProxyInstance = null;
}
if (CordovaIosDeviceLauncher.webDebuggerProxyInstance) {
CordovaIosDeviceLauncher.webDebuggerProxyInstance.kill();
CordovaIosDeviceLauncher.webDebuggerProxyInstance = null;
}
}
public static getBundleIdentifier(projectRoot: string): Promise<string> {
return fs.promises
.readdir(path.join(projectRoot, "platforms", "ios"))
.then((files: string[]) => {
const xcodeprojfiles = files.filter((file: string) => /\.xcodeproj$/.test(file));
if (xcodeprojfiles.length === 0) {
throw new Error(
localize("UnableToFindXCodeProjFile", "Unable to find xcodeproj file"),
);
}
const xcodeprojfile = xcodeprojfiles[0];
const projectName = /^(.*)\.xcodeproj/.exec(xcodeprojfile)[1];
const filepath = path.join(
projectRoot,
"platforms",
"ios",
projectName,
`${projectName}-Info.plist`,
);
const plist = pl.parse(fs.readFileSync(filepath, "utf8"));
// Since Cordova 9, no plain value is used, so we need to take it from project.pbxproj
if (plist.CFBundleIdentifier === "$(PRODUCT_BUNDLE_IDENTIFIER)") {
return this.getBundleIdentifierFromPbxproj(
path.join(projectRoot, "platforms", "ios", xcodeprojfile),
);
}
return plist.CFBundleIdentifier;
});
}
public static startDebugProxy(proxyPort: number): Promise<child_process.ChildProcess> {
if (CordovaIosDeviceLauncher.nativeDebuggerProxyInstance) {
CordovaIosDeviceLauncher.nativeDebuggerProxyInstance.kill("SIGHUP"); // idevicedebugserver does not exit from SIGTERM
CordovaIosDeviceLauncher.nativeDebuggerProxyInstance = null;
}
return CordovaIosDeviceLauncher.mountDeveloperImage().then(() => {
return new Promise((resolve, reject) => {
CordovaIosDeviceLauncher.nativeDebuggerProxyInstance = child_process.spawn(
"idevicedebugserverproxy",
[proxyPort.toString()],
);
CordovaIosDeviceLauncher.nativeDebuggerProxyInstance.on(
"error",
function (err: any): void {
reject(err);
},
);
// Allow 200ms for the spawn to error out, ~125ms isn't uncommon for some failures
// eslint-disable-next-line
return delay(200).then(() =>
resolve(CordovaIosDeviceLauncher.nativeDebuggerProxyInstance),
);
});
});
}
public static startWebkitDebugProxy(
proxyPort: number,
proxyRangeStart: number,
proxyRangeEnd: number,
iOSTarget: IOSTarget,
): Promise<void> {
if (CordovaIosDeviceLauncher.webDebuggerProxyInstance) {
CordovaIosDeviceLauncher.webDebuggerProxyInstance.kill();
CordovaIosDeviceLauncher.webDebuggerProxyInstance = null;
}
return new Promise(async (resolve, reject) => {
const iwdpArgs = ["-c", `null:${proxyPort},:${proxyRangeStart}-${proxyRangeEnd}`];
if (iOSTarget.isVirtualTarget) {
const webInspectorSocketPath = await CordovaIosDeviceLauncher.getWebInspectorSocket(
iOSTarget.id,
);
if (!webInspectorSocketPath) {
throw new Error(
`Couldn't find a web inspector socket for the simulator udid ${iOSTarget.id}`,
);
}
iwdpArgs.push("-s", `unix:${webInspectorSocketPath}`);
}
CordovaIosDeviceLauncher.webDebuggerProxyInstance = child_process.spawn(
"ios_webkit_debug_proxy",
iwdpArgs,
);
CordovaIosDeviceLauncher.webDebuggerProxyInstance.on("error", function () {
reject(
new Error(
localize(
"UnableToStartIosWebkitDebugProxy",
"Unable to start ios_webkit_debug_proxy.",
),
),
);
});
// Allow some time for the spawned process to error out
return delay(300).then(() => resolve(undefined)); // eslint-disable-line
});
}
public static getPathOnDevice(packageId: string): Promise<string> {
const cp = new ChildProcess();
return cp
.execToString(
"ideviceinstaller -l -o xml > /tmp/$$.ideviceinstaller && echo /tmp/$$.ideviceinstaller",
)
.catch(function (err: any): any {
if (err.code === "ENOENT") {
throw new Error(
localize(
"UnableToStartiDeviceInstaller",
"Unable to find ideviceinstaller.",
),
);
}
throw err;
})
.then((stdout: string) => {
// First find the path of the app on the device
const filename: string = stdout.trim();
if (!/^\/tmp\/\d+\.ideviceinstaller$/.test(filename)) {
throw new Error(
localize(
"UnableToListInstalledApplicationsOnDevice",
"Unable to list installed applications on device",
),
);
}
const list: any[] = pl.parse(fs.readFileSync(filename, "utf8"));
fs.unlinkSync(filename);
for (let i = 0; i < list.length; ++i) {
if (list[i].CFBundleIdentifier === packageId) {
const path: string = list[i].Path;
return path;
}
}
throw new Error(
localize(
"ApplicationNotInstalledOnTheDevice",
"Application not installed on the device",
),
);
});
}
public static encodePath(packagePath: string): string {
// Encode the path by converting each character value to hex
return packagePath
.split("")
.map((c: string) => c.charCodeAt(0).toString(16))
.join("")
.toUpperCase();
}
public static async getPathOnSimulator(
packageId: string,
deviceDataPath: string,
): Promise<string> {
const appsContainer = path.join(deviceDataPath, "Containers", "Bundle", "Application");
if (!fs.existsSync(appsContainer)) {
throw new Error(
`Could not detect installed apps on the simulator: the path ${appsContainer} doesn't exist`,
);
}
const plistBuddy = new PlistBuddy();
const appFolders = (await fs.promises.readdir(appsContainer))
.map(appId => path.join(appsContainer, appId))
.filter(appDir => isDirectory(appDir));
const appFiles: string[] = [];
for (const appFolder of appFolders) {
appFiles.push(
...(await fs.promises.readdir(appFolder))
.filter(file => file.endsWith(".app"))
.map(file => path.join(appFolder, file)),
);
}
for (const appFile of appFiles) {
try {
const bundleIdentifie = await plistBuddy.readPlistProperty(
path.join(appFile, "Info.plist"),
":CFBundleIdentifier",
);
if (bundleIdentifie === packageId) {
return appFile;
}
} catch {}
}
throw new Error(
localize(
"ApplicationNotInstalledOnTheSimulator",
"Application not installed on the simulator",
),
);
}
private static getBundleIdentifierFromPbxproj(xcodeprojFilePath: string): Promise<string> {
const pbxprojFilePath = path.join(xcodeprojFilePath, "project.pbxproj");
const pbxproj = xcode.project(pbxprojFilePath).parseSync();
const target = pbxproj.getFirstTarget();
const configListUUID = target.firstTarget.buildConfigurationList;
const configListsMap = pbxproj.pbxXCConfigurationList();
const targetConfigs = configListsMap[configListUUID].buildConfigurations;
const targetConfigUUID = targetConfigs[0].value; // 0 is "Debug, 1 is Release" - usually they have the same associated bundleId, it's highly unlikely someone would change it
const allConfigs = pbxproj.pbxXCBuildConfigurationSection();
const bundleId = allConfigs[targetConfigUUID].buildSettings.PRODUCT_BUNDLE_IDENTIFIER;
return Promise.resolve(bundleId);
}
private static async getWebInspectorSocket(simId: string): Promise<string | null> {
const cp = new ChildProcess();
// lsof -aUc launchd_sim
// gives a set of records like:
// launchd_s 69760 isaac 3u unix 0x57aa4fceea3937f3 0t0 /private/tmp/com.apple.CoreSimulator.SimDevice.D7082A5C-34B5-475C-994E-A21534423B9E/syslogsock
// launchd_s 69760 isaac 5u unix 0x57aa4fceea395f03 0t0 /private/tmp/com.apple.launchd.2B2u8CkN8S/Listeners
// launchd_s 69760 isaac 6u unix 0x57aa4fceea39372b 0t0 ->0x57aa4fceea3937f3
// launchd_s 69760 isaac 8u unix 0x57aa4fceea39598b 0t0 /private/tmp/com.apple.launchd.2j5k1TMh6i/com.apple.webinspectord_sim.socket
// launchd_s 69760 isaac 9u unix 0x57aa4fceea394c43 0t0 /private/tmp/com.apple.launchd.4zm9JO9KEs/com.apple.testmanagerd.unix-domain.socket
// launchd_s 69760 isaac 10u unix 0x57aa4fceea395f03 0t0 /private/tmp/com.apple.launchd.2B2u8CkN8S/Listeners
// launchd_s 69760 isaac 11u unix 0x57aa4fceea39598b 0t0 /private/tmp/com.apple.launchd.2j5k1TMh6i/com.apple.webinspectord_sim.socket
// launchd_s 69760 isaac 12u unix 0x57aa4fceea394c43 0t0 /private/tmp/com.apple.launchd.4zm9JO9KEs/com.apple.testmanagerd.unix-domain.socket
// these _appear_ to always be grouped together (so, the records for the particular sim are all in a group, before the next sim, etc.)
// so starting from the correct UDID, we ought to be able to pull the next record with `com.apple.webinspectord_sim.socket` to get the correct socket
const result = await cp.execToString("lsof -aUc launchd_sim");
for (const record of result.split("com.apple.CoreSimulator.SimDevice.")) {
if (!record.includes(simId)) {
continue;
}
const webinspectorSocketRegexp = /\s+(\S+com\.apple\.webinspectord_sim\.socket)/;
const match = webinspectorSocketRegexp.exec(record);
if (!match) {
return null;
}
return match[1]; // /private/tmp/com.apple.launchd.ZY99hRfFoa/com.apple.webinspectord_sim.socket
}
return null;
}
private static mountDeveloperImage(): Promise<void> {
return CordovaIosDeviceLauncher.getDiskImage().then((path: string) => {
const imagemounter: child_process.ChildProcess = child_process.spawn(
"ideviceimagemounter",
[path, `${path}.signature`],
);
return new Promise((resolve, reject) => {
let stdout = "";
imagemounter.stdout.on("data", function (data: any): void {
stdout += data.toString();
});
imagemounter.on("close", function (code: number): void {
if (code !== 0) {
if (stdout.includes("Error:")) {
resolve(); // Technically failed, but likely caused by the image already being mounted.
} else if (stdout.includes("No device found, is it plugged in?")) {
reject(
localize(
"UnableToFindDevice",
"Unable to find device. Is the device plugged in?",
),
);
}
reject(
localize(
"UnableToMountDeveloperDiskImage",
"Unable to mount developer disk image.",
),
);
} else {
resolve();
}
});
imagemounter.on("error", function (err: any): void {
reject(err);
});
});
});
}
private static getDiskImage(): Promise<string> {
const cp = new ChildProcess();
// Attempt to find the OS version of the iDevice, e.g. 7.1
const versionInfo: Promise<string> = cp
.execToString("ideviceinfo -s -k ProductVersion")
.then(stdout => {
// Versions for DeveloperDiskImage seem to be X.Y, while some device versions are X.Y.Z
return /^(\d+\.\d+)(?:\.\d+)?$/gm.exec(stdout.trim())[1];
})
.catch(err => {
throw new Error(
localize(
"UnableToGetDeviceOSVersion",
"Unable to get device OS version. Details: {0}",
err.message,
),
);
});
// Attempt to find the path where developer resources exist.
const pathInfo: Promise<string> = cp
.execToString("xcrun -sdk iphoneos --show-sdk-platform-path")
.then(stdout => {
const sdkpath: string = stdout.trim();
return sdkpath;
});
// Attempt to find the developer disk image for the appropriate
return Promise.all([versionInfo, pathInfo]).then(([version, sdkpath]) => {
const find: child_process.ChildProcess = child_process.spawn("find", [
sdkpath,
"-path",
`*${version}*`,
"-name",
"DeveloperDiskImage.dmg",
]);
return new Promise<string>((resolve, reject) => {
find.stdout.on("data", function (data: any): void {
const dataStr: string = data.toString();
const path: string = dataStr.split("\n")[0].trim();
if (!path) {
reject(
localize(
"UnableToFindDeveloperDiskImage",
"Unable to find developer disk image.",
),
);
} else {
resolve(path);
}
});
find.on("close", function (): void {
reject(
localize(
"UnableToFindDeveloperDiskImage",
"Unable to find developer disk image.",
),
);
});
});
});
}
}

Просмотреть файл

@ -1,142 +1,193 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
import * as child_process from "child_process";
import { CordovaProjectHelper } from "../utils/cordovaProjectHelper";
import * as path from "path";
import * as nls from "vscode-nls";
import { findFileInFolderHierarchy } from "../utils/extensionHelper";
import { DebugConsoleLogger } from "./cordovaDebugSession";
nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
const localize = nls.loadMessageBundle();
// suppress the following strings because they are not actual errors:
const errorsToSuppress = [
"Run an Ionic project on a connected device",
"Error: Unhandled error. ('[ios-sim] Simulator already running.\\n')"
];
export function execCommand(command: string, args: string[], errorLogger: (message: string) => void): Promise<string> {
return new Promise<string>((resolve, reject) => {
const proc = child_process.spawn(command, args, { stdio: "pipe" });
let stderr = "";
let stdout = "";
proc.stderr.on("data", (data: Buffer) => {
stderr += data.toString();
});
proc.stdout.on("data", (data: Buffer) => {
stdout += data.toString();
});
proc.on("error", (err: Error) => {
reject(err);
});
proc.on("close", (code: number) => {
if (code !== 0) {
errorLogger(stderr);
errorLogger(stdout);
reject(new Error(localize("ErrorRunningCommand", "Error running {0} {1}", command, args.join(" "))));
}
resolve(stdout);
});
});
}
export function cordovaRunCommand(command: string, args: string[], env, cordovaRootPath: string, outputLogger?: DebugConsoleLogger): Promise<string[]> {
return new Promise<string[]>((resolve, reject) => {
let isIonicProject = CordovaProjectHelper.isIonicAngularProject(cordovaRootPath);
let output = "";
let stderr = "";
let cordovaProcess = cordovaStartCommand(command, args, env, cordovaRootPath);
// Prevent these lines to be shown more than once
// to prevent debug console pollution
let isShown = {
"Running command": false,
"cordova prepare": false,
"cordova platform add": false,
};
// There is error about run command with specified iOS simulator.
// If this simulator is online error "Error: Unhandled error. ('[ios-sim] Simulator already running.\\n')"
// is shown
let skipCommandReject = false;
cordovaProcess.stderr.on("data", data => {
stderr += data.toString();
for (let i = 0; i < errorsToSuppress.length; i++) {
if (data.toString().indexOf(errorsToSuppress[i]) >= 0) {
if (data.toString().includes("Error: Unhandled error. ('[ios-sim] Simulator already running.\\n')")) {
skipCommandReject = true;
}
return;
}
}
outputLogger && outputLogger(data.toString(), "stderr");
});
cordovaProcess.stdout.on("data", (data: Buffer) => {
let str = data.toString().replace(/\u001b/g, "").replace(/\[2K\[G/g, ""); // Erasing `[2K[G` artifacts from DEBUG CONSOLE output
output += str;
for (let message in isShown) {
if (str.indexOf(message) > -1) {
if (!isShown[message]) {
isShown[message] = true;
outputLogger && outputLogger(str, "stdout");
}
return;
}
}
outputLogger && outputLogger(str, "stdout");
if (isIonicProject && str.indexOf("LAUNCH SUCCESS") >= 0) {
resolve([output, stderr]);
}
});
cordovaProcess.on("exit", exitCode => {
if (exitCode && !skipCommandReject) {
reject(new Error(localize("CommandFailedWithExitCode", "{0} {1} failed with exit code {2}", command, args.join(" "), exitCode)));
} else {
resolve([output, stderr]);
}
});
cordovaProcess.on("error", error => {
reject(error);
});
});
}
export function cordovaStartCommand(command: string, args: string[], env: any, cordovaRootPath: string): child_process.ChildProcess {
const isIonic = CordovaProjectHelper.isIonicAngularProject(cordovaRootPath);
const isIonicServe: boolean = args.indexOf("serve") >= 0;
if (isIonic && !isIonicServe) {
const isIonicCliVersionGte3 = CordovaProjectHelper.isIonicCliVersionGte3(cordovaRootPath, command);
if (isIonicCliVersionGte3) {
args.unshift("cordova");
}
}
if (isIonic) {
args.push("--no-interactive");
} else {
args.push("--no-update-notifier");
}
return child_process.spawn(command, args, { cwd: cordovaRootPath, env });
}
export function killTree(processId: number): void {
const cmd = process.platform === "win32" ?
"taskkill.exe /F /T /PID" :
path.join(findFileInFolderHierarchy(__dirname, "scripts"), "terminateProcess.sh");
try {
child_process.execSync(`${cmd} ${processId}`);
} catch (err) {
}
}
export function killChildProcess(childProcess: child_process.ChildProcess): Promise<void> {
killTree(childProcess.pid);
return Promise.resolve();
}
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
import * as child_process from "child_process";
import * as path from "path";
import * as nls from "vscode-nls";
import { CordovaProjectHelper } from "../utils/cordovaProjectHelper";
import { findFileInFolderHierarchy } from "../utils/extensionHelper";
import { DebugConsoleLogger } from "./cordovaDebugSession";
nls.config({
messageFormat: nls.MessageFormat.bundle,
bundleFormat: nls.BundleFormat.standalone,
})();
const localize = nls.loadMessageBundle();
// suppress the following strings because they are not actual errors:
const errorsToSuppress = [
"Run an Ionic project on a connected device",
"Error: Unhandled error. ('[ios-sim] Simulator already running.\\n')",
];
export function execCommand(
command: string,
args: string[],
errorLogger: (message: string) => void,
): Promise<string> {
return new Promise<string>((resolve, reject) => {
const proc = child_process.spawn(command, args, { stdio: "pipe" });
let stderr = "";
let stdout = "";
proc.stderr.on("data", (data: Buffer) => {
stderr += data.toString();
});
proc.stdout.on("data", (data: Buffer) => {
stdout += data.toString();
});
proc.on("error", (err: Error) => {
reject(err);
});
proc.on("close", (code: number) => {
if (code !== 0) {
errorLogger(stderr);
errorLogger(stdout);
reject(
new Error(
localize(
"ErrorRunningCommand",
"Error running {0} {1}",
command,
args.join(" "),
),
),
);
}
resolve(stdout);
});
});
}
export function cordovaRunCommand(
command: string,
args: string[],
env,
cordovaRootPath: string,
outputLogger?: DebugConsoleLogger,
): Promise<string[]> {
return new Promise<string[]>((resolve, reject) => {
const isIonicProject = CordovaProjectHelper.isIonicAngularProject(cordovaRootPath);
let output = "";
let stderr = "";
const cordovaProcess = cordovaStartCommand(command, args, env, cordovaRootPath);
// Prevent these lines to be shown more than once
// to prevent debug console pollution
const isShown = {
"Running command": false,
"cordova prepare": false,
"cordova platform add": false,
};
// There is error about run command with specified iOS simulator.
// If this simulator is online error "Error: Unhandled error. ('[ios-sim] Simulator already running.\\n')"
// is shown
let skipCommandReject = false;
cordovaProcess.stderr.on("data", data => {
stderr += data.toString();
for (let i = 0; i < errorsToSuppress.length; i++) {
if (data.toString().includes(errorsToSuppress[i])) {
if (
data
.toString()
.includes(
"Error: Unhandled error. ('[ios-sim] Simulator already running.\\n')",
)
) {
skipCommandReject = true;
}
return;
}
}
outputLogger && outputLogger(data.toString(), "stderr");
});
cordovaProcess.stdout.on("data", (data: Buffer) => {
const str = data
.toString()
.replace(/\u001b/g, "")
.replace(/\[2K\[G/g, ""); // Erasing `[2K[G` artifacts from DEBUG CONSOLE output
output += str;
// eslint-disable-next-line
for (const message in isShown) {
if (str.includes(message)) {
if (!isShown[message]) {
isShown[message] = true;
outputLogger && outputLogger(str, "stdout");
}
return;
}
}
outputLogger && outputLogger(str, "stdout");
if (isIonicProject && str.includes("LAUNCH SUCCESS")) {
resolve([output, stderr]);
}
});
cordovaProcess.on("exit", exitCode => {
if (exitCode && !skipCommandReject) {
reject(
new Error(
localize(
"CommandFailedWithExitCode",
"{0} {1} failed with exit code {2}",
command,
args.join(" "),
exitCode,
),
),
);
} else {
resolve([output, stderr]);
}
});
cordovaProcess.on("error", error => {
reject(error);
});
});
}
export function cordovaStartCommand(
command: string,
args: string[],
env: any,
cordovaRootPath: string,
): child_process.ChildProcess {
const isIonic = CordovaProjectHelper.isIonicAngularProject(cordovaRootPath);
const isIonicServe: boolean = args.includes("serve");
if (isIonic && !isIonicServe) {
const isIonicCliVersionGte3 = CordovaProjectHelper.isIonicCliVersionGte3(
cordovaRootPath,
command,
);
if (isIonicCliVersionGte3) {
args.unshift("cordova");
}
}
if (isIonic) {
args.push("--no-interactive");
} else {
args.push("--no-update-notifier");
}
return child_process.spawn(command, args, { cwd: cordovaRootPath, env });
}
export function killTree(processId: number): void {
const cmd =
process.platform === "win32"
? "taskkill.exe /F /T /PID"
: path.join(findFileInFolderHierarchy(__dirname, "scripts"), "terminateProcess.sh");
try {
child_process.execSync(`${cmd} ${processId}`);
} catch (err) {}
}
export function killChildProcess(childProcess: child_process.ChildProcess): Promise<void> {
killTree(childProcess.pid);
return Promise.resolve();
}

Просмотреть файл

@ -1,11 +1,15 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
import { ICordovaAttachRequestArgs } from "./requestArgs";
import { logger } from "vscode-debugadapter";
import { CordovaProjectHelper } from "../utils/cordovaProjectHelper";
import * as nls from "vscode-nls";
nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
import { CordovaProjectHelper } from "../utils/cordovaProjectHelper";
import { ICordovaAttachRequestArgs } from "./requestArgs";
nls.config({
messageFormat: nls.MessageFormat.bundle,
bundleFormat: nls.BundleFormat.standalone,
})();
const localize = nls.loadMessageBundle();
export interface IStringDictionary<T> {
@ -30,8 +34,13 @@ export class JsDebugConfigAdapter {
"../../*": "${cwd}/*",
};
public createChromeDebuggingConfig(attachArgs: ICordovaAttachRequestArgs, cdpProxyPort: number, pwaSessionName: string, sessionId: string): any {
let extraArgs: any = {};
public createChromeDebuggingConfig(
attachArgs: ICordovaAttachRequestArgs,
cdpProxyPort: number,
pwaSessionName: string,
sessionId: string,
): any {
const extraArgs: any = {};
if (!attachArgs.simulatePort) {
extraArgs.pathMapping = {
"/android_asset/www": `${attachArgs.cwd}/www`,
@ -65,17 +74,22 @@ export class JsDebugConfigAdapter {
});
}
public createSafariDebuggingConfig(attachArgs: ICordovaAttachRequestArgs, cdpProxyPort: number, pwaSessionName: string, sessionId: string): any {
let extraArgs: any = {};
public createSafariDebuggingConfig(
attachArgs: ICordovaAttachRequestArgs,
cdpProxyPort: number,
pwaSessionName: string,
sessionId: string,
): any {
const extraArgs: any = {};
if (attachArgs.ionicLiveReload) {
// Pointing js-debug to use all the sourcemaps urls sent with sources data
extraArgs.resolveSourceMapLocations = [
"**",
"!**/node_modules/**",
];
extraArgs.resolveSourceMapLocations = ["**", "!**/node_modules/**"];
if (CordovaProjectHelper.determineIonicMajorVersion(attachArgs.cwd) === 3) {
if (attachArgs.sourceMapPathOverrides) {
attachArgs.sourceMapPathOverrides = Object.assign(attachArgs.sourceMapPathOverrides, this.Ionic3LiveReloadSourceMapPathOverrides);
attachArgs.sourceMapPathOverrides = Object.assign(
attachArgs.sourceMapPathOverrides,
this.Ionic3LiveReloadSourceMapPathOverrides,
);
} else {
attachArgs.sourceMapPathOverrides = Object.assign(
{},
@ -95,15 +109,12 @@ export class JsDebugConfigAdapter {
// debug sessions from other ones. So we can save and process only the extension's debug sessions
// in vscode.debug API methods "onDidStartDebugSession" and "onDidTerminateDebugSession".
cordovaDebugSessionId: sessionId,
outFiles: [
"${workspaceFolder}/**/*.js",
"!{**/node_modules/**,platforms/**}",
],
outFiles: ["${workspaceFolder}/**/*.js", "!{**/node_modules/**,platforms/**}"],
});
}
private getExistingExtraArgs(attachArgs: ICordovaAttachRequestArgs): any {
let existingExtraArgs: any = {};
const existingExtraArgs: any = {};
if (attachArgs.env) {
existingExtraArgs.env = attachArgs.env;
}
@ -115,7 +126,7 @@ export class JsDebugConfigAdapter {
}
existingExtraArgs.sourceMapPathOverrides = this.getSourceMapPathOverrides(
attachArgs.cwd,
attachArgs.sourceMapPathOverrides || this.DefaultWebSourceMapPathOverrides
attachArgs.sourceMapPathOverrides || this.DefaultWebSourceMapPathOverrides,
);
if (attachArgs.skipFiles) {
existingExtraArgs.skipFiles = attachArgs.skipFiles;
@ -130,34 +141,68 @@ export class JsDebugConfigAdapter {
return existingExtraArgs;
}
private getSourceMapPathOverrides(cwd: string, sourceMapPathOverrides?: ISourceMapPathOverrides): ISourceMapPathOverrides {
return sourceMapPathOverrides ? this.resolveWebRootPattern(cwd, sourceMapPathOverrides, /*warnOnMissing=*/true) :
this.resolveWebRootPattern(cwd, this.DefaultWebSourceMapPathOverrides, /*warnOnMissing=*/false);
private getSourceMapPathOverrides(
cwd: string,
sourceMapPathOverrides?: ISourceMapPathOverrides,
): ISourceMapPathOverrides {
return sourceMapPathOverrides
? this.resolveWebRootPattern(cwd, sourceMapPathOverrides, /* warnOnMissing=*/ true)
: this.resolveWebRootPattern(
cwd,
this.DefaultWebSourceMapPathOverrides,
/* warnOnMissing=*/ false,
);
}
/**
* Returns a copy of sourceMapPathOverrides with the ${cwd} pattern resolved in all entries.
*/
private resolveWebRootPattern(cwd: string, sourceMapPathOverrides: ISourceMapPathOverrides, warnOnMissing: boolean): ISourceMapPathOverrides {
private resolveWebRootPattern(
cwd: string,
sourceMapPathOverrides: ISourceMapPathOverrides,
warnOnMissing: boolean,
): ISourceMapPathOverrides {
const resolvedOverrides: ISourceMapPathOverrides = {};
// tslint:disable-next-line:forin
for (let pattern in sourceMapPathOverrides) {
const replacePattern = this.replaceWebRootInSourceMapPathOverridesEntry(cwd, pattern, warnOnMissing);
const replacePatternValue = this.replaceWebRootInSourceMapPathOverridesEntry(cwd, sourceMapPathOverrides[pattern], warnOnMissing);
// eslint-disable-next-line
for (const pattern in sourceMapPathOverrides) {
const replacePattern = this.replaceWebRootInSourceMapPathOverridesEntry(
cwd,
pattern,
warnOnMissing,
);
const replacePatternValue = this.replaceWebRootInSourceMapPathOverridesEntry(
cwd,
sourceMapPathOverrides[pattern],
warnOnMissing,
);
resolvedOverrides[replacePattern] = replacePatternValue;
}
return resolvedOverrides;
}
private replaceWebRootInSourceMapPathOverridesEntry(cwd: string, entry: string, warnOnMissing: boolean): string {
private replaceWebRootInSourceMapPathOverridesEntry(
cwd: string,
entry: string,
warnOnMissing: boolean,
): string {
const cwdIndex = entry.indexOf("${cwd}");
if (cwdIndex === 0) {
if (cwd) {
return entry.replace("${cwd}", cwd);
} else if (warnOnMissing) {
logger.warn(localize("SourceMapOverridesEntryContainsCwd", "Warning: sourceMapPathOverrides entry contains ${cwd}, but cwd is not set"));
logger.warn(
localize(
"SourceMapOverridesEntryContainsCwd",
"Warning: sourceMapPathOverrides entry contains ${cwd}, but cwd is not set",
),
);
}
} else if (cwdIndex > 0) {
logger.warn(localize("InASourceMapOverridesEntryCwdValidAtTheBeginning", "Warning: in a sourceMapPathOverrides entry, ${cwd} is only valid at the beginning of the path"));
logger.warn(
localize(
"InASourceMapOverridesEntryCwdValidAtTheBeginning",
"Warning: in a sourceMapPathOverrides entry, ${cwd} is only valid at the beginning of the path",
),
);
}
return entry;
}

Просмотреть файл

@ -1,11 +1,11 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
import { ISourceMapPathOverrides } from "./jsDebugConfigAdapter";
import { DebugProtocol } from "vscode-debugprotocol";
import { ISourceMapPathOverrides } from "./jsDebugConfigAdapter";
export interface ICordovaAttachRequestArgs extends DebugProtocol.AttachRequestArguments {
cwd: string; /* Automatically set by VS Code to the currently opened folder */
cwd: string /* Automatically set by VS Code to the currently opened folder */;
port: number;
request: string;
url?: string;
@ -43,7 +43,9 @@ export interface ICordovaAttachRequestArgs extends DebugProtocol.AttachRequestAr
livereload?: boolean;
}
export interface ICordovaLaunchRequestArgs extends DebugProtocol.LaunchRequestArguments, ICordovaAttachRequestArgs {
export interface ICordovaLaunchRequestArgs
extends DebugProtocol.LaunchRequestArguments,
ICordovaAttachRequestArgs {
iosDebugProxyPort?: number;
// Ionic livereload properties

Просмотреть файл

@ -3,15 +3,22 @@
import * as fs from "fs";
import {
CompletionItem, CompletionItemKind, CompletionItemProvider,
DocumentSelector, SnippetString
CompletionItem,
CompletionItemKind,
CompletionItemProvider,
DocumentSelector,
SnippetString,
} from "vscode";
import * as nls from "vscode-nls";
nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
nls.config({
messageFormat: nls.MessageFormat.bundle,
bundleFormat: nls.BundleFormat.standalone,
})();
const localize = nls.loadMessageBundle();
// Types to outline TextMate snippets format, used in this extension's snippet files
type TMSnippet = { prefix: string, body: string[], description: string };
type TMSnippet = { prefix: string; body: string[]; description: string };
type TMSnippets = { [name: string]: TMSnippet };
export class IonicCompletionProvider implements CompletionItemProvider {
@ -20,10 +27,9 @@ export class IonicCompletionProvider implements CompletionItemProvider {
private snippetCompletions: CompletionItem[];
constructor(private completionsSource: string) { }
constructor(private completionsSource: string) {}
public provideCompletionItems(): CompletionItem[] {
if (this.snippetCompletions) {
return this.snippetCompletions;
}
@ -31,13 +37,21 @@ export class IonicCompletionProvider implements CompletionItemProvider {
this.snippetCompletions = [];
try {
let rawSnippets: TMSnippets = JSON.parse(fs.readFileSync(this.completionsSource, "utf8"));
this.snippetCompletions = Object.keys(rawSnippets)
.map(name => makeCompletionItem(rawSnippets[name]));
const rawSnippets: TMSnippets = JSON.parse(
fs.readFileSync(this.completionsSource, "utf8"),
);
this.snippetCompletions = Object.keys(rawSnippets).map(name =>
makeCompletionItem(rawSnippets[name]),
);
} catch (err) {
// Log error here and do not try to read snippets anymore
console.warn(localize("FailedToReadSnippetsFrom", "Failed to read snippets from {0}", this.completionsSource));
console.warn(
localize(
"FailedToReadSnippetsFrom",
"Failed to read snippets from {0}",
this.completionsSource,
),
);
}
return this.snippetCompletions;

Просмотреть файл

@ -1,13 +1,12 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
import * as vscode from "vscode";
import * as Net from "net";
import * as vscode from "vscode";
import { CordovaDebugSession } from "../debugger/cordovaDebugSession";
import { CordovaSession, CordovaSessionStatus } from "../debugger/debugSessionWrapper";
export class CordovaSessionManager implements vscode.DebugAdapterDescriptorFactory {
protected readonly cordovaDebuggingFlag = "isCordovaDebugging";
private servers = new Map<string, Net.Server>();
@ -15,14 +14,17 @@ export class CordovaSessionManager implements vscode.DebugAdapterDescriptorFacto
private cordovaDebugSessions = new Map<string, CordovaSession>();
private restartingVSCodeSessions = new Set<string>();
public createDebugAdapterDescriptor(session: vscode.DebugSession, executable: vscode.DebugAdapterExecutable | undefined): vscode.ProviderResult<vscode.DebugAdapterDescriptor> {
public createDebugAdapterDescriptor(
session: vscode.DebugSession,
executable: vscode.DebugAdapterExecutable | undefined,
): vscode.ProviderResult<vscode.DebugAdapterDescriptor> {
const cordovaSession = this.createCordovaSession(session);
this.cordovaDebugSessions.set(cordovaSession.getSessionId(), cordovaSession);
vscode.commands.executeCommand("setContext", this.cordovaDebuggingFlag, true);
const debugServer = Net.createServer(socket => {
let cordovaDebugSession = new CordovaDebugSession(cordovaSession, this);
const cordovaDebugSession = new CordovaDebugSession(cordovaSession, this);
cordovaDebugSession.setRunAsServer(true);
this.connections.set(cordovaSession.getSessionId(), socket);
cordovaDebugSession.start(<NodeJS.ReadableStream>socket, socket);
@ -33,9 +35,15 @@ export class CordovaSessionManager implements vscode.DebugAdapterDescriptorFacto
return new vscode.DebugAdapterServer((<Net.AddressInfo>debugServer.address()).port);
}
public terminate(cordovaDebugSessionId: string, restart: boolean = false, forcedStop: boolean = false): void {
public terminate(
cordovaDebugSessionId: string,
restart: boolean = false,
forcedStop: boolean = false,
): void {
if (restart && this.cordovaDebugSessions.has(cordovaDebugSessionId)) {
this.restartingVSCodeSessions.add(this.cordovaDebugSessions.get(cordovaDebugSessionId).getVSCodeDebugSession().id);
this.restartingVSCodeSessions.add(
this.cordovaDebugSessions.get(cordovaDebugSessionId).getVSCodeDebugSession().id,
);
}
this.cordovaDebugSessions.delete(cordovaDebugSessionId);
if (this.cordovaDebugSessions.size === 0) {
@ -44,7 +52,7 @@ export class CordovaSessionManager implements vscode.DebugAdapterDescriptorFacto
this.destroyServer(cordovaDebugSessionId, this.servers.get(cordovaDebugSessionId));
let connection = this.connections.get(cordovaDebugSessionId);
const connection = this.connections.get(cordovaDebugSessionId);
if (connection) {
if (forcedStop) {
this.destroySocketConnection(connection);
@ -54,7 +62,7 @@ export class CordovaSessionManager implements vscode.DebugAdapterDescriptorFacto
}
public getCordovaDebugSessionByProjectRoot(projectRoot: string): CordovaSession | null {
for (let cordovaSession of this.cordovaDebugSessions.values()) {
for (const cordovaSession of this.cordovaDebugSessions.values()) {
if (cordovaSession.getVSCodeDebugSession().workspaceFolder.uri.fsPath === projectRoot) {
return cordovaSession;
}
@ -74,7 +82,7 @@ export class CordovaSessionManager implements vscode.DebugAdapterDescriptorFacto
}
private createCordovaSession(session: vscode.DebugSession): CordovaSession {
let cordovaSession = new CordovaSession(session);
const cordovaSession = new CordovaSession(session);
if (this.restartingVSCodeSessions.has(session.id)) {
cordovaSession.setStatus(CordovaSessionStatus.Pending);
this.restartingVSCodeSessions.delete(session.id);

Просмотреть файл

@ -1,16 +1,20 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
import { PluginSimulator } from "./simulate";
import { SimulationInfo } from "../common/simulationInfo";
import { SimulateOptions } from "cordova-simulate";
import * as vscode from "vscode";
import * as nls from "vscode-nls";
import { SimulationInfo } from "../common/simulationInfo";
import { ProjectType } from "../utils/cordovaProjectHelper";
import { CordovaCommandHelper } from "../utils/cordovaCommandHelper";
import { ProjectsStorage } from "./projectsStorage";
import * as nls from "vscode-nls";
import { createAdditionalWorkspaceFolder, onFolderAdded } from "../cordova";
nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
import { ProjectsStorage } from "./projectsStorage";
import { PluginSimulator } from "./simulate";
nls.config({
messageFormat: nls.MessageFormat.bundle,
bundleFormat: nls.BundleFormat.standalone,
})();
const localize = nls.loadMessageBundle();
export class CordovaWorkspaceManager implements vscode.Disposable {
@ -22,16 +26,25 @@ export class CordovaWorkspaceManager implements vscode.Disposable {
this.pluginSimulator = pluginSimulator;
}
public static getWorkspaceManagerByProjectRootPath(projectRootPath: string): CordovaWorkspaceManager {
public static getWorkspaceManagerByProjectRootPath(
projectRootPath: string,
): CordovaWorkspaceManager {
let workspaceManager = ProjectsStorage.projectsCache[projectRootPath.toLowerCase()];
if (!workspaceManager) {
const workspaceFolder = createAdditionalWorkspaceFolder(projectRootPath);
if (workspaceFolder) {
onFolderAdded(workspaceFolder);
workspaceManager = ProjectsStorage.projectsCache[workspaceFolder.uri.fsPath.toLowerCase()];
workspaceManager =
ProjectsStorage.projectsCache[workspaceFolder.uri.fsPath.toLowerCase()];
}
if (!workspaceManager) {
throw new Error(localize("CouldntFindWorkspaceManager", "Could not find workspace manager by the project root path {0}", projectRootPath));
throw new Error(
localize(
"CouldntFindWorkspaceManager",
"Could not find workspace manager by the project root path {0}",
projectRootPath,
),
);
}
}
return workspaceManager;
@ -53,11 +66,16 @@ export class CordovaWorkspaceManager implements vscode.Disposable {
*
* Returns info about the running simulate server
*/
public simulate(fsPath: string, simulateOptions: SimulateOptions, projectType: ProjectType): Promise<SimulationInfo> {
return this.launchSimulateServer(fsPath, simulateOptions, projectType)
.then((simulateInfo: SimulationInfo) => {
public simulate(
fsPath: string,
simulateOptions: SimulateOptions,
projectType: ProjectType,
): Promise<SimulationInfo> {
return this.launchSimulateServer(fsPath, simulateOptions, projectType).then(
(simulateInfo: SimulationInfo) => {
return this.launchSimHost(simulateOptions.target).then(() => simulateInfo);
});
},
);
}
/**
@ -65,7 +83,11 @@ export class CordovaWorkspaceManager implements vscode.Disposable {
*
* Returns info about the running simulate server
*/
public launchSimulateServer(fsPath: string, simulateOptions: SimulateOptions, projectType: ProjectType): Promise<SimulationInfo> {
public launchSimulateServer(
fsPath: string,
simulateOptions: SimulateOptions,
projectType: ProjectType,
): Promise<SimulationInfo> {
return this.pluginSimulator.launchServer(fsPath, simulateOptions, projectType);
}

Просмотреть файл

@ -2,199 +2,244 @@
// Licensed under the MIT license. See LICENSE file in the project root for details.
import * as vscode from "vscode";
import * as nls from "vscode-nls";
import { TelemetryHelper } from "../utils/telemetryHelper";
import { Telemetry } from "../utils/telemetry";
import * as nls from "vscode-nls";
nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
nls.config({
messageFormat: nls.MessageFormat.bundle,
bundleFormat: nls.BundleFormat.standalone,
})();
const localize = nls.loadMessageBundle();
export class CordovaDebugConfigProvider implements vscode.DebugConfigurationProvider {
private debugConfigurations = {
"Run Android on emulator": {
"name": "Run Android on emulator",
"type": "cordova",
"request": "launch",
"platform": "android",
"target": "emulator",
"port": 9222,
"sourceMaps": true,
"cwd": "${workspaceFolder}",
name: "Run Android on emulator",
type: "cordova",
request: "launch",
platform: "android",
target: "emulator",
port: 9222,
sourceMaps: true,
cwd: "${workspaceFolder}",
},
"Attach to running Android on emulator": {
"name": "Attach to running Android on emulator",
"type": "cordova",
"request": "attach",
"platform": "android",
"target": "emulator",
"port": 9222,
"sourceMaps": true,
"cwd": "${workspaceFolder}",
name: "Attach to running Android on emulator",
type: "cordova",
request: "attach",
platform: "android",
target: "emulator",
port: 9222,
sourceMaps: true,
cwd: "${workspaceFolder}",
},
"Run Android on device": {
"name": "Run Android on device",
"type": "cordova",
"request": "launch",
"platform": "android",
"target": "device",
"port": 9222,
"sourceMaps": true,
"cwd": "${workspaceFolder}",
name: "Run Android on device",
type: "cordova",
request: "launch",
platform: "android",
target: "device",
port: 9222,
sourceMaps: true,
cwd: "${workspaceFolder}",
},
"Attach to running Android on device": {
"name": "Attach to running Android on device",
"type": "cordova",
"request": "attach",
"platform": "android",
"target": "device",
"port": 9222,
"sourceMaps": true,
"cwd": "${workspaceFolder}",
name: "Attach to running Android on device",
type: "cordova",
request: "attach",
platform: "android",
target: "device",
port: 9222,
sourceMaps: true,
cwd: "${workspaceFolder}",
},
"Run iOS on device": {
"name": "Run iOS on device",
"type": "cordova",
"request": "launch",
"platform": "ios",
"target": "device",
"port": 9220,
"sourceMaps": true,
"cwd": "${workspaceFolder}",
name: "Run iOS on device",
type: "cordova",
request: "launch",
platform: "ios",
target: "device",
port: 9220,
sourceMaps: true,
cwd: "${workspaceFolder}",
},
"Attach to running iOS on device": {
"name": "Attach to running iOS on device",
"type": "cordova",
"request": "attach",
"platform": "ios",
"target": "device",
"port": 9220,
"sourceMaps": true,
"cwd": "${workspaceFolder}",
name: "Attach to running iOS on device",
type: "cordova",
request: "attach",
platform: "ios",
target: "device",
port: 9220,
sourceMaps: true,
cwd: "${workspaceFolder}",
},
"Run iOS on simulator - experimental": {
"name": "Run iOS on simulator - experimental",
"type": "cordova",
"request": "launch",
"platform": "ios",
"target": "emulator",
"port": 9220,
"sourceMaps": true,
"cwd": "${workspaceFolder}",
name: "Run iOS on simulator - experimental",
type: "cordova",
request: "launch",
platform: "ios",
target: "emulator",
port: 9220,
sourceMaps: true,
cwd: "${workspaceFolder}",
},
"Attach to running iOS on simulator - experimental": {
"name": "Attach to running iOS on simulator - experimental",
"type": "cordova",
"request": "attach",
"platform": "ios",
"target": "emulator",
"port": 9220,
"sourceMaps": true,
"cwd": "${workspaceFolder}",
name: "Attach to running iOS on simulator - experimental",
type: "cordova",
request: "attach",
platform: "ios",
target: "emulator",
port: 9220,
sourceMaps: true,
cwd: "${workspaceFolder}",
},
"Serve to the browser (Ionic Serve)": {
"name": "Serve to the browser (Ionic Serve)",
"type": "cordova",
"request": "launch",
"platform": "serve",
"target": "chrome",
"cwd": "${workspaceFolder}",
"devServerAddress": "localhost",
"sourceMaps": true,
"ionicLiveReload": true,
name: "Serve to the browser (Ionic Serve)",
type: "cordova",
request: "launch",
platform: "serve",
target: "chrome",
cwd: "${workspaceFolder}",
devServerAddress: "localhost",
sourceMaps: true,
ionicLiveReload: true,
},
"Simulate Android in browser": {
"name": "Simulate Android in browser",
"type": "cordova",
"request": "launch",
"platform": "android",
"target": "chrome",
"simulatePort": 8000,
"livereload": true,
"sourceMaps": true,
"cwd": "${workspaceFolder}",
name: "Simulate Android in browser",
type: "cordova",
request: "launch",
platform: "android",
target: "chrome",
simulatePort: 8000,
livereload: true,
sourceMaps: true,
cwd: "${workspaceFolder}",
},
"Simulate iOS in browser": {
"name": "Simulate iOS in browser",
"type": "cordova",
"request": "launch",
"platform": "ios",
"target": "chrome",
"simulatePort": 8000,
"livereload": true,
"sourceMaps": true,
"cwd": "${workspaceFolder}",
name: "Simulate iOS in browser",
type: "cordova",
request: "launch",
platform: "ios",
target: "chrome",
simulatePort: 8000,
livereload: true,
sourceMaps: true,
cwd: "${workspaceFolder}",
},
"Run Browser": {
"name": "Run Browser",
"type": "cordova",
"request": "launch",
"platform": "browser",
"target": "chrome",
"simulatePort": 8000,
"livereload": true,
"sourceMaps": true,
"cwd": "${workspaceFolder}",
name: "Run Browser",
type: "cordova",
request: "launch",
platform: "browser",
target: "chrome",
simulatePort: 8000,
livereload: true,
sourceMaps: true,
cwd: "${workspaceFolder}",
},
};
private pickConfig: ReadonlyArray<vscode.QuickPickItem> = [
{
label: "Run Android on emulator",
description: localize("RunAndDebugCordovaAppOnAndroidEmulator", "Run and debug Cordova app on Android emulator"),
description: localize(
"RunAndDebugCordovaAppOnAndroidEmulator",
"Run and debug Cordova app on Android emulator",
),
},
{
label: "Attach to running Android on emulator",
description: localize("AttachToRunningCordovaAppOnAndroidEmulator", "Attach to running Cordova app on Android emulator"),
description: localize(
"AttachToRunningCordovaAppOnAndroidEmulator",
"Attach to running Cordova app on Android emulator",
),
},
{
label: "Run Android on device",
description: localize("RunAndDebugCordovaAppOnAndroidDevice", "Run and debug Cordova app on Android device"),
description: localize(
"RunAndDebugCordovaAppOnAndroidDevice",
"Run and debug Cordova app on Android device",
),
},
{
label: "Attach to running Android on device",
description: localize("AttachToRunningCordovaAppOnAndroidDevice", "Attach to running Cordova app on Android device"),
description: localize(
"AttachToRunningCordovaAppOnAndroidDevice",
"Attach to running Cordova app on Android device",
),
},
{
label: "Run iOS on simulator - experimental",
description: localize("RunAndDebugCordovaAppOniOSSimulator", "Run and debug Cordova app on iOS simulator"),
description: localize(
"RunAndDebugCordovaAppOniOSSimulator",
"Run and debug Cordova app on iOS simulator",
),
},
{
label: "Attach to running iOS on simulator - experimental",
description: localize("AttachToRunningCordovaAppOniOSSimulator", "Attach to running Cordova app on iOS simulator"),
description: localize(
"AttachToRunningCordovaAppOniOSSimulator",
"Attach to running Cordova app on iOS simulator",
),
},
{
label: "Run iOS on device",
description: localize("RunAndDebugCordovaAppOniOSDevice", "Run and debug Cordova app on iOS device"),
description: localize(
"RunAndDebugCordovaAppOniOSDevice",
"Run and debug Cordova app on iOS device",
),
},
{
label: "Attach to running iOS on device",
description: localize("AttachToRunningCordovaAppOniOSDevice", "Attach to running Cordova app on iOS device"),
description: localize(
"AttachToRunningCordovaAppOniOSDevice",
"Attach to running Cordova app on iOS device",
),
},
{
label: "Serve to the browser (Ionic Serve)",
description: localize("ServeToTheBrowser", "Serve to the browser (currently supported only for Ionic)"),
description: localize(
"ServeToTheBrowser",
"Serve to the browser (currently supported only for Ionic)",
),
},
{
label: "Simulate Android in browser",
description: localize("SimulateCordovaAndroidAppInBrowser", "Simulate Cordova Android application in browser"),
description: localize(
"SimulateCordovaAndroidAppInBrowser",
"Simulate Cordova Android application in browser",
),
},
{
label: "Simulate iOS in browser",
description: localize("SimulateCordovaiOSAppInBrowser", "Simulate Cordova iOS application in browser"),
description: localize(
"SimulateCordovaiOSAppInBrowser",
"Simulate Cordova iOS application in browser",
),
},
{
label: "Run Browser",
description: localize("RunAndDebugCordovaAppInBrowser", "Run and debug Cordova application in browser"),
description: localize(
"RunAndDebugCordovaAppInBrowser",
"Run and debug Cordova application in browser",
),
},
];
public async provideDebugConfigurations(folder: vscode.WorkspaceFolder | undefined, token?: vscode.CancellationToken): Promise<vscode.DebugConfiguration[]> {
return new Promise<vscode.DebugConfiguration[]>((resolve) => {
public async provideDebugConfigurations(
folder: vscode.WorkspaceFolder | undefined,
token?: vscode.CancellationToken,
): Promise<vscode.DebugConfiguration[]> {
return new Promise<vscode.DebugConfiguration[]>(resolve => {
const configPicker = this.prepareDebugConfigPicker();
const disposables: vscode.Disposable[] = [];
const pickHandler = () => {
let chosenConfigsEvent = TelemetryHelper.createTelemetryEvent("chosenDebugConfigurations");
let selected: string[] = configPicker.selectedItems.map(element => element.label);
chosenConfigsEvent.properties["selectedItems"] = selected;
const chosenConfigsEvent = TelemetryHelper.createTelemetryEvent(
"chosenDebugConfigurations",
);
const selected: string[] = configPicker.selectedItems.map(element => element.label);
chosenConfigsEvent.properties.selectedItems = selected;
Telemetry.send(chosenConfigsEvent);
const launchConfig = this.gatherDebugScenarios(selected);
disposables.forEach(d => d.dispose());
@ -204,7 +249,7 @@ export class CordovaDebugConfigProvider implements vscode.DebugConfigurationProv
disposables.push(
configPicker.onDidAccept(pickHandler),
configPicker.onDidHide(pickHandler),
configPicker
configPicker,
);
configPicker.show();
@ -212,7 +257,9 @@ export class CordovaDebugConfigProvider implements vscode.DebugConfigurationProv
}
private gatherDebugScenarios(selectedItems: string[]): vscode.DebugConfiguration[] {
let launchConfig: vscode.DebugConfiguration[] = selectedItems.map(element => this.debugConfigurations[element]);
const launchConfig: vscode.DebugConfiguration[] = selectedItems.map(
element => this.debugConfigurations[element],
);
return launchConfig;
}

Просмотреть файл

@ -2,12 +2,17 @@
// Licensed under the MIT license. See LICENSE file in the project root for details.
import * as vscode from "vscode";
import {CordovaWorkspaceManager} from "./cordovaWorkspaceManager";
import { CordovaWorkspaceManager } from "./cordovaWorkspaceManager";
export class ProjectsStorage {
public static readonly projectsCache: {[key: string]: CordovaWorkspaceManager} = {};
public static readonly projectsCache: {
[key: string]: CordovaWorkspaceManager;
} = {};
public static addFolder(workspaceFolder: vscode.WorkspaceFolder, workspaceManager: CordovaWorkspaceManager): void {
public static addFolder(
workspaceFolder: vscode.WorkspaceFolder,
workspaceManager: CordovaWorkspaceManager,
): void {
this.projectsCache[workspaceFolder.uri.fsPath.toLowerCase()] = workspaceManager;
}
@ -19,8 +24,7 @@ export class ProjectsStorage {
let key = "";
if (typeof workspaceFolder === "string") {
key = workspaceFolder.toLowerCase();
}
else {
} else {
key = workspaceFolder.uri.fsPath.toLowerCase();
}
delete this.projectsCache[key];

Просмотреть файл

@ -2,18 +2,22 @@
// Licensed under the MIT license. See LICENSE file in the project root for details.
import * as path from "path";
import * as cp from "child_process";
import * as CordovaSimulate from "cordova-simulate";
import * as vscode from "vscode";
import * as nls from "vscode-nls";
import { CordovaSimulateTelemetry } from "../utils/cordovaSimulateTelemetry";
import { ProjectType, CordovaProjectHelper } from "../utils/cordovaProjectHelper";
import { SimulationInfo } from "../common/simulationInfo";
import { PlatformType } from "../debugger/cordovaDebugSession";
import * as vscode from "vscode";
import * as cp from "child_process";
import customRequire from "../common/customRequire";
import { OutputChannelLogger } from "../utils/log/outputChannelLogger";
import { findFileInFolderHierarchy } from "../utils/extensionHelper";
import * as nls from "vscode-nls";
nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
nls.config({
messageFormat: nls.MessageFormat.bundle,
bundleFormat: nls.BundleFormat.standalone,
})();
const localize = nls.loadMessageBundle();
export enum SimulateTargets {
@ -39,35 +43,52 @@ export class PluginSimulator implements vscode.Disposable {
private simulatePackage: typeof CordovaSimulate;
private packageInstallProc: cp.ChildProcess | null = null;
public simulate(fsPath: string, simulateOptions: CordovaSimulate.SimulateOptions, projectType: ProjectType): Promise<any> {
public simulate(
fsPath: string,
simulateOptions: CordovaSimulate.SimulateOptions,
projectType: ProjectType,
): Promise<any> {
return this.launchServer(fsPath, simulateOptions, projectType)
.then(() => this.launchSimHost(simulateOptions.target))
.then(() => this.launchAppHost(simulateOptions.target));
}
public launchAppHost(target: string): Promise<void> {
return this.getPackage()
.then(simulate => {
return simulate.launchBrowser(target, this.simulationInfo.appHostUrl);
});
return this.getPackage().then(simulate => {
return simulate.launchBrowser(target, this.simulationInfo.appHostUrl);
});
}
public launchSimHost(target: string): Promise<void> {
if (!this.simulator) {
return Promise.reject(new Error(localize("LaunchingSimHostBeforeStartSimulationServer", "Launching sim host before starting simulation server")));
return Promise.reject(
new Error(
localize(
"LaunchingSimHostBeforeStartSimulationServer",
"Launching sim host before starting simulation server",
),
),
);
}
return this.getPackage()
.then(simulate => {
return simulate.launchBrowser(target, this.simulator.simHostUrl());
});
return this.getPackage().then(simulate => {
return simulate.launchBrowser(target, this.simulator.simHostUrl());
});
}
public launchServer(fsPath: string, simulateOptions: CordovaSimulate.SimulateOptions, projectType: ProjectType): Promise<SimulationInfo> {
public launchServer(
fsPath: string,
simulateOptions: CordovaSimulate.SimulateOptions,
projectType: ProjectType,
): Promise<SimulationInfo> {
const uri = vscode.Uri.file(fsPath);
const workspaceFolder = <vscode.WorkspaceFolder>vscode.workspace.getWorkspaceFolder(uri);
simulateOptions.dir = workspaceFolder.uri.fsPath;
if (!simulateOptions.simulationpath) {
simulateOptions.simulationpath = path.join(workspaceFolder.uri.fsPath, ".vscode", "simulate");
simulateOptions.simulationpath = path.join(
workspaceFolder.uri.fsPath,
".vscode",
"simulate",
);
}
return this.getPackage()
@ -78,43 +99,59 @@ export class PluginSimulator implements vscode.Disposable {
}
})
.then(() => {
let simulateTelemetryWrapper = new CordovaSimulateTelemetry();
const simulateTelemetryWrapper = new CordovaSimulateTelemetry();
simulateOptions.telemetry = simulateTelemetryWrapper;
this.simulator = new this.simulatePackage.Simulator(simulateOptions);
let platforms = CordovaProjectHelper.getInstalledPlatforms(workspaceFolder.uri.fsPath);
const platforms = CordovaProjectHelper.getInstalledPlatforms(
workspaceFolder.uri.fsPath,
);
let platform = simulateOptions.platform;
let isPlatformMissing = platform && platforms.indexOf(platform) < 0;
const platform = simulateOptions.platform;
const isPlatformMissing = platform && !platforms.includes(platform);
if (isPlatformMissing) {
let command = "cordova";
if (projectType.isIonic) {
const isIonicCliVersionGte3 = CordovaProjectHelper.isIonicCliVersionGte3(workspaceFolder.uri.fsPath);
command = "ionic" + (isIonicCliVersionGte3 ? " cordova" : "");
const isIonicCliVersionGte3 = CordovaProjectHelper.isIonicCliVersionGte3(
workspaceFolder.uri.fsPath,
);
command = `ionic${isIonicCliVersionGte3 ? " cordova" : ""}`;
}
throw new Error(localize("CouldntFindPlatformInProject", "Couldn't find platform {0} in project, please install it using '{1} platform add {2}'", platform, command, platform));
throw new Error(
localize(
"CouldntFindPlatformInProject",
"Couldn't find platform {0} in project, please install it using '{1} platform add {2}'",
platform,
command,
platform,
),
);
}
return this.simulator.startSimulation()
.then(() => {
if (!this.simulator.isRunning()) {
throw new Error(localize("ErrorStartingTheSimulation", "Error starting the simulation"));
}
return this.simulator.startSimulation().then(() => {
if (!this.simulator.isRunning()) {
throw new Error(
localize("ErrorStartingTheSimulation", "Error starting the simulation"),
);
}
this.simulationInfo = {
appHostUrl: this.simulator.appUrl(),
simHostUrl: this.simulator.simHostUrl(),
urlRoot: this.simulator.urlRoot(),
};
if ((projectType.ionicMajorVersion === 2 || projectType.ionicMajorVersion === 3)
&& platform && platform !== PlatformType.Browser
) {
this.simulationInfo.appHostUrl = `${this.simulationInfo.appHostUrl}?ionicplatform=${simulateOptions.platform}`;
}
return this.simulationInfo;
});
this.simulationInfo = {
appHostUrl: this.simulator.appUrl(),
simHostUrl: this.simulator.simHostUrl(),
urlRoot: this.simulator.urlRoot(),
};
if (
(projectType.ionicMajorVersion === 2 ||
projectType.ionicMajorVersion === 3) &&
platform &&
platform !== PlatformType.Browser
) {
this.simulationInfo.appHostUrl = `${this.simulationInfo.appHostUrl}?ionicplatform=${simulateOptions.platform}`;
}
return this.simulationInfo;
});
});
}
@ -125,7 +162,10 @@ export class PluginSimulator implements vscode.Disposable {
}
if (this.simulator) {
this.simulator.stopSimulation().then(() => { }, () => { });
this.simulator.stopSimulation().then(
() => {},
() => {},
);
this.simulator = null;
}
}
@ -141,7 +181,12 @@ export class PluginSimulator implements vscode.Disposable {
return Promise.resolve(this.simulatePackage);
} catch (e) {
if (e.code === "MODULE_NOT_FOUND") {
OutputChannelLogger.getMainChannel().log(localize("CordovaSimulateDepNotPresent", "cordova-simulate dependency not present. Installing it..."));
OutputChannelLogger.getMainChannel().log(
localize(
"CordovaSimulateDepNotPresent",
"cordova-simulate dependency not present. Installing it...",
),
);
} else {
throw e;
}
@ -149,17 +194,31 @@ export class PluginSimulator implements vscode.Disposable {
return new Promise((resolve, reject) => {
if (!this.packageInstallProc) {
this.packageInstallProc = cp.spawn(process.platform === "win32" ? "npm.cmd" : "npm",
this.packageInstallProc = cp.spawn(
process.platform === "win32" ? "npm.cmd" : "npm",
["install", this.CORDOVA_SIMULATE_PACKAGE, "--verbose", "--no-save"],
{ cwd: path.dirname(findFileInFolderHierarchy(__dirname, "package.json")) });
{
cwd: path.dirname(findFileInFolderHierarchy(__dirname, "package.json")),
},
);
this.packageInstallProc.once("exit", (code: number) => {
if (code === 0) {
this.simulatePackage = customRequire(this.CORDOVA_SIMULATE_PACKAGE);
resolve(this.simulatePackage);
} else {
OutputChannelLogger.getMainChannel().log(localize("ErrorWhileInstallingCordovaSimulateDep", "Error while installing cordova-simulate dependency to the extension"));
reject(localize("ErrorWhileInstallingCordovaSimulateDep", "Error while installing cordova-simulate dependency to the extension"));
OutputChannelLogger.getMainChannel().log(
localize(
"ErrorWhileInstallingCordovaSimulateDep",
"Error while installing cordova-simulate dependency to the extension",
),
);
reject(
localize(
"ErrorWhileInstallingCordovaSimulateDep",
"Error while installing cordova-simulate dependency to the extension",
),
);
}
});

Просмотреть файл

@ -8,6 +8,7 @@ import * as nls from "vscode-nls";
import { OutputChannelLogger } from "../log/outputChannelLogger";
import { ChildProcess, ISpawnResult } from "../../common/node/childProcess";
import { IDebuggableMobileTarget } from "../mobileTarget";
nls.config({
messageFormat: nls.MessageFormat.bundle,
bundleFormat: nls.BundleFormat.standalone,
@ -28,7 +29,6 @@ export enum AndroidAPILevel {
}
export class AdbHelper {
public static readonly AndroidSDKEmulatorPattern = /^emulator-\d{1,5}$/;
private childProcess: ChildProcess = new ChildProcess();
@ -64,46 +64,48 @@ export class AdbHelper {
}
public async getAvdNameById(emulatorId: string): Promise<string | null> {
return this.childProcess.execToString(`${this.adbExecutable} -s ${emulatorId} emu avd name`)
// The command returns the name of avd by id of this running emulator.
// Return value example:
// "
// emuName
// OK
// "
.then(output => {
if (output) {
// Return the name of avd: emuName
return output.split(/\r?\n|\r/g)[0];
} else {
return null;
}
})
// If the command returned an error, it means that we could not find the emulator with the passed id
.catch(() => null);
return (
this.childProcess
.execToString(`${this.adbExecutable} -s ${emulatorId} emu avd name`)
// The command returns the name of avd by id of this running emulator.
// Return value example:
// "
// emuName
// OK
// "
.then(output => {
if (output) {
// Return the name of avd: emuName
return output.split(/\r?\n|\r/g)[0];
}
return null;
})
// If the command returned an error, it means that we could not find the emulator with the passed id
.catch(() => null)
);
}
public async getAvdsNames(): Promise<string[]> {
const res = await this.childProcess.execToString(
"emulator -list-avds",
);
const res = await this.childProcess.execToString("emulator -list-avds");
let emulatorsNames: string[] = [];
if (res) {
emulatorsNames = res.split(/\r?\n|\r/g);
const indexOfBlank = emulatorsNames.indexOf("");
if (emulatorsNames.indexOf("") >= 0) {
if (emulatorsNames.includes("")) {
emulatorsNames.splice(indexOfBlank, 1);
}
}
return emulatorsNames;
}
public async findOnlineTargetById(targetId: string): Promise<IDebuggableMobileTarget | undefined> {
return (await this.getOnlineTargets()).find((target) => target.id === targetId);
public async findOnlineTargetById(
targetId: string,
): Promise<IDebuggableMobileTarget | undefined> {
return (await this.getOnlineTargets()).find(target => target.id === targetId);
}
public startLogCat(adbParameters: string[]): ISpawnResult {
return this.childProcess.spawn(this.adbExecutable.replace(/\"/g, ""), adbParameters);
return this.childProcess.spawn(this.adbExecutable.replace(/"/g, ""), adbParameters);
}
public parseSdkLocation(fileContent: string, logger?: OutputChannelLogger): string | null {
@ -146,8 +148,8 @@ export class AdbHelper {
}
private parseConnectedTargets(input: string): IDebuggableMobileTarget[] {
let result: IDebuggableMobileTarget[] = [];
let regex = new RegExp("^(\\S+)\\t(\\S+)$", "mg");
const result: IDebuggableMobileTarget[] = [];
const regex = new RegExp("^(\\S+)\\t(\\S+)$", "mg");
let match = regex.exec(input);
while (match != null) {
result.push({
@ -198,7 +200,13 @@ export class AdbHelper {
fileContent = fs.readFileSync(localPropertiesFilePath).toString();
} catch (e) {
if (logger) {
logger.log(`${localize("CouldNotReadFrom", "Couldn't read from {0}.", localPropertiesFilePath)}\n${e}\n${e.stack}`);
logger.log(
`${localize(
"CouldNotReadFrom",
"Couldn't read from {0}.",
localPropertiesFilePath,
)}\n${e}\n${e.stack}`,
);
logger.log(
localize(
"UsingAndroidSDKLocationFromPATH",

Просмотреть файл

@ -3,10 +3,10 @@
import * as nls from "vscode-nls";
import { MobileTargetManager } from "../mobileTargetManager";
import { AdbHelper } from "./adb";
import { ChildProcess } from "../../common/node/childProcess";
import { IDebuggableMobileTarget, IMobileTarget, MobileTarget } from "../mobileTarget";
import { TargetType } from "../../debugger/cordovaDebugSession";
import { AdbHelper } from "./adb";
nls.config({
messageFormat: nls.MessageFormat.bundle,
@ -15,7 +15,6 @@ nls.config({
const localize = nls.loadMessageBundle();
export class AndroidTarget extends MobileTarget {
public static fromInterface(obj: IDebuggableMobileTarget): AndroidTarget {
return new AndroidTarget(obj.isOnline, obj.isVirtualTarget, obj.id, obj.name);
}
@ -44,48 +43,60 @@ export class AndroidTargetManager extends MobileTargetManager {
try {
if (target === TargetType.Device) {
return false;
} else if (target === TargetType.Emulator || target.match(AdbHelper.AndroidSDKEmulatorPattern)) {
} else if (
target === TargetType.Emulator ||
target.match(AdbHelper.AndroidSDKEmulatorPattern)
) {
return true;
} else {
const onlineTarget = await this.adbHelper.findOnlineTargetById(target);
if (onlineTarget) {
return onlineTarget.isVirtualTarget;
} else if ((await this.adbHelper.getAvdsNames()).includes(target)) {
return true;
} else {
throw Error();
}
}
const onlineTarget = await this.adbHelper.findOnlineTargetById(target);
if (onlineTarget) {
return onlineTarget.isVirtualTarget;
} else if ((await this.adbHelper.getAvdsNames()).includes(target)) {
return true;
}
throw Error();
} catch {
throw new Error(localize("CouldNotRecognizeTargetType", "Could not recognize type of the target {0}", target));
throw new Error(
localize(
"CouldNotRecognizeTargetType",
"Could not recognize type of the target {0}",
target,
),
);
}
}
public async selectAndPrepareTarget(filter?: (el: IMobileTarget) => boolean): Promise<AndroidTarget | undefined> {
public async selectAndPrepareTarget(
filter?: (el: IMobileTarget) => boolean,
): Promise<AndroidTarget | undefined> {
const selectedTarget = await this.startSelection(filter);
if (selectedTarget) {
if (!selectedTarget.isOnline) {
return this.launchSimulator(selectedTarget);
} else {
if (selectedTarget.id) {
return AndroidTarget.fromInterface(<IDebuggableMobileTarget>selectedTarget);
}
}
if (selectedTarget.id) {
return AndroidTarget.fromInterface(<IDebuggableMobileTarget>selectedTarget);
}
}
}
public async collectTargets(targetType?: TargetType.Device | TargetType.Emulator): Promise<void> {
public async collectTargets(
targetType?: TargetType.Device | TargetType.Emulator,
): Promise<void> {
const targetList: IMobileTarget[] = [];
if (!targetType || targetType === TargetType.Emulator) {
const emulatorsNames: string[] = await this.adbHelper.getAvdsNames();
targetList.push(...emulatorsNames.map(name => {
return { name, isOnline: false, isVirtualTarget: true };
}));
targetList.push(
...emulatorsNames.map(name => {
return { name, isOnline: false, isVirtualTarget: true };
}),
);
}
const onlineTargets = await this.adbHelper.getOnlineTargets();
for (let device of onlineTargets) {
for (const device of onlineTargets) {
if (device.isVirtualTarget && (!targetType || targetType === TargetType.Emulator)) {
const avdName = await this.adbHelper.getAvdNameById(device.id);
const emulatorTarget = targetList.find(target => target.name === avdName);
@ -97,14 +108,20 @@ export class AndroidTargetManager extends MobileTargetManager {
!device.isVirtualTarget &&
(!targetType || targetType === TargetType.Device)
) {
targetList.push({ id: device.id, isOnline: true, isVirtualTarget: false });
targetList.push({
id: device.id,
isOnline: true,
isVirtualTarget: false,
});
}
}
this.targets = targetList;
}
protected async startSelection(filter?: (el: IMobileTarget) => boolean): Promise<IMobileTarget | undefined> {
protected async startSelection(
filter?: (el: IMobileTarget) => boolean,
): Promise<IMobileTarget | undefined> {
return this.selectTarget(filter);
}
@ -136,27 +153,39 @@ export class AndroidTargetManager extends MobileTargetManager {
emulatorProcess.spawnedProcess.unref();
const rejectTimeout = setTimeout(() => {
cleanup();
reject(new Error(`Virtual device launch finished with an exception: ${localize(
"EmulatorStartWarning",
"Could not start the emulator {0} within {1} seconds.",
emulatorTarget.name,
AndroidTargetManager.EMULATOR_START_TIMEOUT,
)}`));
cleanup(); // eslint-disable-line
reject(
new Error(
`Virtual device launch finished with an exception: ${localize(
"EmulatorStartWarning",
"Could not start the emulator {0} within {1} seconds.",
emulatorTarget.name,
AndroidTargetManager.EMULATOR_START_TIMEOUT,
)}`,
),
);
}, AndroidTargetManager.EMULATOR_START_TIMEOUT * 1000);
const bootCheckInterval = setInterval(async () => {
const connectedDevices = await this.adbHelper.getOnlineTargets();
for (let i = 0; i < connectedDevices.length; i++) {
const onlineAvdName = await this.adbHelper.getAvdNameById(connectedDevices[i].id);
const onlineAvdName = await this.adbHelper.getAvdNameById(
connectedDevices[i].id,
);
if (onlineAvdName === emulatorTarget.name) {
emulatorTarget.id = connectedDevices[i].id;
emulatorTarget.isOnline = true;
this.logger.log(
localize("EmulatorLaunched", "Launched emulator {0}", emulatorTarget.name),
localize(
"EmulatorLaunched",
"Launched emulator {0}",
emulatorTarget.name,
),
);
cleanup(); // eslint-disable-line
resolve(
AndroidTarget.fromInterface(<IDebuggableMobileTarget>emulatorTarget),
);
cleanup();
resolve(AndroidTarget.fromInterface(<IDebuggableMobileTarget>emulatorTarget));
break;
}
}

Просмотреть файл

@ -4,10 +4,14 @@
import * as child_process from "child_process";
import * as os from "os";
import { window, WorkspaceConfiguration, workspace, Uri, commands } from "vscode";
import * as nls from "vscode-nls";
import { CordovaSessionManager } from "../extension/cordovaSessionManager";
import { CordovaSessionStatus } from "../debugger/debugSessionWrapper";
import * as nls from "vscode-nls";
nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
nls.config({
messageFormat: nls.MessageFormat.bundle,
bundleFormat: nls.BundleFormat.standalone,
})();
const localize = nls.loadMessageBundle();
import { TelemetryHelper } from "./telemetryHelper";
@ -23,7 +27,11 @@ export class CordovaCommandHelper {
private static IONIC_DISPLAY_NAME: string = "Ionic";
private static readonly RESTART_SESSION_COMMAND: string = "workbench.action.debug.restart";
public static executeCordovaCommand(projectRoot: string, command: string, useIonic: boolean = false): Promise<void> {
public static executeCordovaCommand(
projectRoot: string,
command: string,
useIonic: boolean = false,
): Promise<void> {
let telemetryEventName: string = CordovaCommandHelper.CORDOVA_TELEMETRY_EVENT_NAME;
let cliCommandName: string = CordovaCommandHelper.CORDOVA_CMD_NAME;
let cliDisplayName: string = CordovaCommandHelper.CORDOVA_DISPLAY_NAME;
@ -34,13 +42,13 @@ export class CordovaCommandHelper {
cliDisplayName = CordovaCommandHelper.IONIC_DISPLAY_NAME;
}
return CordovaCommandHelper.selectPlatform(projectRoot, command, useIonic)
.then((platform) => {
TelemetryHelper.generate(telemetryEventName, (generator) => {
return CordovaCommandHelper.selectPlatform(projectRoot, command, useIonic).then(
platform => {
TelemetryHelper.generate(telemetryEventName, generator => {
generator.add("command", command, false);
let logger = OutputChannelLogger.getMainChannel();
const logger = OutputChannelLogger.getMainChannel();
let commandToExecute;
if (useIonic && ["run", "prepare"].indexOf(command) > -1) {
if (useIonic && ["run", "prepare"].includes(command)) {
commandToExecute = `${cliCommandName} cordova ${command}`;
} else {
commandToExecute = `${cliCommandName} ${command}`;
@ -60,19 +68,36 @@ export class CordovaCommandHelper {
commandToExecute += ` ${runArgs.join(" ")}`;
}
logger.log(localize("Executing", "########### EXECUTING: {0} ###########", commandToExecute));
logger.log(
localize(
"Executing",
"########### EXECUTING: {0} ###########",
commandToExecute,
),
);
const env = CordovaProjectHelper.getEnvArgument({
env: CordovaCommandHelper.getEnvArgs(projectRoot),
envFile: CordovaCommandHelper.getEnvFile(projectRoot),
});
const execution = new Promise((resolve, reject) => {
const process = child_process.exec(commandToExecute, { cwd: projectRoot, env });
const process = child_process.exec(commandToExecute, {
cwd: projectRoot,
env,
});
process.on("error", (err: any) => {
// ENOENT error will be thrown if no Cordova.cmd or ionic.cmd is found
if (err.code === "ENOENT") {
window.showErrorMessage(localize("PackageNotFoundPleaseInstall", "{0} not found, please run \"npm install –g {1}\" to install {2} globally", cliDisplayName, cliDisplayName.toLowerCase(), cliDisplayName));
window.showErrorMessage(
localize(
"PackageNotFoundPleaseInstall",
'{0} not found, please run "npm install –g {1}" to install {2} globally', // eslint-disable-line
cliDisplayName,
cliDisplayName.toLowerCase(),
cliDisplayName,
),
);
}
reject(err);
});
@ -86,36 +111,68 @@ export class CordovaCommandHelper {
});
process.stdout.on("close", () => {
logger.log(localize("FinishedExecuting", "########### FINISHED EXECUTING: {0} ###########", commandToExecute));
logger.log(
localize(
"FinishedExecuting",
"########### FINISHED EXECUTING: {0} ###########",
commandToExecute,
),
);
resolve({});
});
});
return TelemetryHelper.determineProjectTypes(projectRoot)
.then((projectType) => generator.add("projectType", TelemetryHelper.prepareProjectTypesTelemetry(projectType), false))
.then(projectType =>
generator.add(
"projectType",
TelemetryHelper.prepareProjectTypesTelemetry(projectType),
false,
),
)
.then(() => execution);
});
});
},
);
}
public static restartCordovaDebugging(projectRoot: string, cordovaSessionManager: CordovaSessionManager): void {
const cordovaDebugSession = cordovaSessionManager.getCordovaDebugSessionByProjectRoot(projectRoot);
public static restartCordovaDebugging(
projectRoot: string,
cordovaSessionManager: CordovaSessionManager,
): void {
const cordovaDebugSession =
cordovaSessionManager.getCordovaDebugSessionByProjectRoot(projectRoot);
if (cordovaDebugSession) {
switch (cordovaDebugSession.getStatus()) {
case CordovaSessionStatus.Activated:
cordovaDebugSession.setStatus(CordovaSessionStatus.Pending);
commands.executeCommand(CordovaCommandHelper.RESTART_SESSION_COMMAND, undefined, { sessionId: cordovaDebugSession.getVSCodeDebugSession().id });
commands.executeCommand(
CordovaCommandHelper.RESTART_SESSION_COMMAND,
undefined,
{ sessionId: cordovaDebugSession.getVSCodeDebugSession().id },
);
break;
case CordovaSessionStatus.NotActivated:
cordovaDebugSession.setStatus(CordovaSessionStatus.Pending);
commands.executeCommand(CordovaCommandHelper.RESTART_SESSION_COMMAND);
break;
case CordovaSessionStatus.Pending:
window.showWarningMessage(localize("CordovaSessionPendingWarning", "A Cordova application is building now. Please wait for the build completion to start the build process again."));
window.showWarningMessage(
localize(
"CordovaSessionPendingWarning",
"A Cordova application is building now. Please wait for the build completion to start the build process again.",
),
);
break;
}
} else {
window.showErrorMessage(localize("CannotRestartDebugging", "Cannot restart debugging of a Cordova application by the path \"{0}\". Could not find a debugging session for the application.", projectRoot));
window.showErrorMessage(
localize(
"CannotRestartDebugging",
'Cannot restart debugging of a Cordova application by the path "{0}". Could not find a debugging session for the application.', // eslint-disable-line
projectRoot,
),
);
}
}
@ -139,52 +196,64 @@ export class CordovaCommandHelper {
}
private static getSetting(fsPath: string, configKey: string): any {
let uri = Uri.file(fsPath);
const workspaceConfiguration: WorkspaceConfiguration = workspace.getConfiguration("cordova", uri);
const uri = Uri.file(fsPath);
const workspaceConfiguration: WorkspaceConfiguration = workspace.getConfiguration(
"cordova",
uri,
);
if (workspaceConfiguration.has(configKey)) {
return workspaceConfiguration.get(configKey);
}
}
private static selectPlatform(projectRoot: string, command: string, useIonic: boolean): Promise<string> {
private static selectPlatform(
projectRoot: string,
command: string,
useIonic: boolean,
): Promise<string> {
let platforms = CordovaProjectHelper.getInstalledPlatforms(projectRoot);
platforms = CordovaCommandHelper.filterAvailablePlatforms(platforms);
return new Promise((resolve, reject) => {
if (["prepare", "build", "run"].indexOf(command) > -1) {
if (["prepare", "build", "run"].includes(command)) {
if (platforms.length > 1) {
platforms.unshift("all");
// Ionic doesn't support prepare and run command without platform
if (useIonic && (command === "prepare" || command === "run")) {
platforms.shift();
}
return window.showQuickPick(platforms)
.then((platform) => {
if (!platform) {
throw new Error(localize("PlatformSelectionWasCancelled", "Platform selection was canceled. Please select target platform to continue!"));
}
// eslint-disable-next-line
return window.showQuickPick(platforms).then(platform => {
if (!platform) {
throw new Error(
localize(
"PlatformSelectionWasCancelled",
"Platform selection was canceled. Please select target platform to continue!",
),
);
}
if (platform === "all") {
return resolve("");
}
if (platform === "all") {
return resolve("");
}
return resolve(platform);
});
return resolve(platform);
});
} else if (platforms.length === 1) {
// eslint-disable-next-line
return resolve(platforms[0]);
} else {
throw new Error(localize("NoAnyPlatformInstalled", "No any platforms installed"));
}
throw new Error(localize("NoAnyPlatformInstalled", "No any platforms installed"));
}
return resolve("");
return resolve(""); // eslint-disable-line
});
}
private static filterAvailablePlatforms(platforms: string[]): string[] {
const osPlatform = os.platform();
return platforms.filter((platform) => {
return platforms.filter(platform => {
switch (platform) {
case "ios":
case "osx":

Просмотреть файл

@ -5,10 +5,14 @@ import * as child_process from "child_process";
import * as fs from "fs";
import * as path from "path";
import { URL } from "url";
import * as semver from "semver";
import * as os from "os";
import * as semver from "semver";
import * as nls from "vscode-nls";
nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
nls.config({
messageFormat: nls.MessageFormat.bundle,
bundleFormat: nls.BundleFormat.standalone,
})();
const localize = nls.loadMessageBundle();
export interface IPluginDetails {
@ -23,7 +27,7 @@ export class ProjectType {
public isMobilefirst: boolean,
public isPhonegap: boolean,
public isCordova: boolean,
public ionicMajorVersion?: number
public ionicMajorVersion?: number,
) {}
get isIonic(): boolean {
@ -46,7 +50,8 @@ export class CordovaProjectHelper {
private static CONFIG_IONIC_FILENAME: string = "ionic.config.json";
private static IONIC_LIB_DEFAULT_PATH: string = path.join("www", "lib", "ionic");
private static CORE_PLUGIN_LIST: string[] = ["cordova-plugin-battery-status",
private static CORE_PLUGIN_LIST: string[] = [
"cordova-plugin-battery-status",
"cordova-plugin-camera",
"cordova-plugin-console",
"cordova-plugin-contacts",
@ -76,7 +81,8 @@ export class CordovaProjectHelper {
"cordova-plugin-ms-adal",
"com-intel-security-cordova-plugin",
"cordova-sqlite-storage",
"cordova-plugin-ms-intune-mam"];
"cordova-plugin-ms-intune-mam",
];
/**
* Helper function check if a file exists.
@ -95,7 +101,8 @@ export class CordovaProjectHelper {
* Helper (asynchronous) function to check if a file or directory exists
*/
public static exists(filename: string): Promise<boolean> {
return fs.promises.stat(filename)
return fs.promises
.stat(filename)
.then(() => {
return true;
})
@ -108,7 +115,7 @@ export class CordovaProjectHelper {
* Helper (synchronous) function to create a directory recursively
*/
public static makeDirectoryRecursive(dirPath: string): void {
let parentPath = path.dirname(dirPath);
const parentPath = path.dirname(dirPath);
if (!CordovaProjectHelper.existsSync(parentPath)) {
CordovaProjectHelper.makeDirectoryRecursive(parentPath);
}
@ -123,7 +130,7 @@ export class CordovaProjectHelper {
if (fs.existsSync(dirPath)) {
if (fs.lstatSync(dirPath).isDirectory()) {
fs.readdirSync(dirPath).forEach(function (file) {
let curPath = path.join(dirPath, file);
const curPath = path.join(dirPath, file);
CordovaProjectHelper.deleteDirectoryRecursive(curPath);
});
@ -145,15 +152,19 @@ export class CordovaProjectHelper {
* Helper function to get the list of plugins installed for the project.
*/
public static getInstalledPlugins(projectRoot: string): string[] {
let fetchJsonPath: string = path.resolve(projectRoot, CordovaProjectHelper.PROJECT_PLUGINS_DIR, CordovaProjectHelper.PLUGINS_FETCH_FILENAME);
const fetchJsonPath: string = path.resolve(
projectRoot,
CordovaProjectHelper.PROJECT_PLUGINS_DIR,
CordovaProjectHelper.PLUGINS_FETCH_FILENAME,
);
if (!CordovaProjectHelper.existsSync(fetchJsonPath)) {
return [];
}
try {
let fetchJsonContents = fs.readFileSync(fetchJsonPath, "utf8");
let fetchJson = JSON.parse(fetchJsonContents);
const fetchJsonContents = fs.readFileSync(fetchJsonPath, "utf8");
const fetchJson = JSON.parse(fetchJsonContents);
return Object.keys(fetchJson);
} catch (error) {
console.error(error);
@ -165,15 +176,18 @@ export class CordovaProjectHelper {
* Helper function to get the list of platforms installed for the project.
*/
public static getInstalledPlatforms(projectRoot: string): string[] {
let platformsPath: string = path.resolve(projectRoot, CordovaProjectHelper.PLATFORMS_PATH);
const platformsPath: string = path.resolve(
projectRoot,
CordovaProjectHelper.PLATFORMS_PATH,
);
if (!CordovaProjectHelper.existsSync(platformsPath)) {
return [];
}
try {
let platformsDirContents = fs.readdirSync(platformsPath);
return platformsDirContents.filter((platform) => {
const platformsDirContents = fs.readdirSync(platformsPath);
return platformsDirContents.filter(platform => {
return platform.charAt(0) !== ".";
});
} catch (error) {
@ -183,19 +197,26 @@ export class CordovaProjectHelper {
}
public static getInstalledPluginDetails(projectRoot: string, pluginId: string): IPluginDetails {
let packageJsonPath: string = path.resolve(projectRoot, CordovaProjectHelper.PROJECT_PLUGINS_DIR, pluginId, "package.json");
const packageJsonPath: string = path.resolve(
projectRoot,
CordovaProjectHelper.PROJECT_PLUGINS_DIR,
pluginId,
"package.json",
);
if (!CordovaProjectHelper.existsSync(packageJsonPath)) {
return null;
}
try {
let packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
let details: IPluginDetails = {
const details: IPluginDetails = {
PluginId: packageJson.name,
Version: packageJson.version,
PluginType: CordovaProjectHelper.CORE_PLUGIN_LIST.indexOf(pluginId) >= 0 ? "Core" : "Npm",
PluginType: CordovaProjectHelper.CORE_PLUGIN_LIST.includes(pluginId)
? "Core"
: "Npm",
};
return details;
@ -209,8 +230,13 @@ export class CordovaProjectHelper {
* Helper to check whether a workspace root equals to a Cordova project root
*/
public static isCordovaProject(workspaceRoot: string): boolean {
return !!(CordovaProjectHelper.existsSync(path.join(workspaceRoot, CordovaProjectHelper.CONFIG_XML_FILENAME))
|| CordovaProjectHelper.existsSync(path.join(workspaceRoot, CordovaProjectHelper.CONFIG_IONIC_FILENAME))
return !!(
CordovaProjectHelper.existsSync(
path.join(workspaceRoot, CordovaProjectHelper.CONFIG_XML_FILENAME),
) ||
CordovaProjectHelper.existsSync(
path.join(workspaceRoot, CordovaProjectHelper.CONFIG_IONIC_FILENAME),
)
);
}
@ -237,7 +263,11 @@ export class CordovaProjectHelper {
*/
public static getOrCreateTypingsTargetPath(projectRoot: string): string {
if (projectRoot) {
let targetPath = path.resolve(projectRoot, CordovaProjectHelper.VSCODE_DIR, CordovaProjectHelper.PROJECT_TYPINGS_FOLDERNAME);
const targetPath = path.resolve(
projectRoot,
CordovaProjectHelper.VSCODE_DIR,
CordovaProjectHelper.PROJECT_TYPINGS_FOLDERNAME,
);
if (!CordovaProjectHelper.existsSync(targetPath)) {
CordovaProjectHelper.makeDirectoryRecursive(targetPath);
}
@ -252,14 +282,22 @@ export class CordovaProjectHelper {
* Helper function to get the path to Cordova plugin type definitions folder
*/
public static getCordovaPluginTypeDefsPath(projectRoot: string): string {
return path.resolve(CordovaProjectHelper.getOrCreateTypingsTargetPath(projectRoot), CordovaProjectHelper.PROJECT_TYPINGS_CORDOVA_FOLDERNAME, CordovaProjectHelper.PROJECT_TYPINGS_PLUGINS_FOLDERNAME);
return path.resolve(
CordovaProjectHelper.getOrCreateTypingsTargetPath(projectRoot),
CordovaProjectHelper.PROJECT_TYPINGS_CORDOVA_FOLDERNAME,
CordovaProjectHelper.PROJECT_TYPINGS_PLUGINS_FOLDERNAME,
);
}
/**
* Helper function to get the path to Ionic plugin type definitions folder
*/
public static getIonicPluginTypeDefsPath(projectRoot: string): string {
return path.resolve(CordovaProjectHelper.getOrCreateTypingsTargetPath(projectRoot), CordovaProjectHelper.PROJECT_TYPINGS_CORDOVA_IONIC_FOLDERNAME, CordovaProjectHelper.PROJECT_TYPINGS_PLUGINS_FOLDERNAME);
return path.resolve(
CordovaProjectHelper.getOrCreateTypingsTargetPath(projectRoot),
CordovaProjectHelper.PROJECT_TYPINGS_CORDOVA_IONIC_FOLDERNAME,
CordovaProjectHelper.PROJECT_TYPINGS_PLUGINS_FOLDERNAME,
);
}
/**
@ -278,9 +316,11 @@ export class CordovaProjectHelper {
if (fs.existsSync(path.join(projectRoot, CordovaProjectHelper.IONIC_PROJECT_FILE))) {
return 1;
// If not found, fall back to looking for "www/lib/ionic" folder. This isn't a 100% guarantee though: an Ionic project doesn't necessarily have an "ionic.project" and could have the Ionic lib
// files in a non-default location
} else if (fs.existsSync(path.join(projectRoot, CordovaProjectHelper.IONIC_LIB_DEFAULT_PATH))) {
// If not found, fall back to looking for "www/lib/ionic" folder. This isn't a 100% guarantee though: an Ionic project doesn't necessarily have an "ionic.project" and could have the Ionic lib
// files in a non-default location
} else if (
fs.existsSync(path.join(projectRoot, CordovaProjectHelper.IONIC_LIB_DEFAULT_PATH))
) {
return 1;
}
@ -295,15 +335,20 @@ export class CordovaProjectHelper {
// Ionic 2 & 3 check
const highestNotSupportedIonic2BetaVersion = "2.0.0-beta.9";
const highestNotSupportedIonic3BetaVersion = "3.0.0-beta.3";
if ((dependencies["ionic-angular"]) && (devDependencies["@ionic/app-scripts"] || dependencies["@ionic/app-scripts"])) {
if (
dependencies["ionic-angular"] &&
(devDependencies["@ionic/app-scripts"] || dependencies["@ionic/app-scripts"])
) {
const ionicVersion = dependencies["ionic-angular"];
// If it's a valid version let's check it's greater than highest not supported Ionic major version beta
if (semver.valid(ionicVersion)) {
if (CordovaProjectHelper.versionSatisfiesInterval(
ionicVersion,
highestNotSupportedIonic2BetaVersion,
"3.0.0")
if (
CordovaProjectHelper.versionSatisfiesInterval(
ionicVersion,
highestNotSupportedIonic2BetaVersion,
"3.0.0",
)
) {
return 2;
}
@ -314,10 +359,12 @@ export class CordovaProjectHelper {
// If it's a valid range we check that the entire range is greater than highest not supported Ionic major version beta
if (semver.validRange(ionicVersion)) {
if (CordovaProjectHelper.versionRangeSatisfiesInterval(
ionicVersion,
highestNotSupportedIonic2BetaVersion,
"3.0.0")
if (
CordovaProjectHelper.versionRangeSatisfiesInterval(
ionicVersion,
highestNotSupportedIonic2BetaVersion,
"3.0.0",
)
) {
return 2;
}
@ -342,17 +389,21 @@ export class CordovaProjectHelper {
// If it's a valid version let's check it's greater than highest not supported Ionic major version beta
if (semver.valid(ionicVersion)) {
if (CordovaProjectHelper.versionSatisfiesInterval(
ionicVersion,
highestNotSupportedIonic4BetaVersion,
"5.0.0")
if (
CordovaProjectHelper.versionSatisfiesInterval(
ionicVersion,
highestNotSupportedIonic4BetaVersion,
"5.0.0",
)
) {
return 4;
}
if (CordovaProjectHelper.versionSatisfiesInterval(
ionicVersion,
highestNotSupportedIonic5BetaVersion,
"6.0.0")
if (
CordovaProjectHelper.versionSatisfiesInterval(
ionicVersion,
highestNotSupportedIonic5BetaVersion,
"6.0.0",
)
) {
return 5;
}
@ -363,17 +414,21 @@ export class CordovaProjectHelper {
// If it's a valid range we check that the entire range is greater than highest not supported Ionic major version beta
if (semver.validRange(ionicVersion)) {
if (CordovaProjectHelper.versionRangeSatisfiesInterval(
ionicVersion,
highestNotSupportedIonic4BetaVersion,
"5.0.0")
if (
CordovaProjectHelper.versionRangeSatisfiesInterval(
ionicVersion,
highestNotSupportedIonic4BetaVersion,
"5.0.0",
)
) {
return 4;
}
if (CordovaProjectHelper.versionRangeSatisfiesInterval(
ionicVersion,
highestNotSupportedIonic5BetaVersion,
"6.0.0")
if (
CordovaProjectHelper.versionRangeSatisfiesInterval(
ionicVersion,
highestNotSupportedIonic5BetaVersion,
"6.0.0",
)
) {
return 5;
}
@ -404,7 +459,10 @@ export class CordovaProjectHelper {
return command;
}
public static getIonicCliVersion(fsPath: string, command: string = CordovaProjectHelper.getCliCommand(fsPath)): string {
public static getIonicCliVersion(
fsPath: string,
command: string = CordovaProjectHelper.getCliCommand(fsPath),
): string {
const ionicInfo = child_process.spawnSync(command, ["-v", "--quiet"], {
cwd: fsPath,
env: {
@ -412,7 +470,7 @@ export class CordovaProjectHelper {
CI: "Hack to disable Ionic autoupdate prompt",
},
});
let parseVersion = /\d+\.\d+\.\d+/.exec(ionicInfo.stdout.toString());
const parseVersion = /\d+\.\d+\.\d+/.exec(ionicInfo.stdout.toString());
return parseVersion[0].trim();
}
@ -422,23 +480,36 @@ export class CordovaProjectHelper {
* @param version version to compare
* @param command multiplatform command to use
*/
public static isIonicCliVersionGte(fsPath: string, version: string, command: string = CordovaProjectHelper.getCliCommand(fsPath)): boolean {
public static isIonicCliVersionGte(
fsPath: string,
version: string,
command: string = CordovaProjectHelper.getCliCommand(fsPath),
): boolean {
try {
const ionicVersion = CordovaProjectHelper.getIonicCliVersion(fsPath, command);
return semver.gte(ionicVersion, version);
} catch (err) {
console.error(localize("ErrorWhileDetectingIonicCLIVersion", "Error while detecting Ionic CLI version"), err);
console.error(
localize(
"ErrorWhileDetectingIonicCLIVersion",
"Error while detecting Ionic CLI version",
),
err,
);
}
return true;
}
public static isIonicCliVersionGte3(fsPath: string, command: string = CordovaProjectHelper.getCliCommand(fsPath)): boolean {
public static isIonicCliVersionGte3(
fsPath: string,
command: string = CordovaProjectHelper.getCliCommand(fsPath),
): boolean {
return CordovaProjectHelper.isIonicCliVersionGte(fsPath, "3.0.0", command);
}
public static getEnvArgument(launchArgs): any {
let args = { ...launchArgs };
let env = process.env;
const args = { ...launchArgs };
const env = process.env;
if (args.envFile) {
let buffer = fs.readFileSync(args.envFile, "utf8");
@ -449,15 +520,20 @@ export class CordovaProjectHelper {
}
buffer.split("\n").forEach((line: string) => {
const r = line.match(/^\s*([\w\.\-]+)\s*=\s*(.*)?\s*$/);
const r = line.match(/^\s*([\w.\-]+)\s*=\s*(.*)?\s*$/);
if (r !== null) {
const key = r[1];
if (!env[key]) { // .env variables never overwrite existing variables
if (!env[key]) {
// .env variables never overwrite existing variables
let value = r[2] || "";
if (value.length > 0 && value.charAt(0) === "\"" && value.charAt(value.length - 1) === "\"") {
if (
value.length > 0 &&
value.charAt(0) === '"' && // eslint-disable-line
value.charAt(value.length - 1) === '"' // eslint-disable-line
) {
value = value.replace(/\\n/gm, "\n");
}
env[key] = value.replace(/(^['"]|['"]$)/g, "");
env[key] = value.replace(/(^["']|["']$)/g, "");
}
}
});
@ -465,7 +541,8 @@ export class CordovaProjectHelper {
if (args.env) {
// launch config env vars overwrite .env vars
for (let key in args.env) {
// eslint-disable-next-line
for (const key in args.env) {
if (args.env.hasOwnProperty(key)) {
env[key] = args.env[key];
}
@ -480,9 +557,8 @@ export class CordovaProjectHelper {
return path.posix.join(...segments);
} else if (path.win32.isAbsolute(segments[0])) {
return path.win32.join(...segments);
} else {
return path.join(...segments);
}
return path.join(...segments);
}
public static getPortFromURL(url: string): number {

Просмотреть файл

@ -9,20 +9,27 @@ import { TelemetryGenerator, TelemetryHelper } from "./telemetryHelper";
* telemetry event. This wrapper creates a telemetry event compatible with vscode-cordova's telemetry, based on cordova-simulate's event, and sends it using the Telemetry module.
*/
export class CordovaSimulateTelemetry {
public sendTelemetry(eventName: string, props: Telemetry.ITelemetryProperties, piiProps: Telemetry.ITelemetryProperties): void {
let fullEventName = "cordova-simulate-" + eventName;
let generator = new TelemetryGenerator(fullEventName);
let telemetryEvent = new Telemetry.TelemetryEvent(fullEventName);
public sendTelemetry(
eventName: string,
props: Telemetry.ITelemetryProperties,
piiProps: Telemetry.ITelemetryProperties,
): void {
const fullEventName = `cordova-simulate-${eventName}`;
const generator = new TelemetryGenerator(fullEventName);
const telemetryEvent = new Telemetry.TelemetryEvent(fullEventName);
Object.keys(props).forEach((prop) => {
Object.keys(props).forEach(prop => {
generator.add(prop, props[prop], false);
});
Object.keys(piiProps).forEach((prop) => {
Object.keys(piiProps).forEach(prop => {
generator.add(prop, piiProps[prop], true);
});
TelemetryHelper.addTelemetryEventProperties(telemetryEvent, generator.getTelemetryProperties());
TelemetryHelper.addTelemetryEventProperties(
telemetryEvent,
generator.getTelemetryProperties(),
);
Telemetry.send(telemetryEvent);
}
}

Просмотреть файл

@ -11,40 +11,56 @@ export function generateRandomPortNumber(): number {
return Math.round(Math.random() * 40000 + 3000);
}
export function retryAsync<T>(func: () => Promise<T>, condition: (result: T) => boolean, maxRetries: number, iteration: number, delayTime: number, failure: string, cancellationToken?: CancellationToken): Promise<T> {
export function retryAsync<T>(
func: () => Promise<T>,
condition: (result: T) => boolean,
maxRetries: number,
iteration: number,
delayTime: number,
failure: string,
cancellationToken?: CancellationToken,
): Promise<T> {
const retry = () => {
if (cancellationToken && cancellationToken.isCancellationRequested) {
let cancelError = new Error(CANCELLATION_ERROR_NAME);
const cancelError = new Error(CANCELLATION_ERROR_NAME);
cancelError.name = CANCELLATION_ERROR_NAME;
throw cancelError;
}
if (iteration < maxRetries) {
return delay(delayTime).then(() => retryAsync(func, condition, maxRetries, iteration + 1, delayTime, failure, cancellationToken));
return delay(delayTime).then(() =>
retryAsync(
func,
condition,
maxRetries,
iteration + 1,
delayTime,
failure,
cancellationToken,
),
);
}
throw new Error(failure);
};
return func()
.then(result => {
if (condition(result)) {
return result;
}
return func().then(result => {
if (condition(result)) {
return result;
}
return retry();
},
retry);
return retry();
}, retry);
}
export function delay(duration: number): Promise<void> {
return new Promise<void>(resolve => setTimeout(resolve, duration));
return new Promise<void>(resolve => setTimeout(resolve, duration)); // eslint-disable-line
}
export function promiseGet(url: string, reqErrMessage: string): Promise<string> {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
const err = new Error("Request timeout");
req.destroy(err);
req.destroy(err); // eslint-disable-line
reject(err);
}, 9500);
@ -69,7 +85,7 @@ export function promiseGet(url: string, reqErrMessage: string): Promise<string>
export function findFileInFolderHierarchy(dir: string, filename: string): string | null {
let parentPath: string;
let projectRoot: string = dir;
let atFsRoot: boolean = false;
let atFsRoot = false;
while (!fs.existsSync(path.join(projectRoot, filename))) {
parentPath = path.resolve(projectRoot, "..");

Просмотреть файл

@ -8,9 +8,6 @@ export class Hash {
* Creates a hash code from a string.
*/
public static hashCode(s: string): string {
return crypto
.createHash("md5")
.update(s)
.digest("hex");
return crypto.createHash("md5").update(s).digest("hex");
}
}

Просмотреть файл

@ -91,11 +91,15 @@ export class IOSTargetManager extends MobileTargetManager {
private childProcess: ChildProcess = new ChildProcess();
protected targets?: IDebuggableIOSTarget[];
public async getTargetList(filter?: (el: IDebuggableIOSTarget) => boolean): Promise<IDebuggableIOSTarget[]> {
public async getTargetList(
filter?: (el: IDebuggableIOSTarget) => boolean,
): Promise<IDebuggableIOSTarget[]> {
return (await super.getTargetList(filter)) as IDebuggableIOSTarget[];
}
public async collectTargets(targetType?: TargetType.Device | TargetType.Emulator): Promise<void> {
public async collectTargets(
targetType?: TargetType.Device | TargetType.Emulator,
): Promise<void> {
this.targets = [];
if (targetType === undefined || targetType === TargetType.Emulator) {
@ -112,7 +116,7 @@ export class IOSTargetManager extends MobileTargetManager {
try {
const identifierPieces = device.deviceTypeIdentifier.split(".");
simIdentifier = identifierPieces[identifierPieces.length - 1];
} catch { }
} catch {}
this.targets?.push({
id: device.udid,
@ -120,7 +124,7 @@ export class IOSTargetManager extends MobileTargetManager {
system,
isVirtualTarget: true,
isOnline: device.state === IOSTargetManager.BOOTED_STATE,
simIdentifier: simIdentifier,
simIdentifier,
simDataPath: device.dataPath,
});
}
@ -183,12 +187,16 @@ export class IOSTargetManager extends MobileTargetManager {
}
public async getOnlineTargets(): Promise<IOSTarget[]> {
const onlineTargets = (await this.getTargetList(target => target.isOnline)) as IDebuggableIOSTarget[];
const onlineTargets = (await this.getTargetList(
target => target.isOnline,
)) as IDebuggableIOSTarget[];
return onlineTargets.map(target => IOSTarget.fromInterface(target));
}
public async getOnlineSimulators(): Promise<IOSTarget[]> {
const onlineTargets = (await this.getTargetList(target => target.isOnline && target.isVirtualTarget)) as IDebuggableIOSTarget[];
const onlineTargets = (await this.getTargetList(
target => target.isOnline && target.isVirtualTarget,
)) as IDebuggableIOSTarget[];
return onlineTargets.map(target => IOSTarget.fromInterface(target));
}
@ -274,7 +282,11 @@ export class IOSTargetManager extends MobileTargetManager {
emulatorProcess.spawnedProcess.unref();
emulatorProcess.outcome.catch(err => {
emulatorLaunchFailed = true;
reject(new Error(`Error while launching simulator ${emulatorTarget.name}(${emulatorTarget.id} with an exception: ${err}`));
reject(
new Error(
`Error while launching simulator ${emulatorTarget.name}(${emulatorTarget.id} with an exception: ${err}`,
),
);
});
const condition = async () => {
@ -296,10 +308,13 @@ export class IOSTargetManager extends MobileTargetManager {
if (isBooted) {
emulatorTarget.isOnline = true;
this.logger.log(
localize("SimulatorLaunched", "Launched simulator {0}", emulatorTarget.name),
localize(
"SimulatorLaunched",
"Launched simulator {0}",
emulatorTarget.name,
),
);
resolve(IOSTarget.fromInterface(emulatorTarget));
} else {
reject(
new Error(
@ -308,8 +323,8 @@ export class IOSTargetManager extends MobileTargetManager {
"Could not start the simulator {0} within {1} seconds.",
emulatorTarget.name,
IOSTargetManager.SIMULATOR_START_TIMEOUT,
)}`
)
)}`,
),
);
}
},

Просмотреть файл

@ -60,7 +60,7 @@ export class LaunchScenariosManager {
public updateLaunchScenario(launchArgs: any, updates: any): void {
this.readLaunchScenarios();
let launchConfigIndex = this.getFirstScenarioIndexByParams(launchArgs);
const launchConfigIndex = this.getFirstScenarioIndexByParams(launchArgs);
const launchScenarios = this.getLaunchScenarios();
if (launchConfigIndex !== null && launchScenarios.configurations) {
Object.assign(launchScenarios.configurations[launchConfigIndex], updates);

Просмотреть файл

@ -16,7 +16,7 @@ export function getFormattedTimeString(date: Date): string {
}
export function getFormattedDateString(date: Date): string {
return date.getUTCFullYear() + "-" + `${date.getUTCMonth() + 1}` + "-" + date.getUTCDate();
return `${date.getUTCFullYear()}-` + `${date.getUTCMonth() + 1}` + `-${date.getUTCDate()}`;
}
export function getFormattedDatetimeString(date: Date): string {
@ -26,7 +26,6 @@ export function getFormattedDatetimeString(date: Date): string {
function padZeroes(minDesiredLength: number, numberToPad: string): string {
if (numberToPad.length >= minDesiredLength) {
return numberToPad;
} else {
return String("0".repeat(minDesiredLength) + numberToPad).slice(-minDesiredLength);
}
return String("0".repeat(minDesiredLength) + numberToPad).slice(-minDesiredLength);
}

Просмотреть файл

@ -7,12 +7,16 @@ import { LogLevel, getFormattedDatetimeString } from "./logHelper";
const channels: { [channelName: string]: OutputChannelLogger } = {};
export class OutputChannelLogger {
public static MAIN_CHANNEL_NAME: string = "Cordova Tools";
private outputChannel: vscode.OutputChannel;
private logTimestamps: boolean;
constructor(public readonly channelName: string, lazy: boolean = false, private preserveFocus: boolean = false, logTimestamps: boolean = false) {
constructor(
public readonly channelName: string,
lazy: boolean = false,
private preserveFocus: boolean = false,
logTimestamps: boolean = false,
) {
this.logTimestamps = logTimestamps;
if (!lazy) {
this.channel = vscode.window.createOutputChannel(this.channelName);
@ -31,9 +35,19 @@ export class OutputChannelLogger {
return this.getChannel(this.MAIN_CHANNEL_NAME, true);
}
public static getChannel(channelName: string, lazy?: boolean, preserveFocus?: boolean, logTimestamps?: boolean): OutputChannelLogger {
public static getChannel(
channelName: string,
lazy?: boolean,
preserveFocus?: boolean,
logTimestamps?: boolean,
): OutputChannelLogger {
if (!channels[channelName]) {
channels[channelName] = new OutputChannelLogger(channelName, lazy, preserveFocus, logTimestamps);
channels[channelName] = new OutputChannelLogger(
channelName,
lazy,
preserveFocus,
logTimestamps,
);
}
return channels[channelName];
@ -73,18 +87,21 @@ export class OutputChannelLogger {
private get channel(): vscode.OutputChannel {
if (this.outputChannel) {
return this.outputChannel;
} else {
this.outputChannel = vscode.window.createOutputChannel(this.channelName);
this.outputChannel.show(this.preserveFocus);
return this.outputChannel;
}
this.outputChannel = vscode.window.createOutputChannel(this.channelName);
this.outputChannel.show(this.preserveFocus);
return this.outputChannel;
}
private set channel(channel: vscode.OutputChannel) {
this.outputChannel = channel;
}
private getFormattedMessage(message: string, tag: string, prependTimestamp: boolean = false): string {
private getFormattedMessage(
message: string,
tag: string,
prependTimestamp: boolean = false,
): string {
let formattedMessage = `[${tag}] ${message}\n`;
if (prependTimestamp) {

Просмотреть файл

@ -3,9 +3,10 @@
import * as nls from "vscode-nls";
import { QuickPickOptions, window } from "vscode";
import { TargetType } from "../debugger/cordovaDebugSession";
import { IMobileTarget, MobileTarget } from "./mobileTarget";
import { OutputChannelLogger } from "./log/outputChannelLogger";
import { TargetType } from "../debugger/cordovaDebugSession";
nls.config({
messageFormat: nls.MessageFormat.bundle,
bundleFormat: nls.BundleFormat.standalone,
@ -20,9 +21,13 @@ export abstract class MobileTargetManager {
true,
);
public abstract collectTargets(targetType?: TargetType.Device | TargetType.Emulator): Promise<void>;
public abstract collectTargets(
targetType?: TargetType.Device | TargetType.Emulator,
): Promise<void>;
public abstract selectAndPrepareTarget(filter?: (el: IMobileTarget) => boolean): Promise<MobileTarget | undefined>;
public abstract selectAndPrepareTarget(
filter?: (el: IMobileTarget) => boolean,
): Promise<MobileTarget | undefined>;
public async isVirtualTarget(target: string): Promise<boolean> {
if (target.includes("device")) {
@ -31,7 +36,13 @@ export abstract class MobileTargetManager {
if (target.includes("emulator")) {
return true;
}
throw new Error(localize("CouldNotRecognizeTargetType", "Could not recognize type of the target {0}", target));
throw new Error(
localize(
"CouldNotRecognizeTargetType",
"Could not recognize type of the target {0}",
target,
),
);
}
public async getTargetList(filter?: (el: IMobileTarget) => boolean): Promise<IMobileTarget[]> {
@ -41,9 +52,13 @@ export abstract class MobileTargetManager {
return filter ? this.targets.filter(filter) : this.targets;
}
protected abstract launchSimulator(emulatorTarget: IMobileTarget): Promise<MobileTarget | undefined>;
protected abstract launchSimulator(
emulatorTarget: IMobileTarget,
): Promise<MobileTarget | undefined>;
protected abstract startSelection(filter?: (el: IMobileTarget) => boolean): Promise<IMobileTarget | undefined>;
protected abstract startSelection(
filter?: (el: IMobileTarget) => boolean,
): Promise<IMobileTarget | undefined>;
protected async selectTarget(
filter?: (el: IMobileTarget) => boolean,
@ -59,7 +74,10 @@ export abstract class MobileTargetManager {
"Select target device for launch application",
),
};
result = await window.showQuickPick(targetList.map(target => target?.name || target?.id), quickPickOptions);
result = await window.showQuickPick(
targetList.map(target => target?.name || target?.id),
quickPickOptions,
);
}
return result
? targetList.find(target => target.name === result || target.id === result)

Просмотреть файл

@ -5,7 +5,11 @@ import * as fs from "fs";
import * as path from "path";
import * as nls from "vscode-nls";
import { ICordovaAttachRequestArgs, ICordovaLaunchRequestArgs } from "../debugger/requestArgs";
nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
nls.config({
messageFormat: nls.MessageFormat.bundle,
bundleFormat: nls.BundleFormat.standalone,
})();
const localize = nls.loadMessageBundle();
export class NodeVersionHelper {
@ -21,7 +25,7 @@ export class NodeVersionHelper {
return "x64";
case "arm":
const arm_version = (process.config.variables as any).arm_version;
return arm_version ? "armv" + arm_version + "l" : "arm";
return arm_version ? `armv${arm_version}l` : "arm";
default:
return arch;
}
@ -31,12 +35,18 @@ export class NodeVersionHelper {
* Parses a node version string into remote name, semantic version, and architecture
* components. Infers some unspecified components based on configuration.
*/
private static parseVersionString(versionString: string): { nvsFormat: boolean, remoteName: string, semanticVersion: string, arch: string} {
const versionRegex = /^(([\w-]+)\/)?(v?(\d+(\.\d+(\.\d+)?)?))(\/((x86)|(32)|((x)?64)|(arm\w*)|(ppc\w*)))?$/i;
private static parseVersionString(versionString: string): {
nvsFormat: boolean;
remoteName: string;
semanticVersion: string;
arch: string;
} {
const versionRegex =
/^(([\w-]+)\/)?(v?(\d+(\.\d+(\.\d+)?)?))(\/((x86)|(32)|((x)?64)|(arm\w*)|(ppc\w*)))?$/i;
const match = versionRegex.exec(versionString);
if (!match) {
throw new Error("Invalid version string: " + versionString);
throw new Error(`Invalid version string: ${versionString}`);
}
const nvsFormat = !!(match[2] || match[8]);
@ -48,76 +58,101 @@ export class NodeVersionHelper {
}
/**
* if a runtime version is specified we prepend env.PATH with the folder that corresponds to the version.
* Returns false on error
*/
public static nvmSupport(config: ICordovaAttachRequestArgs | ICordovaLaunchRequestArgs): void {
* if a runtime version is specified we prepend env.PATH with the folder that corresponds to the version.
* Returns false on error
*/
public static nvmSupport(config: ICordovaAttachRequestArgs | ICordovaLaunchRequestArgs): void {
let bin: string | undefined;
let versionManagerName: string | undefined;
let bin: string | undefined = undefined;
let versionManagerName: string | undefined = undefined;
// first try the Node Version Switcher 'nvs'
let nvsHome = process.env.NVS_HOME;
if (!nvsHome) {
// NVS_HOME is not always set. Probe for 'nvs' directory instead
const nvsDir =
process.platform === "win32"
? path.join(process.env.LOCALAPPDATA || "", "nvs")
: path.join(process.env.HOME || "", ".nvs");
if (fs.existsSync(nvsDir)) {
nvsHome = nvsDir;
}
}
// first try the Node Version Switcher 'nvs'
let nvsHome = process.env["NVS_HOME"];
if (!nvsHome) {
// NVS_HOME is not always set. Probe for 'nvs' directory instead
const nvsDir = process.platform === "win32" ? path.join(process.env["LOCALAPPDATA"] || "", "nvs") : path.join(process.env["HOME"] || "", ".nvs");
if (fs.existsSync(nvsDir)) {
nvsHome = nvsDir;
}
}
const { nvsFormat, remoteName, semanticVersion, arch } =
NodeVersionHelper.parseVersionString(config.runtimeVersion);
const { nvsFormat, remoteName, semanticVersion, arch } = NodeVersionHelper.parseVersionString(config.runtimeVersion);
if (nvsFormat || nvsHome) {
if (nvsHome) {
bin = path.join(nvsHome, remoteName, semanticVersion, arch);
if (process.platform !== "win32") {
bin = path.join(bin, "bin");
}
versionManagerName = "nvs";
} else {
throw new Error(
localize(
"nvsHomeNotFoundMessage",
"Attribute 'runtimeVersion' requires Node.js version manager 'nvs'.",
),
);
}
}
if (nvsFormat || nvsHome) {
if (nvsHome) {
bin = path.join(nvsHome, remoteName, semanticVersion, arch);
if (process.platform !== "win32") {
bin = path.join(bin, "bin");
}
versionManagerName = "nvs";
} else {
throw new Error(localize("nvsHomeNotFoundMessage", "Attribute 'runtimeVersion' requires Node.js version manager 'nvs'."));
}
}
if (!bin) {
// now try the Node Version Manager "nvm"
if (process.platform === "win32") {
const nvmHome = process.env.NVM_HOME;
if (!nvmHome) {
throw new Error(
localize(
"nvmWindowsNotFoundMessage",
"Attribute 'runtimeVersion' requires Node.js version manager 'nvm-windows' or 'nvs'.",
),
);
}
bin = path.join(nvmHome, `v${config.runtimeVersion}`);
versionManagerName = "nvm-windows";
} else {
// macOS and linux
let nvmHome = process.env.NVM_DIR;
if (!nvmHome) {
// if NVM_DIR is not set. Probe for '.nvm' directory instead
const nvmDir = path.join(process.env.HOME || "", ".nvm");
if (fs.existsSync(nvmDir)) {
nvmHome = nvmDir;
}
}
if (!nvmHome) {
throw new Error(
localize(
"nvmHomeNotFoundMessage",
"Attribute 'runtimeVersion' requires Node.js version manager 'nvm' or 'nvs'.",
),
);
}
bin = path.join(nvmHome, "versions", "node", `v${config.runtimeVersion}`, "bin");
versionManagerName = "nvm";
}
}
if (!bin) {
// now try the Node Version Manager "nvm"
if (process.platform === "win32") {
const nvmHome = process.env["NVM_HOME"];
if (!nvmHome) {
throw new Error(localize("nvmWindowsNotFoundMessage", "Attribute 'runtimeVersion' requires Node.js version manager 'nvm-windows' or 'nvs'."));
}
bin = path.join(nvmHome, `v${config.runtimeVersion}`);
versionManagerName = "nvm-windows";
} else { // macOS and linux
let nvmHome = process.env["NVM_DIR"];
if (!nvmHome) {
// if NVM_DIR is not set. Probe for '.nvm' directory instead
const nvmDir = path.join(process.env["HOME"] || "", ".nvm");
if (fs.existsSync(nvmDir)) {
nvmHome = nvmDir;
}
}
if (!nvmHome) {
throw new Error(localize("nvmHomeNotFoundMessage", "Attribute 'runtimeVersion' requires Node.js version manager 'nvm' or 'nvs'."));
}
bin = path.join(nvmHome, "versions", "node", `v${config.runtimeVersion}`, "bin");
versionManagerName = "nvm";
}
}
if (fs.existsSync(bin)) {
if (!config.env) {
config.env = {};
}
if (process.platform === "win32") {
process.env["Path"] = `${bin};${process.env["Path"]}`;
} else {
process.env["PATH"] = `${bin}:${process.env["PATH"]}`;
}
} else {
throw new Error(localize("runtimeVersionNotFoundMessage", "Node.js version '{0}' not installed for '{1}'.", config.runtimeVersion, versionManagerName));
}
}
if (fs.existsSync(bin)) {
if (!config.env) {
config.env = {};
}
if (process.platform === "win32") {
process.env.Path = `${bin};${process.env.Path}`;
} else {
process.env.PATH = `${bin}:${process.env.PATH}`;
}
} else {
throw new Error(
localize(
"runtimeVersionNotFoundMessage",
"Node.js version '{0}' not installed for '{1}'.",
config.runtimeVersion,
versionManagerName,
),
);
}
}
}

Просмотреть файл

@ -4,16 +4,20 @@
import * as os from "os";
import * as path from "path";
import * as nls from "vscode-nls";
nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
nls.config({
messageFormat: nls.MessageFormat.bundle,
bundleFormat: nls.BundleFormat.standalone,
})();
const localize = nls.loadMessageBundle();
export function settingsHome(): string {
switch (os.platform()) {
case "win32":
return path.join(process.env["APPDATA"], "vscode-cordova");
return path.join(process.env.APPDATA, "vscode-cordova");
case "darwin":
case "linux":
return path.join(process.env["HOME"], ".vscode-cordova");
return path.join(process.env.HOME, ".vscode-cordova");
default:
throw new Error(localize("UnexpectedPlatform", "Unexpected Platform"));
}

Просмотреть файл

@ -8,7 +8,7 @@ export class SimulateHelper {
return Object.values(SimulateTargets).includes(target as SimulateTargets);
}
public static isSimulate(args: { target?: string, simulatePort?: number }) {
public static isSimulate(args: { target?: string; simulatePort?: number }) {
return !!(SimulateHelper.isSimulateTarget(args.target) && args.simulatePort);
}
}

Просмотреть файл

@ -1,370 +1,467 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
/// <reference path='../../typings/winreg/winreg.d.ts' />
import * as crypto from "crypto";
import * as fs from "fs";
import * as os from "os";
import * as path from "path";
import * as winreg from "winreg";
import { settingsHome } from "./settingsHelper";
import { DeferredPromise } from "../common/node/promise";
/**
* Telemetry module specialized for vscode integration.
*/
export module Telemetry {
export let appName: string;
export let isOptedIn: boolean = false;
export let reporter: ITelemetryReporter;
export let reporterDictionary: { [key: string]: ITelemetryReporter } = {};
export interface ITelemetryProperties {
[propertyName: string]: any;
}
export interface ITelemetryReporter {
sendTelemetryEvent(eventName: string, properties?: ITelemetryEventProperties, measures?: ITelemetryEventMeasures);
}
class ExtensionTelemetryReporter implements ITelemetryReporter {
private extensionId: string;
private extensionVersion: string;
private appInsightsKey: string;
constructor(extensionId: string, extensionVersion: string, key: string, projectRoot: string) {
this.extensionId = extensionId;
this.extensionVersion = extensionVersion;
this.appInsightsKey = key;
}
public sendTelemetryEvent(eventName: string, properties?: ITelemetryEventProperties, measures?: ITelemetryEventMeasures): void {
Telemetry.sendExtensionTelemetry(this.extensionId, this.extensionVersion, this.appInsightsKey, eventName, properties, measures);
}
}
class TelemetryUtils {
public static USERTYPE_INTERNAL: string = "Internal";
public static USERTYPE_EXTERNAL: string = "External";
public static userType: string;
public static sessionId: string;
public static optInCollectedForCurrentSession: boolean;
private static initDeferred: DeferredPromise<any> = new DeferredPromise<any>();
private static userId: string;
private static telemetrySettings: ITelemetrySettings = null;
private static TELEMETRY_SETTINGS_FILENAME: string = "VSCodeTelemetrySettings.json";
private static APPINSIGHTS_INSTRUMENTATIONKEY: string = "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217"; // Matches vscode telemetry key
private static REGISTRY_SQMCLIENT_NODE: string = "\\SOFTWARE\\Microsoft\\SQMClient";
private static REGISTRY_USERID_VALUE: string = "UserId";
private static INTERNAL_DOMAIN_SUFFIX: string = "microsoft.com";
private static INTERNAL_USER_ENV_VAR: string = "TACOINTERNAL";
private static get telemetrySettingsFile(): string {
return path.join(settingsHome(), TelemetryUtils.TELEMETRY_SETTINGS_FILENAME);
}
public static get initDeferredPromise(): Promise<void> {
return TelemetryUtils.initDeferred.promise;
}
public static init(appVersion: string, initOptions: ITelemetryInitOptions): Promise<any> {
TelemetryUtils.loadSettings();
if (initOptions.isExtensionProcess) {
let TelemetryReporter = require("vscode-extension-telemetry").default;
Telemetry.reporter = new TelemetryReporter(Telemetry.appName, appVersion, TelemetryUtils.APPINSIGHTS_INSTRUMENTATIONKEY);
} else {
Telemetry.reporter = new ExtensionTelemetryReporter(Telemetry.appName, appVersion, TelemetryUtils.APPINSIGHTS_INSTRUMENTATIONKEY, initOptions.projectRoot);
}
TelemetryUtils.getUserId()
.then(function (userId: string): void {
TelemetryUtils.userId = userId;
TelemetryUtils.userType = TelemetryUtils.getUserType();
Telemetry.isOptedIn = TelemetryUtils.getTelemetryOptInSetting();
TelemetryUtils.saveSettings();
TelemetryUtils.initDeferred.resolve(void 0);
});
return TelemetryUtils.initDeferred.promise;
}
public static addCommonProperties(event: any): void {
if (Telemetry.isOptedIn) {
event.properties["cordova.userId"] = TelemetryUtils.userId;
}
event.properties["cordova.userType"] = TelemetryUtils.userType;
}
public static generateGuid(): string {
let hexValues: string[] = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"];
// c.f. rfc4122 (UUID version 4 = xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx)
let oct: string = "";
let tmp: number;
/* tslint:disable:no-bitwise */
for (let a: number = 0; a < 4; a++) {
tmp = (4294967296 * Math.random()) | 0;
oct += hexValues[tmp & 0xF] + hexValues[tmp >> 4 & 0xF] + hexValues[tmp >> 8 & 0xF] + hexValues[tmp >> 12 & 0xF] + hexValues[tmp >> 16 & 0xF] + hexValues[tmp >> 20 & 0xF] + hexValues[tmp >> 24 & 0xF] + hexValues[tmp >> 28 & 0xF];
}
// 'Set the two most significant bits (bits 6 and 7) of the clock_seq_hi_and_reserved to zero and one, respectively'
let clockSequenceHi: string = hexValues[8 + (Math.random() * 4) | 0];
return oct.substr(0, 8) + "-" + oct.substr(9, 4) + "-4" + oct.substr(13, 3) + "-" + clockSequenceHi + oct.substr(16, 3) + "-" + oct.substr(19, 12);
/* tslint:enable:no-bitwise */
}
public static getTelemetryOptInSetting(): boolean {
if (TelemetryUtils.telemetrySettings.optIn === undefined) {
// Opt-in by default
TelemetryUtils.telemetrySettings.optIn = true;
}
return TelemetryUtils.telemetrySettings.optIn;
}
private static getUserType(): string {
let userType: string = TelemetryUtils.telemetrySettings.userType;
if (userType === undefined) {
if (process.env[TelemetryUtils.INTERNAL_USER_ENV_VAR]) {
userType = TelemetryUtils.USERTYPE_INTERNAL;
} else if (os.platform() === "win32") {
let domain: string = process.env["USERDNSDOMAIN"];
domain = domain ? domain.toLowerCase().substring(domain.length - TelemetryUtils.INTERNAL_DOMAIN_SUFFIX.length) : null;
userType = domain === TelemetryUtils.INTERNAL_DOMAIN_SUFFIX ? TelemetryUtils.USERTYPE_INTERNAL : TelemetryUtils.USERTYPE_EXTERNAL;
} else {
userType = TelemetryUtils.USERTYPE_EXTERNAL;
}
TelemetryUtils.telemetrySettings.userType = userType;
}
return userType;
}
private static getRegistryValue(key: string, value: string, hive: string): Promise<string> {
return new Promise((resolve, reject) => {
let regKey = new winreg({ hive, key });
regKey.get(value, function (err: any, itemValue: winreg.RegistryItem) {
if (err) {
// Fail gracefully by returning null if there was an error.
resolve(null);
} else {
resolve(itemValue.value);
}
});
});
}
/*
* Load settings data from settingsHome/TelemetrySettings.json
*/
private static loadSettings(): ITelemetrySettings {
try {
TelemetryUtils.telemetrySettings = JSON.parse(<any>fs.readFileSync(TelemetryUtils.telemetrySettingsFile));
} catch (e) {
// if file does not exist or fails to parse then assume no settings are saved and start over
TelemetryUtils.telemetrySettings = {};
}
return TelemetryUtils.telemetrySettings;
}
/*
* Save settings data in settingsHome/TelemetrySettings.json
*/
private static saveSettings(): void {
if (!fs.existsSync(settingsHome())) {
fs.mkdirSync(settingsHome());
}
fs.writeFileSync(TelemetryUtils.telemetrySettingsFile, JSON.stringify(TelemetryUtils.telemetrySettings));
}
private static getUniqueId(regValue: string, regHive: string, fallback: () => string): Promise<any> {
let uniqueId: string;
if (os.platform() === "win32") {
return TelemetryUtils.getRegistryValue(TelemetryUtils.REGISTRY_SQMCLIENT_NODE, regValue, regHive)
.then((id: string) => {
if (id) {
uniqueId = id.replace(/[{}]/g, "");
return uniqueId;
} else {
return fallback();
}
});
} else {
return Promise.resolve(fallback());
}
}
private static getUserId(): Promise<string> {
let userId: string = TelemetryUtils.telemetrySettings.userId;
if (!userId) {
return TelemetryUtils.getUniqueId(TelemetryUtils.REGISTRY_USERID_VALUE, winreg.HKCU, TelemetryUtils.generateGuid)
.then((id: string) => {
TelemetryUtils.telemetrySettings.userId = id;
return id;
});
} else {
TelemetryUtils.telemetrySettings.userId = userId;
return Promise.resolve(userId);
}
}
}
/**
* TelemetryEvent represents a basic telemetry data point
*/
export class TelemetryEvent {
private static PII_HASH_KEY: string = "959069c9-9e93-4fa1-bf16-3f8120d7db0c";
public name: string;
public properties: ITelemetryProperties;
// private eventId: string;
constructor(name: string, properties?: ITelemetryProperties) {
this.name = name;
this.properties = properties || {};
// this.eventId = TelemetryUtils.generateGuid();
}
public setPiiProperty(name: string, value: string): void {
let hmac: any = crypto.createHmac("sha256", new Buffer(TelemetryEvent.PII_HASH_KEY, "utf8"));
let hashedValue: any = hmac.update(value).digest("hex");
this.properties[name] = hashedValue;
if (Telemetry.isInternal()) {
this.properties[name + ".nothashed"] = value;
}
}
}
/**
* `TelemetryActivity` automatically includes timing data, used for scenarios where we want to track performance.
* Calls to `start()` and `end()` are optional, if not called explicitly then the constructor will be the start and send will be the end.
* This event will include a property called `completion.time` which represents time in milliseconds.
*/
export class TelemetryActivity extends TelemetryEvent {
private startTime: [number, number];
private endTime: [number, number];
constructor(name: string, properties?: ITelemetryProperties) {
super(name, properties);
this.start();
}
public start(): void {
this.startTime = process.hrtime();
}
public end(): void {
if (!this.endTime) {
this.endTime = process.hrtime(this.startTime);
// convert [seconds, nanoseconds] to milliseconds and include as property
this.properties["completion.time"] = this.endTime[0] * 1000 + this.endTime[1] / 1000000;
}
}
}
export interface ITelemetryInitOptions {
isExtensionProcess: boolean;
projectRoot: string;
}
export function init(appNameValue: string, appVersion: string, initOptions: ITelemetryInitOptions): Promise<void> {
try {
Telemetry.appName = appNameValue;
return TelemetryUtils.init(appVersion, initOptions);
} catch (err) {
console.error(err);
return Promise.reject(err);
}
}
export function send(event: TelemetryEvent, ignoreOptIn: boolean = false): Promise<void> {
return TelemetryUtils.initDeferredPromise.then(function () {
if (Telemetry.isOptedIn || ignoreOptIn) {
if (event instanceof TelemetryActivity) {
(<TelemetryActivity>event).end();
}
TelemetryUtils.addCommonProperties(event);
try {
if (Telemetry.reporter) {
let properties: ITelemetryEventProperties = {};
let measures: ITelemetryEventMeasures = {};
Object.keys(event.properties || {}).forEach(function (key: string) {
let propertyValue = event.properties[key];
switch (typeof propertyValue) {
case "string":
properties[key] = <string>propertyValue;
break;
case "number":
measures[key] = <number>propertyValue;
break;
default:
properties[key] = JSON.stringify(propertyValue);
break;
}
});
Telemetry.reporter.sendTelemetryEvent(event.name, properties, measures);
}
} catch (err) {
console.error(err);
}
}
});
}
export function isInternal(): boolean {
return TelemetryUtils.userType === TelemetryUtils.USERTYPE_INTERNAL;
}
export function getSessionId(): string {
return TelemetryUtils.sessionId;
}
export function setSessionId(sessionId: string): void {
TelemetryUtils.sessionId = sessionId;
}
interface ITelemetrySettings {
[settingKey: string]: any;
userId?: string;
machineId?: string;
optIn?: boolean;
userType?: string;
}
export interface ITelemetryEventProperties {
[key: string]: string;
}
export interface ITelemetryEventMeasures {
[key: string]: number;
}
export function sendExtensionTelemetry(extensionId: string, extensionVersion: string, appInsightsKey: string, eventName: string, properties: ITelemetryEventProperties, measures: ITelemetryEventMeasures): void {
let reporter: ITelemetryReporter = Telemetry.reporterDictionary[extensionId];
if (!reporter) {
let TelemetryReporter = require("vscode-extension-telemetry").default;
Telemetry.reporterDictionary[extensionId] = new TelemetryReporter(extensionId, extensionVersion, appInsightsKey);
reporter = Telemetry.reporterDictionary[extensionId];
}
reporter.sendTelemetryEvent(eventName, properties, measures);
}
}
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
/// <reference path='../../typings/winreg/winreg.d.ts' />
import * as crypto from "crypto";
import * as fs from "fs";
import * as os from "os";
import * as path from "path";
import * as winreg from "winreg";
import { DeferredPromise } from "../common/node/promise";
import { settingsHome } from "./settingsHelper";
/**
* Telemetry module specialized for vscode integration.
*/
export namespace Telemetry {
export let appName: string;
export let isOptedIn = false;
export let reporter: ITelemetryReporter;
export const reporterDictionary: { [key: string]: ITelemetryReporter } = {};
export interface ITelemetryProperties {
[propertyName: string]: any;
}
export interface ITelemetryReporter {
sendTelemetryEvent(
eventName: string,
properties?: ITelemetryEventProperties,
measures?: ITelemetryEventMeasures,
);
}
class ExtensionTelemetryReporter implements ITelemetryReporter {
private extensionId: string;
private extensionVersion: string;
private appInsightsKey: string;
constructor(
extensionId: string,
extensionVersion: string,
key: string,
projectRoot: string,
) {
this.extensionId = extensionId;
this.extensionVersion = extensionVersion;
this.appInsightsKey = key;
}
public sendTelemetryEvent(
eventName: string,
properties?: ITelemetryEventProperties,
measures?: ITelemetryEventMeasures,
): void {
Telemetry.sendExtensionTelemetry(
this.extensionId,
this.extensionVersion,
this.appInsightsKey,
eventName,
properties,
measures,
);
}
}
class TelemetryUtils {
public static USERTYPE_INTERNAL: string = "Internal";
public static USERTYPE_EXTERNAL: string = "External";
public static userType: string;
public static sessionId: string;
public static optInCollectedForCurrentSession: boolean;
private static initDeferred: DeferredPromise<any> = new DeferredPromise<any>();
private static userId: string;
private static telemetrySettings: ITelemetrySettings = null;
private static TELEMETRY_SETTINGS_FILENAME: string = "VSCodeTelemetrySettings.json";
private static APPINSIGHTS_INSTRUMENTATIONKEY: string =
"AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217"; // Matches vscode telemetry key
private static REGISTRY_SQMCLIENT_NODE: string = "\\SOFTWARE\\Microsoft\\SQMClient";
private static REGISTRY_USERID_VALUE: string = "UserId";
private static INTERNAL_DOMAIN_SUFFIX: string = "microsoft.com";
private static INTERNAL_USER_ENV_VAR: string = "TACOINTERNAL";
private static get telemetrySettingsFile(): string {
return path.join(settingsHome(), TelemetryUtils.TELEMETRY_SETTINGS_FILENAME);
}
public static get initDeferredPromise(): Promise<void> {
return TelemetryUtils.initDeferred.promise;
}
public static init(appVersion: string, initOptions: ITelemetryInitOptions): Promise<any> {
TelemetryUtils.loadSettings();
if (initOptions.isExtensionProcess) {
const TelemetryReporter = require("vscode-extension-telemetry").default;
Telemetry.reporter = new TelemetryReporter(
Telemetry.appName,
appVersion,
TelemetryUtils.APPINSIGHTS_INSTRUMENTATIONKEY,
);
} else {
Telemetry.reporter = new ExtensionTelemetryReporter(
Telemetry.appName,
appVersion,
TelemetryUtils.APPINSIGHTS_INSTRUMENTATIONKEY,
initOptions.projectRoot,
);
}
TelemetryUtils.getUserId().then(function (userId: string): void {
TelemetryUtils.userId = userId;
TelemetryUtils.userType = TelemetryUtils.getUserType();
isOptedIn = TelemetryUtils.getTelemetryOptInSetting();
TelemetryUtils.saveSettings();
TelemetryUtils.initDeferred.resolve(undefined);
});
return TelemetryUtils.initDeferred.promise;
}
public static addCommonProperties(event: any): void {
if (Telemetry.isOptedIn) {
event.properties["cordova.userId"] = TelemetryUtils.userId;
}
event.properties["cordova.userType"] = TelemetryUtils.userType;
}
public static generateGuid(): string {
const hexValues: string[] = [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"A",
"B",
"C",
"D",
"E",
"F",
];
// c.f. rfc4122 (UUID version 4 = xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx)
let oct = "";
let tmp: number;
/* tslint:disable:no-bitwise */
for (let a = 0; a < 4; a++) {
tmp = (4294967296 * Math.random()) | 0;
oct +=
hexValues[tmp & 0xf] +
hexValues[(tmp >> 4) & 0xf] +
hexValues[(tmp >> 8) & 0xf] +
hexValues[(tmp >> 12) & 0xf] +
hexValues[(tmp >> 16) & 0xf] +
hexValues[(tmp >> 20) & 0xf] +
hexValues[(tmp >> 24) & 0xf] +
hexValues[(tmp >> 28) & 0xf];
}
// 'Set the two most significant bits (bits 6 and 7) of the clock_seq_hi_and_reserved to zero and one, respectively'
const clockSequenceHi: string = hexValues[(8 + Math.random() * 4) | 0];
return `${oct.substr(0, 8)}-${oct.substr(9, 4)}-4${oct.substr(
13,
3,
)}-${clockSequenceHi}${oct.substr(16, 3)}-${oct.substr(19, 12)}`;
/* tslint:enable:no-bitwise */
}
public static getTelemetryOptInSetting(): boolean {
if (TelemetryUtils.telemetrySettings.optIn === undefined) {
// Opt-in by default
TelemetryUtils.telemetrySettings.optIn = true;
}
return TelemetryUtils.telemetrySettings.optIn;
}
private static getUserType(): string {
let userType: string = TelemetryUtils.telemetrySettings.userType;
if (userType === undefined) {
if (process.env[TelemetryUtils.INTERNAL_USER_ENV_VAR]) {
userType = TelemetryUtils.USERTYPE_INTERNAL;
} else if (os.platform() === "win32") {
let domain: string = process.env.USERDNSDOMAIN;
domain = domain
? domain
.toLowerCase()
.substring(
domain.length - TelemetryUtils.INTERNAL_DOMAIN_SUFFIX.length,
)
: null;
userType =
domain === TelemetryUtils.INTERNAL_DOMAIN_SUFFIX
? TelemetryUtils.USERTYPE_INTERNAL
: TelemetryUtils.USERTYPE_EXTERNAL;
} else {
userType = TelemetryUtils.USERTYPE_EXTERNAL;
}
TelemetryUtils.telemetrySettings.userType = userType;
}
return userType;
}
private static getRegistryValue(key: string, value: string, hive: string): Promise<string> {
return new Promise((resolve, reject) => {
const regKey = new winreg({ hive, key });
regKey.get(value, function (err: any, itemValue: winreg.RegistryItem) {
if (err) {
// Fail gracefully by returning null if there was an error.
resolve(null);
} else {
resolve(itemValue.value);
}
});
});
}
/*
* Load settings data from settingsHome/TelemetrySettings.json
*/
private static loadSettings(): ITelemetrySettings {
try {
TelemetryUtils.telemetrySettings = JSON.parse(
<any>fs.readFileSync(TelemetryUtils.telemetrySettingsFile),
);
} catch (e) {
// if file does not exist or fails to parse then assume no settings are saved and start over
TelemetryUtils.telemetrySettings = {};
}
return TelemetryUtils.telemetrySettings;
}
/*
* Save settings data in settingsHome/TelemetrySettings.json
*/
private static saveSettings(): void {
if (!fs.existsSync(settingsHome())) {
fs.mkdirSync(settingsHome());
}
fs.writeFileSync(
TelemetryUtils.telemetrySettingsFile,
JSON.stringify(TelemetryUtils.telemetrySettings),
);
}
private static getUniqueId(
regValue: string,
regHive: string,
fallback: () => string,
): Promise<any> {
let uniqueId: string;
if (os.platform() === "win32") {
return TelemetryUtils.getRegistryValue(
TelemetryUtils.REGISTRY_SQMCLIENT_NODE,
regValue,
regHive,
).then((id: string) => {
if (id) {
uniqueId = id.replace(/[{}]/g, "");
return uniqueId;
}
return fallback();
});
}
return Promise.resolve(fallback());
}
private static getUserId(): Promise<string> {
const userId: string = TelemetryUtils.telemetrySettings.userId;
if (!userId) {
return TelemetryUtils.getUniqueId(
TelemetryUtils.REGISTRY_USERID_VALUE,
winreg.HKCU,
TelemetryUtils.generateGuid,
).then((id: string) => {
TelemetryUtils.telemetrySettings.userId = id;
return id;
});
}
TelemetryUtils.telemetrySettings.userId = userId;
return Promise.resolve(userId);
}
}
/**
* TelemetryEvent represents a basic telemetry data point
*/
export class TelemetryEvent {
private static PII_HASH_KEY: string = "959069c9-9e93-4fa1-bf16-3f8120d7db0c";
public name: string;
public properties: ITelemetryProperties;
// private eventId: string;
constructor(name: string, properties?: ITelemetryProperties) {
this.name = name;
this.properties = properties || {};
// this.eventId = TelemetryUtils.generateGuid();
}
public setPiiProperty(name: string, value: string): void {
const hmac: any = crypto.createHmac(
"sha256",
Buffer.from(TelemetryEvent.PII_HASH_KEY, "utf8"),
);
const hashedValue: any = hmac.update(value).digest("hex");
this.properties[name] = hashedValue;
if (Telemetry.isInternal()) {
this.properties[`${name}.nothashed`] = value;
}
}
}
/**
* `TelemetryActivity` automatically includes timing data, used for scenarios where we want to track performance.
* Calls to `start()` and `end()` are optional, if not called explicitly then the constructor will be the start and send will be the end.
* This event will include a property called `completion.time` which represents time in milliseconds.
*/
export class TelemetryActivity extends TelemetryEvent {
private startTime: [number, number];
private endTime: [number, number];
constructor(name: string, properties?: ITelemetryProperties) {
super(name, properties);
this.start();
}
public start(): void {
this.startTime = process.hrtime();
}
public end(): void {
if (!this.endTime) {
this.endTime = process.hrtime(this.startTime);
// convert [seconds, nanoseconds] to milliseconds and include as property
this.properties["completion.time"] =
this.endTime[0] * 1000 + this.endTime[1] / 1000000;
}
}
}
export interface ITelemetryInitOptions {
isExtensionProcess: boolean;
projectRoot: string;
}
export function init(
appNameValue: string,
appVersion: string,
initOptions: ITelemetryInitOptions,
): Promise<void> {
try {
Telemetry.appName = appNameValue;
return TelemetryUtils.init(appVersion, initOptions);
} catch (err) {
console.error(err);
return Promise.reject(err);
}
}
export function send(event: TelemetryEvent, ignoreOptIn: boolean = false): Promise<void> {
return TelemetryUtils.initDeferredPromise.then(function () {
if (Telemetry.isOptedIn || ignoreOptIn) {
if (event instanceof TelemetryActivity) {
(<TelemetryActivity>event).end();
}
TelemetryUtils.addCommonProperties(event);
try {
if (Telemetry.reporter) {
const properties: ITelemetryEventProperties = {};
const measures: ITelemetryEventMeasures = {};
Object.keys(event.properties || {}).forEach(function (key: string) {
const propertyValue = event.properties[key];
switch (typeof propertyValue) {
case "string":
properties[key] = <string>propertyValue;
break;
case "number":
measures[key] = <number>propertyValue;
break;
default:
properties[key] = JSON.stringify(propertyValue);
break;
}
});
Telemetry.reporter.sendTelemetryEvent(event.name, properties, measures);
}
} catch (err) {
console.error(err);
}
}
});
}
export function isInternal(): boolean {
return TelemetryUtils.userType === TelemetryUtils.USERTYPE_INTERNAL;
}
export function getSessionId(): string {
return TelemetryUtils.sessionId;
}
export function setSessionId(sessionId: string): void {
TelemetryUtils.sessionId = sessionId;
}
interface ITelemetrySettings {
[settingKey: string]: any;
userId?: string;
machineId?: string;
optIn?: boolean;
userType?: string;
}
export interface ITelemetryEventProperties {
[key: string]: string;
}
export interface ITelemetryEventMeasures {
[key: string]: number;
}
export function sendExtensionTelemetry(
extensionId: string,
extensionVersion: string,
appInsightsKey: string,
eventName: string,
properties: ITelemetryEventProperties,
measures: ITelemetryEventMeasures,
): void {
let reporter: ITelemetryReporter = Telemetry.reporterDictionary[extensionId];
if (!reporter) {
const TelemetryReporter = require("vscode-extension-telemetry").default;
Telemetry.reporterDictionary[extensionId] = new TelemetryReporter(
extensionId,
extensionVersion,
appInsightsKey,
);
reporter = Telemetry.reporterDictionary[extensionId];
}
reporter.sendTelemetryEvent(eventName, properties, measures);
}
}

Просмотреть файл

@ -1,306 +1,393 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
/* tslint:disable:no-use-before-declare */
import { CordovaProjectHelper, IPluginDetails, ProjectType } from "./cordovaProjectHelper";
import * as fs from "fs";
import * as path from "path";
import { Telemetry } from "./telemetry";
import * as nls from "vscode-nls";
nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
const localize = nls.loadMessageBundle();
export interface ITelemetryPropertyInfo {
value: any;
isPii: boolean;
}
export interface ICommandTelemetryProperties {
[propertyName: string]: ITelemetryPropertyInfo;
}
export interface IExternalTelemetryProvider {
sendTelemetry: (event: string, props: Telemetry.ITelemetryProperties, error?: Error) => void;
}
interface IDictionary<T> {
[key: string]: T;
}
interface IHasErrorCode {
errorCode: number;
}
export abstract class TelemetryGeneratorBase {
protected telemetryProperties: ICommandTelemetryProperties = {};
private componentName: string;
private currentStepStartTime: [number, number];
private currentStep: string = "initialStep";
private errorIndex: number = -1; // In case we have more than one error (We start at -1 because we increment it before using it)
constructor(componentName: string) {
this.componentName = componentName;
this.currentStepStartTime = process.hrtime();
}
public add(baseName: string, value: any, isPii: boolean): TelemetryGeneratorBase {
return this.addWithPiiEvaluator(baseName, value, () => isPii);
}
public addWithPiiEvaluator(baseName: string, value: any, piiEvaluator: { (value: string, name: string): boolean }): TelemetryGeneratorBase {
// We have 3 cases:
// * Object is an array, we add each element as baseNameNNN
// * Object is a hash, we add each element as baseName.KEY
// * Object is a value, we add the element as baseName
try {
if (Array.isArray(value)) {
this.addArray(baseName, <any[]>value, piiEvaluator);
} else if (!!value && (typeof value === "object" || typeof value === "function")) {
this.addHash(baseName, <IDictionary<any>>value, piiEvaluator);
} else {
this.addString(baseName, String(value), piiEvaluator);
}
} catch (error) {
// We don't want to crash the functionality if the telemetry fails.
// This error message will be a javascript error message, so it's not pii
this.addString("telemetryGenerationError." + baseName, String(error), () => false);
}
return this;
}
public addError(error: Error): TelemetryGeneratorBase {
this.add("error.message" + ++this.errorIndex, error.message, /*isPii*/ true);
let errorWithErrorCode: IHasErrorCode = <IHasErrorCode><Record<string, any>>error;
if (errorWithErrorCode.errorCode) {
this.add("error.code" + this.errorIndex, errorWithErrorCode.errorCode, /*isPii*/ false);
}
return this;
}
public time<T>(name: string, codeToMeasure: { (): Promise<T> }): Promise<T> {
let startTime: [number, number] = process.hrtime();
return codeToMeasure()
.finally(() => this.finishTime(name, startTime))
.catch((reason: any) => {
this.addError(reason);
throw reason;
});
}
public step(name: string): TelemetryGeneratorBase {
// First we finish measuring this step time, and we send a telemetry event for this step
this.finishTime(this.currentStep, this.currentStepStartTime);
this.sendCurrentStep();
// Then we prepare to start gathering information about the next step
this.currentStep = name;
this.telemetryProperties = {};
this.currentStepStartTime = process.hrtime();
return this;
}
public send(): void {
if (this.currentStep) {
this.add("lastStepExecuted", this.currentStep, /*isPii*/ false);
}
this.step(null); // Send the last step
}
public getTelemetryProperties(): ICommandTelemetryProperties {
return this.telemetryProperties;
}
protected abstract sendTelemetryEvent(telemetryEvent: Telemetry.TelemetryEvent): void;
private sendCurrentStep(): void {
this.add("step", this.currentStep, /*isPii*/ false);
let telemetryEvent: Telemetry.TelemetryEvent = new Telemetry.TelemetryEvent(this.componentName);
TelemetryHelper.addTelemetryEventProperties(telemetryEvent, this.telemetryProperties);
this.sendTelemetryEvent(telemetryEvent);
}
private addArray(baseName: string, array: any[], piiEvaluator: { (value: string, name: string): boolean }): void {
// Object is an array, we add each element as baseNameNNN
let elementIndex: number = 1; // We send telemetry properties in a one-based index
array.forEach((element: any) => this.addWithPiiEvaluator(baseName + elementIndex++, element, piiEvaluator));
}
private addHash(baseName: string, hash: IDictionary<any>, piiEvaluator: { (value: string, name: string): boolean }): void {
// Object is a hash, we add each element as baseName.KEY
Object.keys(hash).forEach((key: string) => this.addWithPiiEvaluator(baseName + "." + key, hash[key], piiEvaluator));
}
private addString(name: string, value: string, piiEvaluator: { (value: string, name: string): boolean }): void {
this.telemetryProperties[name] = TelemetryHelper.telemetryProperty(value, piiEvaluator(value, name));
}
private combine(...components: string[]): string {
let nonNullComponents: string[] = components.filter((component: string) => component !== null);
return nonNullComponents.join(".");
}
private finishTime(name: string, startTime: [number, number]): void {
let endTime: [number, number] = process.hrtime(startTime);
this.add(this.combine(name, "time"), String(endTime[0] * 1000 + endTime[1] / 1000000), /*isPii*/ false);
}
}
export class TelemetryGenerator extends TelemetryGeneratorBase {
protected sendTelemetryEvent(telemetryEvent: Telemetry.TelemetryEvent): void {
Telemetry.send(telemetryEvent);
}
}
export class TelemetryHelper {
public static createTelemetryEvent(eventName: string): Telemetry.TelemetryEvent {
return new Telemetry.TelemetryEvent(eventName);
}
public static createTelemetryActivity(eventName: string): Telemetry.TelemetryActivity {
return new Telemetry.TelemetryActivity(eventName);
}
public static determineProjectTypes(projectRoot: string): Promise<ProjectType> {
let ionicMajorVersion = CordovaProjectHelper.determineIonicMajorVersion(projectRoot);
let meteor = CordovaProjectHelper.exists(path.join(projectRoot, ".meteor"));
let mobilefirst = CordovaProjectHelper.exists(path.join(projectRoot, ".project"));
let phonegap = CordovaProjectHelper.exists(path.join(projectRoot, "www", "res", ".pgbomit"));
let cordova = CordovaProjectHelper.exists(path.join(projectRoot, "config.xml"));
return Promise.all([meteor, mobilefirst, phonegap, cordova])
.then(([isMeteor, isMobilefirst, isPhonegap, isCordova]) => (
new ProjectType(
isMeteor,
isMobilefirst,
isPhonegap,
isCordova,
ionicMajorVersion,
)
));
}
public static prepareProjectTypesTelemetry(projectType: ProjectType): Partial<ProjectType> {
let relevantProjectTypes: Partial<ProjectType> = Object.entries(projectType)
.reduce((relProjType, [key, val]) => {
// We should send only relevant project types and skip all the rest.
// Relevant types have the true boolean value
if (val) {
relProjType[key] = val;
}
return relProjType;
}, {});
if (relevantProjectTypes.ionicMajorVersion) {
relevantProjectTypes[`isIonic${relevantProjectTypes.ionicMajorVersion}`] = true;
delete relevantProjectTypes.ionicMajorVersion;
}
return relevantProjectTypes;
}
public static telemetryProperty(propertyValue: any, pii?: boolean): ITelemetryPropertyInfo {
return { value: String(propertyValue), isPii: pii || false };
}
public static addTelemetryEventProperties(event: Telemetry.TelemetryEvent, properties: ICommandTelemetryProperties): void {
if (!properties) {
return;
}
Object.keys(properties).forEach(function (propertyName: string): void {
TelemetryHelper.addTelemetryEventProperty(event, propertyName, properties[propertyName].value, properties[propertyName].isPii);
});
}
public static addTelemetryEventProperty(event: Telemetry.TelemetryEvent, propertyName: string, propertyValue: any, isPii: boolean): void {
if (Array.isArray(propertyValue)) {
TelemetryHelper.addMultiValuedTelemetryEventProperty(event, propertyName, propertyValue, isPii);
} else {
TelemetryHelper.setTelemetryEventProperty(event, propertyName, propertyValue, isPii);
}
}
public static generate<T>(name: string, codeGeneratingTelemetry: { (telemetry: TelemetryGenerator): Promise<T> }): Promise<T> {
let generator: TelemetryGenerator = new TelemetryGenerator(name);
return generator.time(null, () => codeGeneratingTelemetry(generator)).finally(() => generator.send());
}
public static sendPluginsList(projectRoot: string, pluginsList: string[]): void {
// Load list of previously sent plugins = previousPlugins
let pluginFilePath = path.join(projectRoot, ".vscode", "plugins.json");
let pluginFileJson: any;
if (CordovaProjectHelper.existsSync(pluginFilePath)) {
try {
let pluginFileJsonContents = fs.readFileSync(pluginFilePath, "utf8").toString();
pluginFileJson = JSON.parse(pluginFileJsonContents);
} catch (error) {
console.error(error);
}
}
// Get list of plugins in pluginsList but not in previousPlugins
let pluginsFileList: string[] = new Array<string>();
if (pluginFileJson && pluginFileJson.plugins) {
pluginsFileList = pluginFileJson.plugins;
} else {
pluginFileJson = new Object();
}
let newPlugins: string[] = new Array<string>();
pluginsList.forEach(plugin => {
if (pluginsFileList.indexOf(plugin) < 0) {
newPlugins.push(plugin);
pluginsFileList.push(plugin);
}
});
// If none, return
if (newPlugins.length === 0) {
return;
}
// Send telemetry event with list of new plugins
let pluginDetails: IPluginDetails[] =
newPlugins.map(pluginName => CordovaProjectHelper.getInstalledPluginDetails(projectRoot, pluginName))
.filter(detail => !!detail);
let pluginEvent = new Telemetry.TelemetryEvent("plugins", { plugins: JSON.stringify(pluginDetails) });
Telemetry.send(pluginEvent);
// Write out new list of previousPlugins
pluginFileJson.plugins = pluginsFileList;
try {
fs.writeFileSync(pluginFilePath, JSON.stringify(pluginFileJson));
} catch (err) {
throw new Error(err.message + localize("CWDDoesntReferToTheWorkspaceRootDirectory", " It seems that 'cwd' parameter doesn't refer to the workspace root directory. Please make sure that 'cwd' contains the path to the workspace root directory."));
}
}
private static setTelemetryEventProperty(event: Telemetry.TelemetryEvent, propertyName: string, propertyValue: string, isPii: boolean): void {
if (isPii) {
event.setPiiProperty(propertyName, String(propertyValue));
} else {
event.properties[propertyName] = String(propertyValue);
}
}
private static addMultiValuedTelemetryEventProperty(event: Telemetry.TelemetryEvent, propertyName: string, propertyValue: string[], isPii: boolean): void {
for (let i: number = 0; i < propertyValue.length; i++) {
TelemetryHelper.setTelemetryEventProperty(event, propertyName + i, propertyValue[i], isPii);
}
}
}
export interface ISimulateTelemetryProperties {
platform?: string;
target: string;
port: number;
simulatePort?: number;
livereload?: boolean;
livereloadDelay?: number;
forcePrepare?: boolean;
}
/* tslint:enable */
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
/* tslint:disable:no-use-before-declare */
import * as fs from "fs";
import * as path from "path";
import * as nls from "vscode-nls";
import { Telemetry } from "./telemetry";
import { CordovaProjectHelper, IPluginDetails, ProjectType } from "./cordovaProjectHelper";
nls.config({
messageFormat: nls.MessageFormat.bundle,
bundleFormat: nls.BundleFormat.standalone,
})();
const localize = nls.loadMessageBundle();
export interface ITelemetryPropertyInfo {
value: any;
isPii: boolean;
}
export interface ICommandTelemetryProperties {
[propertyName: string]: ITelemetryPropertyInfo;
}
export interface IExternalTelemetryProvider {
sendTelemetry: (event: string, props: Telemetry.ITelemetryProperties, error?: Error) => void;
}
interface IDictionary<T> {
[key: string]: T;
}
interface IHasErrorCode {
errorCode: number;
}
export class TelemetryHelper {
public static createTelemetryEvent(eventName: string): Telemetry.TelemetryEvent {
return new Telemetry.TelemetryEvent(eventName);
}
public static createTelemetryActivity(eventName: string): Telemetry.TelemetryActivity {
return new Telemetry.TelemetryActivity(eventName);
}
public static determineProjectTypes(projectRoot: string): Promise<ProjectType> {
const ionicMajorVersion = CordovaProjectHelper.determineIonicMajorVersion(projectRoot);
const meteor = CordovaProjectHelper.exists(path.join(projectRoot, ".meteor"));
const mobilefirst = CordovaProjectHelper.exists(path.join(projectRoot, ".project"));
const phonegap = CordovaProjectHelper.exists(
path.join(projectRoot, "www", "res", ".pgbomit"),
);
const cordova = CordovaProjectHelper.exists(path.join(projectRoot, "config.xml"));
return Promise.all([meteor, mobilefirst, phonegap, cordova]).then(
([isMeteor, isMobilefirst, isPhonegap, isCordova]) =>
new ProjectType(isMeteor, isMobilefirst, isPhonegap, isCordova, ionicMajorVersion),
);
}
public static prepareProjectTypesTelemetry(projectType: ProjectType): Partial<ProjectType> {
const relevantProjectTypes: Partial<ProjectType> = Object.entries(projectType).reduce(
(relProjType, [key, val]) => {
// We should send only relevant project types and skip all the rest.
// Relevant types have the true boolean value
if (val) {
relProjType[key] = val;
}
return relProjType;
},
{},
);
if (relevantProjectTypes.ionicMajorVersion) {
relevantProjectTypes[`isIonic${relevantProjectTypes.ionicMajorVersion}`] = true;
delete relevantProjectTypes.ionicMajorVersion;
}
return relevantProjectTypes;
}
public static telemetryProperty(propertyValue: any, pii?: boolean): ITelemetryPropertyInfo {
return { value: String(propertyValue), isPii: pii || false };
}
public static addTelemetryEventProperties(
event: Telemetry.TelemetryEvent,
properties: ICommandTelemetryProperties,
): void {
if (!properties) {
return;
}
Object.keys(properties).forEach(function (propertyName: string): void {
TelemetryHelper.addTelemetryEventProperty(
event,
propertyName,
properties[propertyName].value,
properties[propertyName].isPii,
);
});
}
public static addTelemetryEventProperty(
event: Telemetry.TelemetryEvent,
propertyName: string,
propertyValue: any,
isPii: boolean,
): void {
if (Array.isArray(propertyValue)) {
TelemetryHelper.addMultiValuedTelemetryEventProperty(
event,
propertyName,
propertyValue,
isPii,
);
} else {
TelemetryHelper.setTelemetryEventProperty(event, propertyName, propertyValue, isPii);
}
}
public static generate<T>(
name: string,
// eslint-disable-next-line
codeGeneratingTelemetry: { (telemetry: TelemetryGenerator): Promise<T> },
): Promise<T> {
// eslint-disable-next-line
const generator: TelemetryGenerator = new TelemetryGenerator(name);
return generator
.time(null, () => codeGeneratingTelemetry(generator))
.finally(() => generator.send());
}
public static sendPluginsList(projectRoot: string, pluginsList: string[]): void {
// Load list of previously sent plugins = previousPlugins
const pluginFilePath = path.join(projectRoot, ".vscode", "plugins.json");
let pluginFileJson: any;
if (CordovaProjectHelper.existsSync(pluginFilePath)) {
try {
const pluginFileJsonContents = fs.readFileSync(pluginFilePath, "utf8").toString();
pluginFileJson = JSON.parse(pluginFileJsonContents);
} catch (error) {
console.error(error);
}
}
// Get list of plugins in pluginsList but not in previousPlugins
let pluginsFileList: string[] = new Array<string>();
if (pluginFileJson && pluginFileJson.plugins) {
pluginsFileList = pluginFileJson.plugins;
} else {
pluginFileJson = new Object();
}
const newPlugins: string[] = new Array<string>();
pluginsList.forEach(plugin => {
if (!pluginsFileList.includes(plugin)) {
newPlugins.push(plugin);
pluginsFileList.push(plugin);
}
});
// If none, return
if (newPlugins.length === 0) {
return;
}
// Send telemetry event with list of new plugins
const pluginDetails: IPluginDetails[] = newPlugins
.map(pluginName =>
CordovaProjectHelper.getInstalledPluginDetails(projectRoot, pluginName),
)
.filter(detail => !!detail);
const pluginEvent = new Telemetry.TelemetryEvent("plugins", {
plugins: JSON.stringify(pluginDetails),
});
Telemetry.send(pluginEvent);
// Write out new list of previousPlugins
pluginFileJson.plugins = pluginsFileList;
try {
fs.writeFileSync(pluginFilePath, JSON.stringify(pluginFileJson));
} catch (err) {
throw new Error(
err.message +
localize(
"CWDDoesntReferToTheWorkspaceRootDirectory",
" It seems that 'cwd' parameter doesn't refer to the workspace root directory. Please make sure that 'cwd' contains the path to the workspace root directory.",
),
);
}
}
private static setTelemetryEventProperty(
event: Telemetry.TelemetryEvent,
propertyName: string,
propertyValue: string,
isPii: boolean,
): void {
if (isPii) {
event.setPiiProperty(propertyName, String(propertyValue));
} else {
event.properties[propertyName] = String(propertyValue);
}
}
private static addMultiValuedTelemetryEventProperty(
event: Telemetry.TelemetryEvent,
propertyName: string,
propertyValue: string[],
isPii: boolean,
): void {
for (let i = 0; i < propertyValue.length; i++) {
TelemetryHelper.setTelemetryEventProperty(
event,
propertyName + i,
propertyValue[i],
isPii,
);
}
}
}
export abstract class TelemetryGeneratorBase {
protected telemetryProperties: ICommandTelemetryProperties = {};
private componentName: string;
private currentStepStartTime: [number, number];
private currentStep: string = "initialStep";
private errorIndex: number = -1; // In case we have more than one error (We start at -1 because we increment it before using it)
constructor(componentName: string) {
this.componentName = componentName;
this.currentStepStartTime = process.hrtime();
}
public add(baseName: string, value: any, isPii: boolean): TelemetryGeneratorBase {
return this.addWithPiiEvaluator(baseName, value, () => isPii);
}
public addWithPiiEvaluator(
baseName: string,
value: any,
piiEvaluator: { (value: string, name: string): boolean },
): TelemetryGeneratorBase {
// We have 3 cases:
// * Object is an array, we add each element as baseNameNNN
// * Object is a hash, we add each element as baseName.KEY
// * Object is a value, we add the element as baseName
try {
if (Array.isArray(value)) {
this.addArray(baseName, <any[]>value, piiEvaluator);
} else if (!!value && (typeof value === "object" || typeof value === "function")) {
this.addHash(baseName, <IDictionary<any>>value, piiEvaluator);
} else {
this.addString(baseName, String(value), piiEvaluator);
}
} catch (error) {
// We don't want to crash the functionality if the telemetry fails.
// This error message will be a javascript error message, so it's not pii
this.addString(`telemetryGenerationError.${baseName}`, String(error), () => false);
}
return this;
}
public addError(error: Error): TelemetryGeneratorBase {
this.add(`error.message${++this.errorIndex}`, error.message, /* isPii*/ true);
const errorWithErrorCode: IHasErrorCode = <IHasErrorCode>(<Record<string, any>>error);
if (errorWithErrorCode.errorCode) {
this.add(
`error.code${this.errorIndex}`,
errorWithErrorCode.errorCode,
/* isPii*/ false,
);
}
return this;
}
public time<T>(name: string, codeToMeasure: { (): Promise<T> }): Promise<T> {
const startTime: [number, number] = process.hrtime();
return codeToMeasure()
.finally(() => this.finishTime(name, startTime))
.catch((reason: any) => {
this.addError(reason);
throw reason;
});
}
public step(name: string): TelemetryGeneratorBase {
// First we finish measuring this step time, and we send a telemetry event for this step
this.finishTime(this.currentStep, this.currentStepStartTime);
this.sendCurrentStep();
// Then we prepare to start gathering information about the next step
this.currentStep = name;
this.telemetryProperties = {};
this.currentStepStartTime = process.hrtime();
return this;
}
public send(): void {
if (this.currentStep) {
this.add("lastStepExecuted", this.currentStep, /* isPii*/ false);
}
this.step(null); // Send the last step
}
public getTelemetryProperties(): ICommandTelemetryProperties {
return this.telemetryProperties;
}
protected abstract sendTelemetryEvent(telemetryEvent: Telemetry.TelemetryEvent): void;
private sendCurrentStep(): void {
this.add("step", this.currentStep, /* isPii*/ false);
const telemetryEvent: Telemetry.TelemetryEvent = new Telemetry.TelemetryEvent(
this.componentName,
);
TelemetryHelper.addTelemetryEventProperties(telemetryEvent, this.telemetryProperties);
this.sendTelemetryEvent(telemetryEvent);
}
private addArray(
baseName: string,
array: any[],
piiEvaluator: { (value: string, name: string): boolean },
): void {
// Object is an array, we add each element as baseNameNNN
let elementIndex = 1; // We send telemetry properties in a one-based index
array.forEach((element: any) =>
this.addWithPiiEvaluator(baseName + elementIndex++, element, piiEvaluator),
);
}
private addHash(
baseName: string,
hash: IDictionary<any>,
piiEvaluator: { (value: string, name: string): boolean },
): void {
// Object is a hash, we add each element as baseName.KEY
Object.keys(hash).forEach((key: string) =>
this.addWithPiiEvaluator(`${baseName}.${key}`, hash[key], piiEvaluator),
);
}
private addString(
name: string,
value: string,
piiEvaluator: { (value: string, name: string): boolean },
): void {
this.telemetryProperties[name] = TelemetryHelper.telemetryProperty(
value,
piiEvaluator(value, name),
);
}
private combine(...components: string[]): string {
const nonNullComponents: string[] = components.filter(
(component: string) => component !== null,
);
return nonNullComponents.join(".");
}
private finishTime(name: string, startTime: [number, number]): void {
const endTime: [number, number] = process.hrtime(startTime);
this.add(
this.combine(name, "time"),
String(endTime[0] * 1000 + endTime[1] / 1000000),
/* isPii*/ false,
);
}
}
export class TelemetryGenerator extends TelemetryGeneratorBase {
protected sendTelemetryEvent(telemetryEvent: Telemetry.TelemetryEvent): void {
Telemetry.send(telemetryEvent);
}
}
export interface ISimulateTelemetryProperties {
platform?: string;
target: string;
port: number;
simulatePort?: number;
livereload?: boolean;
livereloadDelay?: number;
forcePrepare?: boolean;
}
/* tslint:enable */

Просмотреть файл

@ -9,7 +9,10 @@ import { findFileInFolderHierarchy } from "./extensionHelper";
export class TsdHelper {
private static CORDOVA_TYPINGS_FOLDERNAME = "CordovaTypings";
private static CORDOVA_TYPINGS_PATH = findFileInFolderHierarchy(__dirname, TsdHelper.CORDOVA_TYPINGS_FOLDERNAME);
private static CORDOVA_TYPINGS_PATH = findFileInFolderHierarchy(
__dirname,
TsdHelper.CORDOVA_TYPINGS_FOLDERNAME,
);
private static USER_TYPINGS_FOLDERNAME = "typings";
/**
@ -18,55 +21,75 @@ export class TsdHelper {
* {typeDefsPath} - the relative paths of all plugin type definitions that need to be
* installed (relative to <project_root>\.vscode\typings)
*/
public static installTypings(typingsFolderPath: string, typeDefsPath: string[], projectRoot?: string): void {
let installedTypeDefs: string[] = [];
public static installTypings(
typingsFolderPath: string,
typeDefsPath: string[],
projectRoot?: string,
): void {
const installedTypeDefs: string[] = [];
TelemetryHelper.generate("addTypings", (generator) => {
TelemetryHelper.generate("addTypings", generator => {
generator.add("addedTypeDefinitions", typeDefsPath, false);
return Promise.all(typeDefsPath.map((relativePath: string) => {
let src = path.resolve(TsdHelper.CORDOVA_TYPINGS_PATH, relativePath);
let dest = path.resolve(typingsFolderPath, relativePath);
return Promise.all(
typeDefsPath.map((relativePath: string) => {
const src = path.resolve(TsdHelper.CORDOVA_TYPINGS_PATH, relativePath);
const dest = path.resolve(typingsFolderPath, relativePath);
// Check if we've previously copied these typings
if (CordovaProjectHelper.existsSync(dest)) {
return Promise.resolve(void 0);
}
// Check if the user has these typings somewhere else in his project
if (projectRoot) {
// We check for short path (e.g. projectRoot/typings/angular.d.ts) and long path (e.g. projectRoot/typings/angular/angular.d.ts)
let userTypingsShortPath = path.join(projectRoot, TsdHelper.USER_TYPINGS_FOLDERNAME, path.basename(relativePath));
let userTypingsLongPath = path.join(projectRoot, TsdHelper.USER_TYPINGS_FOLDERNAME, relativePath);
if (CordovaProjectHelper.existsSync(userTypingsShortPath) || CordovaProjectHelper.existsSync(userTypingsLongPath)) {
return Promise.resolve(void 0);
// Check if we've previously copied these typings
if (CordovaProjectHelper.existsSync(dest)) {
return Promise.resolve(undefined);
}
}
return TsdHelper.installTypeDefinitionFile(src, dest)
// Save installed typedef to write them all at once later
.then(() => installedTypeDefs.push(dest));
}));
})
.finally(() => {
// Check if the user has these typings somewhere else in his project
if (projectRoot) {
// We check for short path (e.g. projectRoot/typings/angular.d.ts) and long path (e.g. projectRoot/typings/angular/angular.d.ts)
const userTypingsShortPath = path.join(
projectRoot,
TsdHelper.USER_TYPINGS_FOLDERNAME,
path.basename(relativePath),
);
const userTypingsLongPath = path.join(
projectRoot,
TsdHelper.USER_TYPINGS_FOLDERNAME,
relativePath,
);
if (
CordovaProjectHelper.existsSync(userTypingsShortPath) ||
CordovaProjectHelper.existsSync(userTypingsLongPath)
) {
return Promise.resolve(undefined);
}
}
return (
TsdHelper.installTypeDefinitionFile(src, dest)
// Save installed typedef to write them all at once later
.then(() => installedTypeDefs.push(dest))
);
}),
);
}).finally(() => {
if (installedTypeDefs.length === 0) return;
let typingsFolder = path.resolve(projectRoot, TsdHelper.USER_TYPINGS_FOLDERNAME);
let indexFile = path.resolve(typingsFolder, "cordova-typings.d.ts");
const typingsFolder = path.resolve(projectRoot, TsdHelper.USER_TYPINGS_FOLDERNAME);
const indexFile = path.resolve(typingsFolder, "cordova-typings.d.ts");
// Ensure that the 'typings' folder exits; if not, create it
if (!CordovaProjectHelper.existsSync(typingsFolder)) {
CordovaProjectHelper.makeDirectoryRecursive(typingsFolder);
}
let references = CordovaProjectHelper.existsSync(indexFile) ? fs.readFileSync(indexFile, "utf8") : "";
let referencesToAdd = installedTypeDefs
const references = CordovaProjectHelper.existsSync(indexFile)
? fs.readFileSync(indexFile, "utf8")
: "";
const referencesToAdd = installedTypeDefs
// Do not add references to typedefs that are not exist,
// this rarely happens if typedef file fails to copy
.filter(typeDef => CordovaProjectHelper.existsSync(typeDef))
.map(typeDef => path.relative(typingsFolder, typeDef))
// Avoid adding duplicates if reference already exist in index
.filter(typeDef => references.indexOf(typeDef) < 0)
.filter(typeDef => !references.includes(typeDef))
.map(typeDef => `/// <reference path="${typeDef}"/>`);
if (referencesToAdd.length === 0) return;
@ -75,7 +98,11 @@ export class TsdHelper {
});
}
public static removeTypings(typingsFolderPath: string, typeDefsToRemove: string[], projectRoot: string): void {
public static removeTypings(
typingsFolderPath: string,
typeDefsToRemove: string[],
projectRoot: string,
): void {
if (typeDefsToRemove.length === 0) return;
typeDefsToRemove.forEach(typeDef => {
@ -85,7 +112,11 @@ export class TsdHelper {
});
let references = [];
let indexFile = path.resolve(projectRoot, TsdHelper.USER_TYPINGS_FOLDERNAME, "cordova-typings.d.ts");
const indexFile = path.resolve(
projectRoot,
TsdHelper.USER_TYPINGS_FOLDERNAME,
"cordova-typings.d.ts",
);
try {
references = fs.readFileSync(indexFile, "utf8").split("\n");
} catch (e) {
@ -94,19 +125,21 @@ export class TsdHelper {
return;
}
let referencesToPersist = references.filter(ref =>
// Filter out references that we need to delete
ref && !typeDefsToRemove.some(typedef => ref.indexOf(typedef) >= 0));
const referencesToPersist = references.filter(
ref =>
// Filter out references that we need to delete
ref && !typeDefsToRemove.some(typedef => ref.includes(typedef)),
);
referencesToPersist.length === 0 ?
fs.unlinkSync(indexFile) :
// Write filtered references back to index file
fs.writeFileSync(indexFile, referencesToPersist.join("\n"), "utf8");
referencesToPersist.length === 0
? fs.unlinkSync(indexFile)
: // Write filtered references back to index file
fs.writeFileSync(indexFile, referencesToPersist.join("\n"), "utf8");
}
private static installTypeDefinitionFile(src: string, dest: string): Promise<any> {
// Ensure that the parent folder exits; if not, create the hierarchy of directories
let parentFolder = path.resolve(dest, "..");
const parentFolder = path.resolve(dest, "..");
if (!CordovaProjectHelper.existsSync(parentFolder)) {
CordovaProjectHelper.makeDirectoryRecursive(parentFolder);
}

Просмотреть файл

@ -4,11 +4,7 @@
import * as path from "path";
import * as vscode from "vscode";
import * as assert from "assert";
import {
Connection,
Server,
WebSocketTransport
} from "vscode-cdp-proxy";
import { Connection, Server, WebSocketTransport } from "vscode-cdp-proxy";
import { convertWindowsPathToUnixOne } from "../testUtils";
import { generateRandomPortNumber, delay } from "../../src/utils/extensionHelper";
import { CordovaCDPProxy } from "../../src/debugger/cdp-proxy/cordovaCDPProxy";
@ -32,12 +28,16 @@ suite("cordovaCDPProxy", function () {
const cdpProxyHostAddress = "127.0.0.1"; // localhost
const cdpProxyPort = generateRandomPortNumber();
const cdpProxyLogLevel = LogLevel.Custom;
const testProjectPath = process.platform === "win32" ?
convertWindowsPathToUnixOne(path.join(__dirname, "..", "resources", "testCordovaProject")) :
path.join(__dirname, "..", "resources", "testCordovaProject");
const testProjectPath =
process.platform === "win32"
? convertWindowsPathToUnixOne(
path.join(__dirname, "..", "resources", "testCordovaProject"),
)
: path.join(__dirname, "..", "resources", "testCordovaProject");
let proxy: CordovaCDPProxy;
let cancellationTokenSource: vscode.CancellationTokenSource = new vscode.CancellationTokenSource();
let cancellationTokenSource: vscode.CancellationTokenSource =
new vscode.CancellationTokenSource();
let wsTargetPort = generateRandomPortNumber();
let wsTargetServer: Server | null;
@ -65,7 +65,10 @@ suite("cordovaCDPProxy", function () {
}
}
function prepareCDPProxyInternalEntities(debugType: "ionic" | "cordova" | "simulate", cdpHandlerType: "chrome" | "safari"): ICDPProxyInternalEntities {
function prepareCDPProxyInternalEntities(
debugType: "ionic" | "cordova" | "simulate",
cdpHandlerType: "chrome" | "safari",
): ICDPProxyInternalEntities {
let attachArgs: ICordovaAttachRequestArgs;
let projectType: ProjectType;
let sourcemapPathTransformer: SourcemapPathTransformer;
@ -99,12 +102,22 @@ suite("cordovaCDPProxy", function () {
if (cdpHandlerType === "chrome") {
sourcemapPathTransformer = new SourcemapPathTransformer(attachArgs, projectType);
cdpMessageHandler = CDPMessageHandlerCreator.create(sourcemapPathTransformer, projectType, attachArgs, true);
cdpMessageHandler = CDPMessageHandlerCreator.create(
sourcemapPathTransformer,
projectType,
attachArgs,
true,
);
} else {
attachArgs.platform = "ios";
sourcemapPathTransformer = new SourcemapPathTransformer(attachArgs, projectType);
cdpMessageHandler = CDPMessageHandlerCreator.create(sourcemapPathTransformer, projectType, attachArgs, false);
cdpMessageHandler = CDPMessageHandlerCreator.create(
sourcemapPathTransformer,
projectType,
attachArgs,
false,
);
}
return {
@ -122,22 +135,23 @@ suite("cordovaCDPProxy", function () {
cdpProxyPort,
cdpProxyInternalEntities.sourcemapPathTransformer,
cdpProxyInternalEntities.projectType,
cdpProxyInternalEntities.attachArgs
cdpProxyInternalEntities.attachArgs,
);
proxy.setApplicationTargetPort(wsTargetPort);
await proxy.createServer(cdpProxyLogLevel, cancellationTokenSource.token);
await Server.create({ host: "localhost", port: wsTargetPort })
.then((server: Server) => {
wsTargetServer = server;
await Server.create({ host: "localhost", port: wsTargetPort }).then((server: Server) => {
wsTargetServer = server;
server.onConnection(([connection, request]: [Connection, any]) => {
targetConnection = connection;
});
server.onConnection(([connection, request]: [Connection, any]) => {
targetConnection = connection;
});
});
const proxyUri = await new DebuggerEndpointHelper().getWSEndpoint(`http://${cdpProxyHostAddress}:${cdpProxyPort}`);
const proxyUri = await new DebuggerEndpointHelper().getWSEndpoint(
`http://${cdpProxyHostAddress}:${cdpProxyPort}`,
);
debugConnection = new Connection(await WebSocketTransport.create(proxyUri));
// Due to the time limit, sooner or later this cycle will end
@ -154,15 +168,26 @@ suite("cordovaCDPProxy", function () {
suite("ChromeCDPMessageHandler", () => {
suite("Pure Cordova", () => {
suiteSetup(() => {
let cdpProxyInternalEntities = prepareCDPProxyInternalEntities("cordova", "chrome");
Object.assign(proxy, { CDPMessageHandler: cdpProxyInternalEntities.cdpMessageHandler });
let cdpProxyInternalEntities = prepareCDPProxyInternalEntities(
"cordova",
"chrome",
);
Object.assign(proxy, {
CDPMessageHandler: cdpProxyInternalEntities.cdpMessageHandler,
});
});
test("Messages should be delivered correctly with ChromeCDPMessageHandler", async () => {
const targetMessageStart = { method: "Target.start", params: { reason: "test" } };
const debuggerMessageStart = { method: "Debugger.start", params: { reason: "test" } };
const targetMessageStart = {
method: "Target.start",
params: { reason: "test" },
};
const debuggerMessageStart = {
method: "Debugger.start",
params: { reason: "test" },
};
const messageFromTarget = await new Promise((resolve) => {
const messageFromTarget = await new Promise(resolve => {
targetConnection?.send(targetMessageStart);
debugConnection?.onCommand((evt: any) => {
@ -170,7 +195,7 @@ suite("cordovaCDPProxy", function () {
});
});
const messageFromDebugger = await new Promise((resolve) => {
const messageFromDebugger = await new Promise(resolve => {
debugConnection?.send(debuggerMessageStart);
targetConnection?.onCommand((evt: any) => {
@ -184,15 +209,24 @@ suite("cordovaCDPProxy", function () {
suite("Simulate", () => {
suiteSetup(() => {
let cdpProxyInternalEntities = prepareCDPProxyInternalEntities("simulate", "chrome");
Object.assign(proxy, { CDPMessageHandler: cdpProxyInternalEntities.cdpMessageHandler });
let cdpProxyInternalEntities = prepareCDPProxyInternalEntities(
"simulate",
"chrome",
);
Object.assign(proxy, {
CDPMessageHandler: cdpProxyInternalEntities.cdpMessageHandler,
});
});
test(`Message from the target with ${CDP_API_NAMES.DEBUGGER_SCRIPT_PARSED} should replace the "url" prop with the correct absolute path for the source`, async () => {
const processedMessageRef = {
method: CDP_API_NAMES.DEBUGGER_SCRIPT_PARSED,
params: {
url: `file://${process.platform === "win32" ? "/" + testProjectPath : testProjectPath}/www/js/index.js`,
url: `file://${
process.platform === "win32"
? "/" + testProjectPath
: testProjectPath
}/www/js/index.js`,
},
};
@ -203,7 +237,7 @@ suite("cordovaCDPProxy", function () {
},
};
const processedMessage = await new Promise((resolve) => {
const processedMessage = await new Promise(resolve => {
targetConnection?.send(targetMessageTest);
debugConnection?.onCommand((evt: any) => {
@ -218,15 +252,24 @@ suite("cordovaCDPProxy", function () {
suite("Ionic", () => {
suiteSetup(() => {
let cdpProxyInternalEntities = prepareCDPProxyInternalEntities("ionic", "chrome");
Object.assign(proxy, { CDPMessageHandler: cdpProxyInternalEntities.cdpMessageHandler });
let cdpProxyInternalEntities = prepareCDPProxyInternalEntities(
"ionic",
"chrome",
);
Object.assign(proxy, {
CDPMessageHandler: cdpProxyInternalEntities.cdpMessageHandler,
});
});
test(`Message from the target with ${CDP_API_NAMES.DEBUGGER_SCRIPT_PARSED} should replace the "url" prop with the correct absolute path for the source`, async () => {
const processedMessageRef = {
method: CDP_API_NAMES.DEBUGGER_SCRIPT_PARSED,
params: {
url: `file://${process.platform === "win32" ? "/" + testProjectPath : testProjectPath}/www/main.js`,
url: `file://${
process.platform === "win32"
? "/" + testProjectPath
: testProjectPath
}/www/main.js`,
},
};
@ -237,7 +280,7 @@ suite("cordovaCDPProxy", function () {
},
};
const processedMessage = await new Promise((resolve) => {
const processedMessage = await new Promise(resolve => {
targetConnection?.send(targetMessageTest);
debugConnection?.onCommand((evt: any) => {
@ -252,16 +295,26 @@ suite("cordovaCDPProxy", function () {
const processedMessageRef = {
method: CDP_API_NAMES.DEBUGGER_SET_BREAKPOINT_BY_URL,
params: {
urlRegex: `http:\\/\\/localhost\\/${process.platform === "win32" ? "[mM][aA][iI][nN]\\.[jJ][sS]" : "main\\.js"}`,
urlRegex: `http:\\/\\/localhost\\/${
process.platform === "win32"
? "[mM][aA][iI][nN]\\.[jJ][sS]"
: "main\\.js"
}`,
},
};
let testUrlRegex;
if (process.platform === "win32") {
const testPathRegex = `${testProjectPath.replace(/([\/\.])/g, "\\$1")}\\\\[wW][wW][wW]\\\\[mM][aA][iI][nN]\\.[jJ][sS]`;
const testPathRegex = `${testProjectPath.replace(
/([\/\.])/g,
"\\$1",
)}\\\\[wW][wW][wW]\\\\[mM][aA][iI][nN]\\.[jJ][sS]`;
testUrlRegex = `[fF][iI][lL][eE]:\\/\\/${testPathRegex}|${testPathRegex}`;
} else {
const testPathRegex = `${testProjectPath}/www/main.js`.replace(/([\/\.])/g, "\\$1");
const testPathRegex = `${testProjectPath}/www/main.js`.replace(
/([\/\.])/g,
"\\$1",
);
testUrlRegex = `file:\\/\\/${testPathRegex}|${testPathRegex}`;
}
@ -272,7 +325,7 @@ suite("cordovaCDPProxy", function () {
},
};
const processedMessage = await new Promise((resolve) => {
const processedMessage = await new Promise(resolve => {
debugConnection?.send(debuggerMessageTest);
targetConnection?.onCommand((evt: any) => {
@ -288,9 +341,14 @@ suite("cordovaCDPProxy", function () {
suite("Ionic", () => {
const targetPageId = "page-7";
suiteSetup(() => {
let cdpProxyInternalEntities = prepareCDPProxyInternalEntities("ionic", "safari");
let cdpProxyInternalEntities = prepareCDPProxyInternalEntities(
"ionic",
"safari",
);
(cdpProxyInternalEntities.cdpMessageHandler as any).targetId = targetPageId;
Object.assign(proxy, { CDPMessageHandler: cdpProxyInternalEntities.cdpMessageHandler });
Object.assign(proxy, {
CDPMessageHandler: cdpProxyInternalEntities.cdpMessageHandler,
});
});
test("Messages should be wrapped in the Target form", async () => {
@ -310,7 +368,7 @@ suite("cordovaCDPProxy", function () {
},
};
const processedMessage = await new Promise((resolve) => {
const processedMessage = await new Promise(resolve => {
debugConnection?.send(debuggerMessageTest);
targetConnection?.onReply((evt: any) => {

Просмотреть файл

@ -11,7 +11,8 @@ import { CordovaProjectHelper } from "../src/utils/cordovaProjectHelper";
suite("extensionContext", () => {
let testProjectPath: string = path.resolve(__dirname, "resources", "testCordovaProject");
let cordovaTypeDefDir: string = CordovaProjectHelper.getOrCreateTypingsTargetPath(testProjectPath);
let cordovaTypeDefDir: string =
CordovaProjectHelper.getOrCreateTypingsTargetPath(testProjectPath);
suiteTeardown(() => {
// Cleanup the target folder for type definitions
@ -21,28 +22,45 @@ suite("extensionContext", () => {
});
test("Verify that the commands registered by Cordova extension are loaded", () => {
return vscode.commands.getCommands(true)
.then((results) => {
let cordovaCmdsAvailable = results.filter((commandName: string) => {
return commandName.indexOf("cordova.") > -1;
});
assert.deepStrictEqual(cordovaCmdsAvailable, ["cordova.restart", "cordova.prepare", "cordova.build", "cordova.run", "cordova.simulate.android", "cordova.simulate.ios"]);
return vscode.commands.getCommands(true).then(results => {
let cordovaCmdsAvailable = results.filter((commandName: string) => {
return commandName.indexOf("cordova.") > -1;
});
assert.deepStrictEqual(cordovaCmdsAvailable, [
"cordova.restart",
"cordova.prepare",
"cordova.build",
"cordova.run",
"cordova.simulate.android",
"cordova.simulate.ios",
]);
});
});
suite("smokeTestsContext", () => {
test("Execute Commands from the command palette", () => {
// Remove the explicit Cordova Android version after a release of the 'cordova-serve' package
// with the fix for the issue https://github.com/apache/cordova-serve/issues/43
return testUtils.addCordovaComponents("platform", testProjectPath, ["android@9.1.0"])
return testUtils
.addCordovaComponents("platform", testProjectPath, ["android@9.1.0"])
.then(() => {
return vscode.commands.executeCommand("cordova.build");
}).then(() => {
})
.then(() => {
return delay(10000);
}).then(_res => {
let androidBuildPath = path.resolve(testProjectPath, "platforms", "android", "app", "build");
})
.then(_res => {
let androidBuildPath = path.resolve(
testProjectPath,
"platforms",
"android",
"app",
"build",
);
assert.ok(CordovaProjectHelper.existsSync(androidBuildPath));
return testUtils.removeCordovaComponents("platform", testProjectPath, ["android"]);
return testUtils.removeCordovaComponents("platform", testProjectPath, [
"android",
]);
});
});
});
@ -51,13 +69,20 @@ suite("extensionContext", () => {
test("Verify that the simulate command launches the simulate server", () => {
// Remove the explicit Cordova Android version after a release of the 'cordova-serve' package
// with the fix for the issue https://github.com/apache/cordova-serve/issues/43
return testUtils.addCordovaComponents("platform", testProjectPath, ["android@9.1.0"])
return testUtils
.addCordovaComponents("platform", testProjectPath, ["android@9.1.0"])
.then(() => vscode.commands.executeCommand("cordova.simulate.android"))
.then(() => testUtils.isUrlReachable("http://localhost:8000/simulator/index.html"))
.then((simHostStarted: boolean) => assert(simHostStarted, "The simulation host is running."))
.then((simHostStarted: boolean) =>
assert(simHostStarted, "The simulation host is running."),
)
.then(() => testUtils.isUrlReachable("http://localhost:8000/index.html"))
.then((appHostStarted: boolean) => assert(appHostStarted, "The application host is running."))
.finally(() => testUtils.removeCordovaComponents("platform", testProjectPath, ["android"]));
.then((appHostStarted: boolean) =>
assert(appHostStarted, "The application host is running."),
)
.finally(() =>
testUtils.removeCordovaComponents("platform", testProjectPath, ["android"]),
);
});
});
});

Просмотреть файл

@ -4,24 +4,27 @@
import * as assert from "assert";
import { execCommand } from "../../src/debugger/extension";
suite("commandExecutor", function() {
suite("execCommand", function() {
test("should execute a command", function() {
return execCommand("node", ["-v"], (message => { console.log(message); }))
.then(result => {
assert(result);
});
suite("commandExecutor", function () {
suite("execCommand", function () {
test("should execute a command", function () {
return execCommand("node", ["-v"], message => {
console.log(message);
}).then(result => {
assert(result);
});
});
test("should reject on bad command", () => {
return execCommand("ber", ["test"], (message => { console.log(message); }))
.then(result => {
assert.fail("bar test should not be a valid command");
return execCommand("ber", ["test"], message => {
console.log(message);
})
.catch(err => {
console.log(err.message);
assert.strictEqual(err.message, "spawn ber ENOENT");
});
.then(result => {
assert.fail("bar test should not be a valid command");
})
.catch(err => {
console.log(err.message);
assert.strictEqual(err.message, "spawn ber ENOENT");
});
});
});
});

Просмотреть файл

@ -1,31 +1,35 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
import * as should from "should";
import * as fs from "fs";
import * as sinon from "sinon";
import * as plist from "plist";
import { CordovaIosDeviceLauncher } from "../../src/debugger/cordovaIosDeviceLauncher";
suite("cordovaIosDeviceLauncher", function () {
let readdirMock;
let readFileSyncMock;
let parseMock;
suiteTeardown(() => {
readdirMock.restore();
readFileSyncMock.restore();
parseMock.restore();
});
test("should be able to find the bundle identifier", () => {
readdirMock = (sinon.stub(fs.promises, "readdir") as any).returns(Promise.resolve(["foo", "bar.xcodeproj"]));
readFileSyncMock = sinon.stub(fs, "readFileSync").returns("");
parseMock = sinon.stub(plist, "parse").returns({CFBundleIdentifier: "test.bundle.identifier"});
return CordovaIosDeviceLauncher.getBundleIdentifier("testApp").then((bundleId) => {
should.equal(bundleId, "test.bundle.identifier");
});
});
});
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
import * as should from "should";
import * as fs from "fs";
import * as sinon from "sinon";
import * as plist from "plist";
import { CordovaIosDeviceLauncher } from "../../src/debugger/cordovaIosDeviceLauncher";
suite("cordovaIosDeviceLauncher", function () {
let readdirMock;
let readFileSyncMock;
let parseMock;
suiteTeardown(() => {
readdirMock.restore();
readFileSyncMock.restore();
parseMock.restore();
});
test("should be able to find the bundle identifier", () => {
readdirMock = (sinon.stub(fs.promises, "readdir") as any).returns(
Promise.resolve(["foo", "bar.xcodeproj"]),
);
readFileSyncMock = sinon.stub(fs, "readFileSync").returns("");
parseMock = sinon
.stub(plist, "parse")
.returns({ CFBundleIdentifier: "test.bundle.identifier" });
return CordovaIosDeviceLauncher.getBundleIdentifier("testApp").then(bundleId => {
should.equal(bundleId, "test.bundle.identifier");
});
});
});

Просмотреть файл

@ -32,41 +32,89 @@ suite("AndroidTargetManager", function () {
let showQuickPickStub: Sinon.SinonStub;
function revertTargetsStates() {
onlineEmulator1 = {name: "emulatorName1", id: "emulator-5551", isVirtualTarget: true, isOnline: true};
onlineEmulator2 = {name: "emulatorName2", id: "emulator-5552", isVirtualTarget: true, isOnline: true};
onlineEmulator1 = {
name: "emulatorName1",
id: "emulator-5551",
isVirtualTarget: true,
isOnline: true,
};
onlineEmulator2 = {
name: "emulatorName2",
id: "emulator-5552",
isVirtualTarget: true,
isOnline: true,
};
offlineEmulator1 = {name: "emulatorName3", id: undefined, isVirtualTarget: true, isOnline: false}; //id: emulator-5553
offlineEmulator2 = {name: "emulatorName4", id: undefined, isVirtualTarget: true, isOnline: false}; //id: emulator-5554
offlineEmulator1 = {
name: "emulatorName3",
id: undefined,
isVirtualTarget: true,
isOnline: false,
}; //id: emulator-5553
offlineEmulator2 = {
name: "emulatorName4",
id: undefined,
isVirtualTarget: true,
isOnline: false,
}; //id: emulator-5554
device1 = {id: "deviceid1", isVirtualTarget: false, isOnline: true};
device2 = {id: "deviceid2", isVirtualTarget: false, isOnline: true};
device1 = { id: "deviceid1", isVirtualTarget: false, isOnline: true };
device2 = { id: "deviceid2", isVirtualTarget: false, isOnline: true };
}
suiteSetup(() => {
revertTargetsStates();
getAbdsNamesStub = Sinon.stub(adbHelper, "getAvdsNames").callsFake(async () => {
return [onlineEmulator1.name, onlineEmulator2.name, offlineEmulator1.name, offlineEmulator2.name];
return [
onlineEmulator1.name,
onlineEmulator2.name,
offlineEmulator1.name,
offlineEmulator2.name,
];
});
getOnlineTargetsStub = Sinon.stub(adbHelper, "getOnlineTargets").callsFake(async () => {
return <IDebuggableMobileTarget[]> [onlineEmulator1, onlineEmulator2, offlineEmulator1, offlineEmulator2, device1, device2].filter(target => target.isOnline);
return <IDebuggableMobileTarget[]>(
[
onlineEmulator1,
onlineEmulator2,
offlineEmulator1,
offlineEmulator2,
device1,
device2,
].filter(target => target.isOnline)
);
});
launchSimulatorStub = Sinon.stub(<any> androidTargetManager, "launchSimulator").callsFake(async (emulatorTarget: IMobileTarget) => {
emulatorTarget.isOnline = true;
switch (emulatorTarget.name) {
case "emulatorName1": emulatorTarget.id = "emulator-5551"; break;
case "emulatorName2": emulatorTarget.id = "emulator-5552"; break;
case "emulatorName3": emulatorTarget.id = "emulator-5553"; break;
case "emulatorName4": emulatorTarget.id = "emulator-5554"; break;
}
return AndroidTarget.fromInterface(<IDebuggableMobileTarget> emulatorTarget);
});
launchSimulatorStub = Sinon.stub(<any>androidTargetManager, "launchSimulator").callsFake(
async (emulatorTarget: IMobileTarget) => {
emulatorTarget.isOnline = true;
switch (emulatorTarget.name) {
case "emulatorName1":
emulatorTarget.id = "emulator-5551";
break;
case "emulatorName2":
emulatorTarget.id = "emulator-5552";
break;
case "emulatorName3":
emulatorTarget.id = "emulator-5553";
break;
case "emulatorName4":
emulatorTarget.id = "emulator-5554";
break;
}
return AndroidTarget.fromInterface(<IDebuggableMobileTarget>emulatorTarget);
},
);
showQuickPickStub = Sinon.stub(window, "showQuickPick").callsFake(async (items: string[] | Thenable<string[]> | QuickPickItem[] | Thenable<QuickPickItem[]>) => {
targetsForSelection = <string[]> await items;
return items[0];
});
showQuickPickStub = Sinon.stub(window, "showQuickPick").callsFake(
async (
items: string[] | Thenable<string[]> | QuickPickItem[] | Thenable<QuickPickItem[]>,
) => {
targetsForSelection = <string[]>await items;
return items[0];
},
);
targetsForSelection = [];
});
@ -78,7 +126,10 @@ suite("AndroidTargetManager", function () {
});
suite("Target identification", function () {
async function checkTargetTargetTypeCheck(assertFun: () => Promise<void>, catchFun?: () => void): Promise<void> {
async function checkTargetTargetTypeCheck(
assertFun: () => Promise<void>,
catchFun?: () => void,
): Promise<void> {
try {
await assertFun();
} catch {
@ -90,42 +141,87 @@ suite("AndroidTargetManager", function () {
test("Should properly recognize virtual target type", async function () {
await checkTargetTargetTypeCheck(
async () => assert.strictEqual(await androidTargetManager.isVirtualTarget("emulator-1234"), true, "Could not recognize emulator id: emulator-1234"),
() => assert.fail("Could not recognize emulator id: (emulator-1234)")
async () =>
assert.strictEqual(
await androidTargetManager.isVirtualTarget("emulator-1234"),
true,
"Could not recognize emulator id: emulator-1234",
),
() => assert.fail("Could not recognize emulator id: (emulator-1234)"),
);
await checkTargetTargetTypeCheck(
async () => assert.strictEqual(await androidTargetManager.isVirtualTarget("emulator"), true, "Could not recognize any emulator"),
() => assert.fail("Could not recognize any emulator")
async () =>
assert.strictEqual(
await androidTargetManager.isVirtualTarget("emulator"),
true,
"Could not recognize any emulator",
),
() => assert.fail("Could not recognize any emulator"),
);
await checkTargetTargetTypeCheck(
async () => assert.strictEqual(await androidTargetManager.isVirtualTarget("emulatorName2"), true, "Could not recognize emulator AVD name"),
() => assert.fail("Could not recognize emulator AVD name")
async () =>
assert.strictEqual(
await androidTargetManager.isVirtualTarget("emulatorName2"),
true,
"Could not recognize emulator AVD name",
),
() => assert.fail("Could not recognize emulator AVD name"),
);
await checkTargetTargetTypeCheck(
async () => assert.strictEqual(await androidTargetManager.isVirtualTarget("emulaor-1234"), false, "Misrecognized emulator id: emulaor-1234")
await checkTargetTargetTypeCheck(async () =>
assert.strictEqual(
await androidTargetManager.isVirtualTarget("emulaor-1234"),
false,
"Misrecognized emulator id: emulaor-1234",
),
);
await checkTargetTargetTypeCheck(
async () => assert.strictEqual(await androidTargetManager.isVirtualTarget("emulator--1234"), false, "Misrecognized emulator id: emulator--1234")
await checkTargetTargetTypeCheck(async () =>
assert.strictEqual(
await androidTargetManager.isVirtualTarget("emulator--1234"),
false,
"Misrecognized emulator id: emulator--1234",
),
);
await checkTargetTargetTypeCheck(
async () => assert.strictEqual(await androidTargetManager.isVirtualTarget("emulaor1234"), false, "Misrecognized emulator id: emulator1234")
await checkTargetTargetTypeCheck(async () =>
assert.strictEqual(
await androidTargetManager.isVirtualTarget("emulaor1234"),
false,
"Misrecognized emulator id: emulator1234",
),
);
await checkTargetTargetTypeCheck(
async () => assert.strictEqual(await androidTargetManager.isVirtualTarget("1232emulator1234"), false, "Misrecognized emulator id: 1232emulator1234")
await checkTargetTargetTypeCheck(async () =>
assert.strictEqual(
await androidTargetManager.isVirtualTarget("1232emulator1234"),
false,
"Misrecognized emulator id: 1232emulator1234",
),
);
});
test("Should properly recognize device target", async function () {
await checkTargetTargetTypeCheck(
async () => assert.strictEqual(await androidTargetManager.isVirtualTarget("device"), false, "Could not recognize any device"),
() => assert.fail("Could not recognize any device")
async () =>
assert.strictEqual(
await androidTargetManager.isVirtualTarget("device"),
false,
"Could not recognize any device",
),
() => assert.fail("Could not recognize any device"),
);
await checkTargetTargetTypeCheck(
async () => assert.strictEqual(await androidTargetManager.isVirtualTarget("deviceid1"), false, "Could not recognize device id"),
() => assert.fail("Could not recognize device id")
async () =>
assert.strictEqual(
await androidTargetManager.isVirtualTarget("deviceid1"),
false,
"Could not recognize device id",
),
() => assert.fail("Could not recognize device id"),
);
await checkTargetTargetTypeCheck(
async () => assert.strictEqual(await androidTargetManager.isVirtualTarget("deviceid111"), false, "Misrecognized device id: deviceid111")
await checkTargetTargetTypeCheck(async () =>
assert.strictEqual(
await androidTargetManager.isVirtualTarget("deviceid111"),
false,
"Misrecognized device id: deviceid111",
),
);
});
});
@ -138,7 +234,10 @@ suite("AndroidTargetManager", function () {
): Promise<void> {
const target = await androidTargetManager.selectAndPrepareTarget(filter);
if (selectionListCheck) {
assert.ok(selectionListCheck(targetsForSelection), "Did not pass options list check");
assert.ok(
selectionListCheck(targetsForSelection),
"Did not pass options list check",
);
}
if (resultCheck) {
assert.ok(resultCheck(target), "Did not pass result target check");
@ -152,7 +251,7 @@ suite("AndroidTargetManager", function () {
});
test("Should show all targets in case filter has not been defined", async function () {
await checkTargetSeletionResult(undefined, options=> options.length === 6);
await checkTargetSeletionResult(undefined, options => options.length === 6);
});
test("Should show targets by filter", async function () {
@ -160,23 +259,42 @@ suite("AndroidTargetManager", function () {
await checkTargetSeletionResult(
onlineTargetsFilter,
options =>
options.length === options.filter(
(option) => option === onlineEmulator1.name || option === onlineEmulator2.name || option === device1.id || option === device2.id
).length
options.length ===
options.filter(
option =>
option === onlineEmulator1.name ||
option === onlineEmulator2.name ||
option === device1.id ||
option === device2.id,
).length,
);
});
test("Should auto select option in case there is only one target", async function () {
const showQuickPickCallCount = showQuickPickStub.callCount;
const specificNameTargetFilter = (target: IMobileTarget) => target.name === onlineEmulator1.name;
const specificNameTargetFilter = (target: IMobileTarget) =>
target.name === onlineEmulator1.name;
await checkTargetSeletionResult(specificNameTargetFilter, undefined, target => target.id === onlineEmulator1.id);
assert.strictEqual(showQuickPickStub.callCount - showQuickPickCallCount, 0, "There is only one target, but quick pick was shown");
await checkTargetSeletionResult(
specificNameTargetFilter,
undefined,
target => target.id === onlineEmulator1.id,
);
assert.strictEqual(
showQuickPickStub.callCount - showQuickPickCallCount,
0,
"There is only one target, but quick pick was shown",
);
});
test("Should launch the selected emulator in case it's offline", async function () {
const specificNameTargetFilter = (target: IMobileTarget) => target.name === offlineEmulator1.name;
await checkTargetSeletionResult(specificNameTargetFilter, undefined, target => target.isOnline && !!target.id);
const specificNameTargetFilter = (target: IMobileTarget) =>
target.name === offlineEmulator1.name;
await checkTargetSeletionResult(
specificNameTargetFilter,
undefined,
target => target.isOnline && !!target.id,
);
});
});
});

Просмотреть файл

@ -14,11 +14,20 @@ suite("extensionHelper", function () {
const indexJsFilePath = path.join(testingDirectory, "index.js");
const configXmlFilePath = path.join(testProjectPath, "config.xml");
assert.strictEqual(findFileInFolderHierarchy(testingDirectory, "index.js"), indexJsFilePath);
assert.strictEqual(findFileInFolderHierarchy(testingDirectory, "config.xml"), configXmlFilePath);
assert.strictEqual(
findFileInFolderHierarchy(testingDirectory, "index.js"),
indexJsFilePath,
);
assert.strictEqual(
findFileInFolderHierarchy(testingDirectory, "config.xml"),
configXmlFilePath,
);
});
test("should not find a nonexistent required folder and should return 'null'", () => {
assert.strictEqual(findFileInFolderHierarchy(testProjectPath, "testFileHierarchy.js"), null);
assert.strictEqual(
findFileInFolderHierarchy(testProjectPath, "testFileHierarchy.js"),
null,
);
});
});
});

Просмотреть файл

@ -15,16 +15,22 @@ suite("telemetryHelper", function () {
test("should detect Ionic project", () => {
const ionicProjectPath = path.join(__dirname, "..", "resources", "testIonicProject");
sinon.stub(fs, "existsSync").callsFake(p => p === path.join(ionicProjectPath, "package.json"));
sinon.stub(fs, "readFileSync").callsFake((path: string, options?: string | { encoding?: string, flag?: string }) => {
return JSON.stringify({
dependencies: { "@ionic/angular": "5.0.0" },
devDependencies: { "@ionic-native/core": "5.0.0" },
});
});
sinon
.stub(fs, "existsSync")
.callsFake(p => p === path.join(ionicProjectPath, "package.json"));
sinon
.stub(fs, "readFileSync")
.callsFake(
(path: string, options?: string | { encoding?: string; flag?: string }) => {
return JSON.stringify({
dependencies: { "@ionic/angular": "5.0.0" },
devDependencies: { "@ionic-native/core": "5.0.0" },
});
},
);
return TelemetryHelper.determineProjectTypes(ionicProjectPath)
.then((projectType) => {
.then(projectType => {
assert.strictEqual(projectType.ionicMajorVersion, 5);
})
.finally(() => {
@ -39,14 +45,18 @@ suite("telemetryHelper", function () {
});
test("should detect Cordova and Meteor project", () => {
sinon.stub(CordovaProjectHelper, "exists").callsFake((filename: string): Promise<boolean> => {
return Promise.resolve(filename.toString().includes(".meteor") || filename.toString().includes("config.xml"));
});
return TelemetryHelper.determineProjectTypes(testProjectPath)
.then((projectType) => {
assert.ok(projectType.isCordova && projectType.isMeteor);
sinon
.stub(CordovaProjectHelper, "exists")
.callsFake((filename: string): Promise<boolean> => {
return Promise.resolve(
filename.toString().includes(".meteor") ||
filename.toString().includes("config.xml"),
);
});
return TelemetryHelper.determineProjectTypes(testProjectPath).then(projectType => {
assert.ok(projectType.isCordova && projectType.isMeteor);
});
});
});
});

Просмотреть файл

@ -16,7 +16,9 @@ suite("сordovaProjectHelper", function () {
assert.strictEqual(isCordovaProject, true);
});
test("should return 'false' in case there is no the 'config.xml' file in folder hierarchy", () => {
const isCordovaProject = CordovaProjectHelper.isCordovaProject(path.join(testProjectPath, ".."));
const isCordovaProject = CordovaProjectHelper.isCordovaProject(
path.join(testProjectPath, ".."),
);
assert.strictEqual(isCordovaProject, false);
});
});
@ -26,7 +28,7 @@ suite("сordovaProjectHelper", function () {
const ionicPackageJsonFileLocation = path.join(ionicProjectPath, "package.json");
teardown(() => {
// restore individual methods
// restore individual methods
(fs.existsSync as any).restore();
(fs.readFileSync as any).restore();
});
@ -35,34 +37,34 @@ suite("сordovaProjectHelper", function () {
ionicMajorVersionRef: number | undefined,
dep: Record<string, string>,
devDep: Record<string, string>,
existsSyncFake: (path: fs.PathLike) => boolean
existsSyncFake: (path: fs.PathLike) => boolean,
) {
sinon.stub(fs, "existsSync").callsFake(existsSyncFake);
sinon.stub(fs, "readFileSync").callsFake((path: string, options?: string | { encoding?: string, flag?: string }) => {
return JSON.stringify({
dependencies: dep,
devDependencies: devDep,
});
});
sinon
.stub(fs, "readFileSync")
.callsFake(
(path: string, options?: string | { encoding?: string; flag?: string }) => {
return JSON.stringify({
dependencies: dep,
devDependencies: devDep,
});
},
);
const processedMajorVersion = CordovaProjectHelper.determineIonicMajorVersion(ionicProjectPath);
const processedMajorVersion =
CordovaProjectHelper.determineIonicMajorVersion(ionicProjectPath);
assert.deepStrictEqual(processedMajorVersion, ionicMajorVersionRef);
}
test("should detect Ionic 1 version", () => {
determineIonicMajorVersion(
1,
{},
{},
(path) => true
);
determineIonicMajorVersion(1, {}, {}, path => true);
});
test("should detect Ionic 2 version", () => {
determineIonicMajorVersion(
2,
{ "ionic-angular": "2.6.5" },
{ "@ionic/app-scripts": "2.3.4" },
(path) => path === ionicPackageJsonFileLocation
path => path === ionicPackageJsonFileLocation,
);
});
test("should detect Ionic 3 version", () => {
@ -70,7 +72,7 @@ suite("сordovaProjectHelper", function () {
3,
{ "ionic-angular": "3.9.9" },
{ "@ionic/app-scripts": "3.3.4" },
(path) => path === ionicPackageJsonFileLocation
path => path === ionicPackageJsonFileLocation,
);
});
test("should detect Ionic 4 version", () => {
@ -78,7 +80,7 @@ suite("сordovaProjectHelper", function () {
4,
{ "@ionic/angular": "4.0.4" },
{ "@ionic-native/core": "4.0.4" },
(path) => path === ionicPackageJsonFileLocation
path => path === ionicPackageJsonFileLocation,
);
});
test("should detect Ionic 5 version", () => {
@ -86,7 +88,7 @@ suite("сordovaProjectHelper", function () {
5,
{ "@ionic/angular": "5.0.0" },
{ "@ionic-native/core": "5.0.0" },
(path) => path === ionicPackageJsonFileLocation
path => path === ionicPackageJsonFileLocation,
);
});
test("should detect Ionic 6 version", () => {
@ -94,28 +96,23 @@ suite("сordovaProjectHelper", function () {
6,
{ "@ionic/angular": "6.0.0" },
{},
(path) => path === ionicPackageJsonFileLocation
path => path === ionicPackageJsonFileLocation,
);
});
test("shouldn't detect any Ionic version", () => {
determineIonicMajorVersion(
undefined,
{},
{},
(path) => false
);
determineIonicMajorVersion(undefined, {}, {}, path => false);
});
});
suite("getEnvArgument", function () {
function checkEnvData(envData: any, envFileData: any = {}) {
let launchArgs: any = {
cwd: testProjectPath,
platform: "android",
ionicLiveReload: false,
request: "attach",
port: 9222,
env: envData,
cwd: testProjectPath,
platform: "android",
ionicLiveReload: false,
request: "attach",
port: 9222,
env: envData,
};
let envRef = Object.assign({}, process.env);
@ -126,7 +123,10 @@ suite("сordovaProjectHelper", function () {
Object.assign(envRef, envData);
try {
const envProcessed = Object.assign({}, CordovaProjectHelper.getEnvArgument(launchArgs));
const envProcessed = Object.assign(
{},
CordovaProjectHelper.getEnvArgument(launchArgs),
);
assert.deepStrictEqual(envProcessed, envRef);
} finally {
Object.keys(envData).forEach(key => {
@ -140,9 +140,13 @@ suite("сordovaProjectHelper", function () {
}
function checkEnvDataFromFile(env: any, envFile: any, envStrRepres: string) {
sinon.stub(fs, "readFileSync").callsFake((path: string, options?: string | { encoding?: string, flag?: string }) => {
return envStrRepres;
});
sinon
.stub(fs, "readFileSync")
.callsFake(
(path: string, options?: string | { encoding?: string; flag?: string }) => {
return envStrRepres;
},
);
checkEnvData(env, envFile);
@ -154,42 +158,42 @@ suite("сordovaProjectHelper", function () {
});
test("should return env data from launchArgs.env parameter", () => {
checkEnvData({
"TEST1": "test1",
"TEST2": "123",
TEST1: "test1",
TEST2: "123",
});
});
test("should return env data from a .env file", () => {
checkEnvDataFromFile(
{},
{
"TEST1": "test1",
"TEST2": "123",
TEST1: "test1",
TEST2: "123",
},
"TEST1=test1\nTEST2=123"
"TEST1=test1\nTEST2=123",
);
});
test("should return env data: env variables from a .env file are overwritten with ones from launchArgs.env parameter", () => {
checkEnvDataFromFile(
{
"TEST1": "test_test",
"TEST2": "1234",
TEST1: "test_test",
TEST2: "1234",
},
{
"TEST1": "test1",
"TEST2": "123",
"TEST3": "test3",
TEST1: "test1",
TEST2: "123",
TEST3: "test3",
},
"TEST1=test1\nTEST2=123\nTEST3=test3"
"TEST1=test1\nTEST2=123\nTEST3=test3",
);
});
test("should skip incorrectly formatted env data", () => {
checkEnvDataFromFile(
{},
{
"TEST1": "test1",
"TEST2": "123",
TEST1: "test1",
TEST2: "123",
},
"TEST1=test1\nTEST2=123\nTEST3test3"
"TEST1=test1\nTEST2=123\nTEST3test3",
);
});
});

Просмотреть файл

@ -6,7 +6,7 @@ import * as Mocha from "mocha";
import * as glob from "glob";
export function run(): Promise<void> {
const mocha = new Mocha ({
const mocha = new Mocha({
ui: "tdd",
grep: "smokeTestsContext",
color: true,

Просмотреть файл

@ -1,42 +1,42 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
var app = {
// Application Constructor
initialize: function() {
initialize: function () {
this.bindEvents();
},
// Bind Event Listeners
//
// Bind any events that are required on startup. Common events are:
// 'load', 'deviceready', 'offline', and 'online'.
bindEvents: function() {
bindEvents: function () {
document.addEventListener('deviceready', this.onDeviceReady, false);
},
// deviceready Event Handler
//
// The scope of 'this' is the event. In order to call the 'receivedEvent'
// function, we must explicitly call 'app.receivedEvent(...);'
onDeviceReady: function() {
onDeviceReady: function () {
app.receivedEvent('deviceready');
},
// Update DOM on a Received Event
receivedEvent: function(id) {
receivedEvent: function (id) {
console.log('Received Event: ' + id);
},

Просмотреть файл

@ -1 +1,15 @@
{"version":3,"file":"typescript.js","sourceRoot":"","sources":["typescript.ts"],"names":["testClass","testClass.constructor","testClass.printValue","typeScript"],"mappings":"AAAA;IAEIA,mBAAYA,GAAWA;QACnBC,IAAIA,CAACA,KAAKA,GAAGA,GAAGA,CAACA;IACrBA,CAACA;IAEMD,8BAAUA,GAAjBA;QACIE,OAAOA,CAACA,IAAIA,CAACA,wBAAwBA,GAAGA,IAAIA,CAACA,KAAKA,CAACA,CAACA;IACxDA,CAACA;IACLF,gBAACA;AAADA,CAACA,AATD,IASC;AAED;IACIG,IAAIA,GAAGA,GAAGA,IAAIA,SAASA,CAACA,EAAEA,CAACA,CAACA;IAC5BA,GAAGA,CAACA,UAAUA,EAAEA,CAACA;AACrBA,CAACA"}
{
"version": 3,
"file": "typescript.js",
"sourceRoot": "",
"sources": [
"typescript.ts"
],
"names": [
"testClass",
"testClass.constructor",
"testClass.printValue",
"typeScript"
],
"mappings": "AAAA;IAEIA,mBAAYA,GAAWA;QACnBC,IAAIA,CAACA,KAAKA,GAAGA,GAAGA,CAACA;IACrBA,CAACA;IAEMD,8BAAUA,GAAjBA;QACIE,OAAOA,CAACA,IAAIA,CAACA,wBAAwBA,GAAGA,IAAIA,CAACA,KAAKA,CAACA,CAACA;IACxDA,CAACA;IACLF,gBAACA;AAADA,CAACA,AATD,IASC;AAED;IACIG,IAAIA,GAAGA,GAAGA,IAAIA,SAASA,CAACA,EAAEA,CAACA,CAACA;IAC5BA,GAAGA,CAACA,UAAUA,EAAEA,CAACA;AACrBA,CAACA"
}

Просмотреть файл

@ -3,7 +3,7 @@ class testClass {
constructor(val: number) {
this.value = val;
}
public printValue(): void {
console.info("Debugging typescript: " + this.value);
}
@ -12,4 +12,4 @@ class testClass {
function typeScript(): void {
let obj = new testClass(42);
obj.printValue();
}
}

Просмотреть файл

@ -1,3 +1,3 @@
function testPrintIonic() {
console.log("This is a test file for Ionic CDP proxy tests.");
}
}

Просмотреть файл

@ -6,24 +6,29 @@ import * as path from "path";
import { runTests } from "vscode-test";
async function launchTests() {
try {
// The folder containing the Extension Manifest package.json
// Passed to `--extensionDevelopmentPath`
const extensionDevelopmentPath = path.resolve(__dirname, "..");
try {
// The folder containing the Extension Manifest package.json
// Passed to `--extensionDevelopmentPath`
const extensionDevelopmentPath = path.resolve(__dirname, "..");
// The path to the extension test runner script
// Passed to --extensionTestsPath
const extensionTestsPath = path.resolve(__dirname, "index");
// The path to the extension test runner script
// Passed to --extensionTestsPath
const extensionTestsPath = path.resolve(__dirname, "index");
const testWorkspace = path.resolve(__dirname, "resources", "testCordovaProject");
const testWorkspace = path.resolve(__dirname, "resources", "testCordovaProject");
// Download VS Code, unzip it and run the integration test
await runTests({ extensionDevelopmentPath, extensionTestsPath , launchArgs: [testWorkspace], version: "1.53.2" });
} catch (err) {
console.error(err);
console.error("Failed to run tests");
process.exit(1);
}
// Download VS Code, unzip it and run the integration test
await runTests({
extensionDevelopmentPath,
extensionTestsPath,
launchArgs: [testWorkspace],
version: "1.53.2",
});
} catch (err) {
console.error(err);
console.error("Failed to run tests");
process.exit(1);
}
}
launchTests();

Просмотреть файл

@ -6,7 +6,7 @@ import * as fs from "fs";
import * as http from "http";
import * as os from "os";
import {CordovaProjectHelper} from "../src/utils/cordovaProjectHelper";
import { CordovaProjectHelper } from "../src/utils/cordovaProjectHelper";
export function executeCordovaCommand(cwd: string, command: string): Promise<any> {
return new Promise((resolve, reject) => {
@ -27,9 +27,11 @@ export function executeCordovaCommand(cwd: string, command: string): Promise<any
reject(err);
});
process.on("close" , exitCode => {
process.on("close", exitCode => {
if (exitCode !== 0) {
return reject(`Cordova command failed with exit code ${exitCode}. Error: ${stderr}`);
return reject(
`Cordova command failed with exit code ${exitCode}. Error: ${stderr}`,
);
}
resolve({});
});
@ -41,12 +43,20 @@ export function createCordovaProject(cwd: string, projectName: string): Promise<
return executeCordovaCommand(cwd, cordovaCommandToRun);
}
export function addCordovaComponents(componentName: string, projectRoot: string, componentsToAdd: string[]): Promise<any> {
export function addCordovaComponents(
componentName: string,
projectRoot: string,
componentsToAdd: string[],
): Promise<any> {
let cordovaCommandToRun = componentName + " add " + componentsToAdd.join(" ");
return executeCordovaCommand(projectRoot, cordovaCommandToRun);
}
export function removeCordovaComponents(componentName: string, projectRoot: string, componentsToRemove: string[]): Promise<any> {
export function removeCordovaComponents(
componentName: string,
projectRoot: string,
componentsToRemove: string[],
): Promise<any> {
let cordovaCommandToRun = componentName + " remove " + componentsToRemove.join(" ");
return executeCordovaCommand(projectRoot, cordovaCommandToRun);
}
@ -64,7 +74,7 @@ export function enumerateListOfTypeDefinitions(projectRoot: string): string[] {
export function isUrlReachable(url: string): Promise<boolean> {
return new Promise((resolve, reject) => {
http.get(url, (res) => {
http.get(url, res => {
resolve(true);
res.resume();
}).on("error", (_err: Error) => {

101
tools/gulp-extras.js Normal file
Просмотреть файл

@ -0,0 +1,101 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
"use strict";
const child_process = require("child_process");
const fs = require("fs");
const log = require('fancy-log');
const colors = require('ansi-colors');
const path = require("path");
const PluginError = require('plugin-error');
const through = require("through2");
/**
* Pretty logger using 'log'
* @param {string} pluginName Name of the pluginName
* @param {Object} file A gulp file to report on
* @param {string} message The error message to display
*/
function logError(pluginName, file, message) {
const sourcePath = path.relative(__dirname, file.path).replace("../", "");
log(`[${colors.cyan(pluginName)}] ${colors.red("error")} ${sourcePath}: ${message}`);
}
/**
* Plugin to verify the Microsoft copyright notice is present
*/
function checkCopyright() {
const pluginName = "check-copyright";
let hadErrors = false;
const copyrightNotice = "// Copyright (c) Microsoft Corporation. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for details.";
return through.obj(function (file, encoding, callback) {
if (file.isBuffer()) {
let fileContents = file.contents.toString(encoding);
fileContents = fileContents.replace("\r\n", "\n");
fileContents = fileContents.replace("\"use strict\";\n", "");
fileContents = fileContents.replace("Object.defineProperty(exports, \"__esModule\", { value: true });\n", "");
if (fileContents.indexOf(copyrightNotice) !== 0) {
logError(pluginName, file, "missing copyright notice");
hadErrors = true;
}
}
callback(null, file);
},
function (callback) {
if (hadErrors) {
return this.emit("error", new PluginError(pluginName, "Failed copyright check"));
}
callback();
});
}
/**
* Helper function to check if a file exists case sensitive
* @param {string} filePath The path to check
* @returns {boolean} If the path exists case sensitive
*/
function existsCaseSensitive(filePath) {
if (fs.existsSync(filePath)) {
const fileName = path.basename(filePath);
return fs.readdirSync(path.dirname(filePath)).indexOf(fileName) !== -1;
}
return false;
}
function executeCommand(command, args, callback, opts) {
const proc = child_process.spawn(command + (process.platform === "win32" ? ".cmd" : ""), args, opts);
let errorSignaled = false;
proc.stdout.on("data", (data) => {
log(`${data}`);
});
proc.stderr.on("data", (data) => {
log.error(`${data}`);
});
proc.on("error", (error) => {
if (!errorSignaled) {
callback(`An error occurred. ${error}`);
errorSignaled = true;
}
});
proc.on("exit", (code) => {
if (code === 0) {
callback();
} else if (!errorSignaled) {
callback(`Error code: ${code}`);
errorSignaled = true;
}
});
}
module.exports = {
checkCopyright,
executeCommand
}