Added new tslint rules (#410)
This commit is contained in:
Родитель
e5db4fa56e
Коммит
68c8bfd5da
|
@ -98,7 +98,7 @@ gulp.task('tslint-test', function () {
|
|||
.pipe(tslint.report());
|
||||
});
|
||||
|
||||
gulp.task('build-src', ['compile-src'/*, 'tslint-src'*/]);
|
||||
gulp.task('build-src', ['compile-src', 'tslint-src']);
|
||||
gulp.task('build-test', ['compile-test'/*, 'tslint-test'*/]);
|
||||
gulp.task('build', ['build-src'/*, 'build-test'*/]);
|
||||
gulp.task('tslint', ['tslint-src', 'tslint-test']);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for details.
|
||||
|
||||
import {Hash} from "../utils/hash"
|
||||
import {Hash} from "../utils/hash";
|
||||
import * as Q from "q";
|
||||
import * as net from "net";
|
||||
|
||||
|
@ -18,7 +18,7 @@ export enum ExtensionMessage {
|
|||
SIMULATE,
|
||||
START_SIMULATE_SERVER,
|
||||
GET_RUN_ARGUMENTS,
|
||||
GET_SIMULATOR_IN_EXTERNAL_BROWSER_SETTING
|
||||
GET_SIMULATOR_IN_EXTERNAL_BROWSER_SETTING,
|
||||
}
|
||||
|
||||
export interface MessageWithArguments {
|
||||
|
|
|
@ -4,5 +4,5 @@
|
|||
export interface SimulationInfo {
|
||||
appHostUrl: string;
|
||||
simHostUrl: string;
|
||||
urlRoot: string
|
||||
urlRoot: string;
|
||||
}
|
134
src/cordova.ts
134
src/cordova.ts
|
@ -1,29 +1,28 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for details.
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import {SimulateOptions} from 'cordova-simulate';
|
||||
import * as vscode from 'vscode';
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import {SimulateOptions} from "cordova-simulate";
|
||||
import * as vscode from "vscode";
|
||||
|
||||
import {CordovaProjectHelper} from './utils/cordovaProjectHelper';
|
||||
import {CordovaCommandHelper} from './utils/cordovaCommandHelper';
|
||||
import {ExtensionServer} from './extension/extensionServer';
|
||||
import * as Q from 'q';
|
||||
import * as semver from 'semver';
|
||||
import {PluginSimulator} from './extension/simulate';
|
||||
import {Telemetry} from './utils/telemetry';
|
||||
import {TelemetryHelper} from './utils/telemetryHelper';
|
||||
import {IProjectType} from './utils/cordovaProjectHelper';
|
||||
import { TsdHelper } from './utils/tsdHelper';
|
||||
import {CordovaProjectHelper} from "./utils/cordovaProjectHelper";
|
||||
import {CordovaCommandHelper} from "./utils/cordovaCommandHelper";
|
||||
import {ExtensionServer} from "./extension/extensionServer";
|
||||
import * as Q from "q";
|
||||
import * as semver from "semver";
|
||||
import {PluginSimulator} from "./extension/simulate";
|
||||
import {Telemetry} from "./utils/telemetry";
|
||||
import {TelemetryHelper} from "./utils/telemetryHelper";
|
||||
import {TsdHelper} from "./utils/tsdHelper";
|
||||
|
||||
import { IonicCompletionProvider } from './extension/completionProviders';
|
||||
import {IonicCompletionProvider} from "./extension/completionProviders";
|
||||
|
||||
let PLUGIN_TYPE_DEFS_FILENAME = 'pluginTypings.json';
|
||||
let PLUGIN_TYPE_DEFS_PATH = path.resolve(__dirname, '..', '..', PLUGIN_TYPE_DEFS_FILENAME);
|
||||
let CORDOVA_TYPINGS_QUERYSTRING = 'cordova';
|
||||
let JSCONFIG_FILENAME = 'jsconfig.json';
|
||||
let TSCONFIG_FILENAME = 'tsconfig.json';
|
||||
let PLUGIN_TYPE_DEFS_FILENAME = "pluginTypings.json";
|
||||
let PLUGIN_TYPE_DEFS_PATH = path.resolve(__dirname, "..", "..", PLUGIN_TYPE_DEFS_FILENAME);
|
||||
let CORDOVA_TYPINGS_QUERYSTRING = "cordova";
|
||||
let JSCONFIG_FILENAME = "jsconfig.json";
|
||||
let TSCONFIG_FILENAME = "tsconfig.json";
|
||||
|
||||
let projectsCache: {[key: string]: any} = {};
|
||||
|
||||
|
@ -40,14 +39,14 @@ export function activate(context: vscode.ExtensionContext): void {
|
|||
}
|
||||
}
|
||||
|
||||
export function deactivate(context: vscode.ExtensionContext): void {
|
||||
console.log('Extension has been deactivated');
|
||||
export function deactivate(): void {
|
||||
console.log("Extension has been deactivated");
|
||||
}
|
||||
|
||||
function onChangeWorkspaceFolders(context: vscode.ExtensionContext, event: vscode.WorkspaceFoldersChangeEvent) {
|
||||
if (event.removed.length) {
|
||||
event.removed.forEach((folder) => {
|
||||
onFolderRemoved(context, folder);
|
||||
onFolderRemoved(folder);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -63,23 +62,23 @@ function onFolderAdded(context: vscode.ExtensionContext, folder: vscode.Workspac
|
|||
let cordovaProjectRoot = CordovaProjectHelper.getCordovaProjectRoot(workspaceRoot);
|
||||
|
||||
// Asynchronously enable telemetry
|
||||
Telemetry.init('cordova-tools', require('./../../package.json').version, { isExtensionProcess: true, projectRoot: workspaceRoot });
|
||||
Telemetry.init("cordova-tools", require("./../../package.json").version, { isExtensionProcess: true, projectRoot: workspaceRoot });
|
||||
|
||||
if (!cordovaProjectRoot) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (path.resolve(cordovaProjectRoot) !== path.resolve(workspaceRoot)) {
|
||||
vscode.window.showWarningMessage('VSCode Cordova extension requires the workspace root to be your Cordova project\'s root. The extension hasn\'t been activated.');
|
||||
vscode.window.showWarningMessage("VSCode Cordova extension requires the workspace root to be your Cordova project's root. The extension hasn't been activated.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let activateExtensionEvent = TelemetryHelper.createTelemetryEvent('activate');
|
||||
let activateExtensionEvent = TelemetryHelper.createTelemetryEvent("activate");
|
||||
|
||||
TelemetryHelper.determineProjectTypes(cordovaProjectRoot)
|
||||
.then((projType) => {
|
||||
activateExtensionEvent.properties['projectType'] = projType;
|
||||
activateExtensionEvent.properties["projectType"] = projType;
|
||||
})
|
||||
.finally(() => {
|
||||
Telemetry.send(activateExtensionEvent);
|
||||
|
@ -90,10 +89,10 @@ function onFolderAdded(context: vscode.ExtensionContext, folder: vscode.Workspac
|
|||
// 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*/);
|
||||
watcher.onDidChange((e: vscode.Uri) => updatePluginTypeDefinitions(cordovaProjectRoot));
|
||||
watcher.onDidDelete((e: vscode.Uri) => updatePluginTypeDefinitions(cordovaProjectRoot));
|
||||
watcher.onDidCreate((e: vscode.Uri) => updatePluginTypeDefinitions(cordovaProjectRoot));
|
||||
let watcher = vscode.workspace.createFileSystemWatcher("**/plugins/fetch.json", false /*ignoreCreateEvents*/, false /*ignoreChangeEvents*/, false /*ignoreDeleteEvents*/);
|
||||
watcher.onDidChange(() => updatePluginTypeDefinitions(cordovaProjectRoot));
|
||||
watcher.onDidDelete(() => updatePluginTypeDefinitions(cordovaProjectRoot));
|
||||
watcher.onDidCreate(() => updatePluginTypeDefinitions(cordovaProjectRoot));
|
||||
context.subscriptions.push(watcher);
|
||||
|
||||
let simulator: PluginSimulator = new PluginSimulator();
|
||||
|
@ -103,7 +102,7 @@ function onFolderAdded(context: vscode.ExtensionContext, folder: vscode.Workspac
|
|||
projectsCache[workspaceRoot] = {
|
||||
extensionServer,
|
||||
cordovaProjectRoot,
|
||||
folder
|
||||
folder,
|
||||
};
|
||||
|
||||
// extensionServer takes care of disposing the simulator instance
|
||||
|
@ -114,24 +113,24 @@ function onFolderAdded(context: vscode.ExtensionContext, folder: vscode.Workspac
|
|||
context.subscriptions.push(
|
||||
vscode.languages.registerCompletionItemProvider(
|
||||
IonicCompletionProvider.JS_DOCUMENT_SELECTOR,
|
||||
new IonicCompletionProvider(path.resolve(__dirname, '../../snippets/ionicJs.json'))));
|
||||
new IonicCompletionProvider(path.resolve(__dirname, "../../snippets/ionicJs.json"))));
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.languages.registerCompletionItemProvider(
|
||||
IonicCompletionProvider.HTML_DOCUMENT_SELECTOR,
|
||||
new IonicCompletionProvider(path.resolve(__dirname, '../../snippets/ionicHtml.json'))));
|
||||
new IonicCompletionProvider(path.resolve(__dirname, "../../snippets/ionicHtml.json"))));
|
||||
}
|
||||
|
||||
// Install Ionic type definitions if necessary
|
||||
if (CordovaProjectHelper.isIonicProject(cordovaProjectRoot)) {
|
||||
let ionicTypings: string[] = [
|
||||
path.join('jquery', 'jquery.d.ts'),
|
||||
path.join('cordova-ionic', 'plugins', 'keyboard.d.ts')
|
||||
path.join("jquery", "jquery.d.ts"),
|
||||
path.join("cordova-ionic", "plugins", "keyboard.d.ts"),
|
||||
];
|
||||
if (CordovaProjectHelper.isIonic1Project(cordovaProjectRoot)) {
|
||||
ionicTypings = ionicTypings.concat([
|
||||
path.join('angularjs', 'angular.d.ts'),
|
||||
path.join('ionic', 'ionic.d.ts')
|
||||
path.join("angularjs", "angular.d.ts"),
|
||||
path.join("ionic", "ionic.d.ts"),
|
||||
]);
|
||||
}
|
||||
TsdHelper.installTypings(CordovaProjectHelper.getOrCreateTypingsTargetPath(cordovaProjectRoot), ionicTypings, cordovaProjectRoot);
|
||||
|
@ -155,7 +154,7 @@ function onFolderAdded(context: vscode.ExtensionContext, folder: vscode.Workspac
|
|||
// Install type definition files for the currently installed plugins
|
||||
updatePluginTypeDefinitions(cordovaProjectRoot);
|
||||
|
||||
let pluginFilePath = path.join(cordovaProjectRoot, '.vscode', 'plugins.json');
|
||||
let pluginFilePath = path.join(cordovaProjectRoot, ".vscode", "plugins.json");
|
||||
if (fs.existsSync(pluginFilePath)) {
|
||||
fs.unlinkSync(pluginFilePath);
|
||||
}
|
||||
|
@ -168,15 +167,15 @@ function onFolderAdded(context: vscode.ExtensionContext, folder: vscode.Workspac
|
|||
|
||||
Q.all([Q.nfcall(fs.exists, jsconfigPath), Q.nfcall(fs.exists, tsconfigPath)]).spread((jsExists: boolean, tsExists: boolean) => {
|
||||
if (!jsExists && !tsExists) {
|
||||
Q.nfcall(fs.writeFile, jsconfigPath, '{}').then(() => {
|
||||
Q.nfcall(fs.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).");
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function onFolderRemoved(context: vscode.ExtensionContext, folder: vscode.WorkspaceFolder): void {
|
||||
function onFolderRemoved(folder: vscode.WorkspaceFolder): void {
|
||||
delete projectsCache[folder.uri.fsPath];
|
||||
}
|
||||
|
||||
|
@ -185,7 +184,7 @@ function getPluginTypingsJson(): any {
|
|||
return require(PLUGIN_TYPE_DEFS_PATH);
|
||||
}
|
||||
|
||||
console.error('Cordova plugin type declaration mapping file \'pluginTypings.json\' is missing from the extension folder.');
|
||||
console.error("Cordova plugin type declaration mapping file 'pluginTypings.json' is missing from the extension folder.");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -211,8 +210,8 @@ function addPluginTypeDefinitions(projectRoot: string, installedPlugins: string[
|
|||
}
|
||||
|
||||
// If we do not know the plugin, collect it anonymously for future prioritisation
|
||||
let unknownPluginEvent = TelemetryHelper.createTelemetryEvent('unknownPlugin');
|
||||
unknownPluginEvent.setPiiProperty('plugin', pluginName);
|
||||
let unknownPluginEvent = TelemetryHelper.createTelemetryEvent("unknownPlugin");
|
||||
unknownPluginEvent.setPiiProperty("plugin", pluginName);
|
||||
Telemetry.send(unknownPluginEvent);
|
||||
return false;
|
||||
}).map((pluginName: string) => {
|
||||
|
@ -226,13 +225,13 @@ function addPluginTypeDefinitions(projectRoot: string, installedPlugins: string[
|
|||
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)
|
||||
.filter((typeDef: string) => newTypeDefs.indexOf(typeDef) < 0);
|
||||
|
||||
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, '\/')
|
||||
return path.relative(CordovaProjectHelper.getOrCreateTypingsTargetPath(projectRoot), path.resolve(parentPath, typeDefinitionFile)).replace(/\\/g, "\/");
|
||||
}
|
||||
|
||||
function updatePluginTypeDefinitions(cordovaProjectRoot: string): void {
|
||||
|
@ -248,8 +247,8 @@ function updatePluginTypeDefinitions(cordovaProjectRoot: string): void {
|
|||
|
||||
let installedPlugins: string[] = CordovaProjectHelper.getInstalledPlugins(cordovaProjectRoot);
|
||||
|
||||
const nodeModulesDir = path.resolve(cordovaProjectRoot, 'node_modules');
|
||||
if (semver.gte(vscode.version, '1.7.2-insider') && fs.existsSync(nodeModulesDir)) {
|
||||
const nodeModulesDir = path.resolve(cordovaProjectRoot, "node_modules");
|
||||
if (semver.gte(vscode.version, "1.7.2-insider") && fs.existsSync(nodeModulesDir)) {
|
||||
// Read installed node modules and filter out plugins that have been already installed in node_modules
|
||||
// This happens if user has used '--fetch' option to install plugin. In this case VSCode will provide
|
||||
// own intellisense for these plugins using ATA (automatic typings acquisition)
|
||||
|
@ -282,12 +281,19 @@ function updatePluginTypeDefinitions(cordovaProjectRoot: string): void {
|
|||
|
||||
// Now read the type definitions of Cordova plugins
|
||||
fs.readdir(cordovaPluginTypesFolder, (err: Error, cordovaTypeDefs: string[]) => {
|
||||
if (err) {
|
||||
// ignore
|
||||
}
|
||||
if (cordovaTypeDefs) {
|
||||
currentTypeDefs = cordovaTypeDefs.map(typeDef => getRelativeTypeDefinitionFilePath(cordovaProjectRoot, cordovaPluginTypesFolder, typeDef));
|
||||
}
|
||||
|
||||
// Now read the type definitions of Ionic plugins
|
||||
fs.readdir(ionicPluginTypesFolder, (err: Error, ionicTypeDefs: string[]) => {
|
||||
if (err) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
if (ionicTypeDefs) {
|
||||
currentTypeDefs.concat(ionicTypeDefs.map(typeDef => getRelativeTypeDefinitionFilePath(cordovaProjectRoot, ionicPluginTypesFolder, typeDef)));
|
||||
}
|
||||
|
@ -300,13 +306,13 @@ function updatePluginTypeDefinitions(cordovaProjectRoot: string): void {
|
|||
|
||||
/* Launches a simulate command and records telemetry for it */
|
||||
function launchSimulateCommand(cordovaProjectRoot: string, options: SimulateOptions): Q.Promise<void> {
|
||||
return TelemetryHelper.generate('simulateCommand', (generator) => {
|
||||
return TelemetryHelper.generate("simulateCommand", (generator) => {
|
||||
return TelemetryHelper.determineProjectTypes(cordovaProjectRoot)
|
||||
.then((projectType) => {
|
||||
generator.add('simulateOptions', options, false);
|
||||
generator.add('projectType', projectType, false);
|
||||
generator.add("simulateOptions", options, false);
|
||||
generator.add("projectType", projectType, false);
|
||||
// visibleTextEditors is null proof (returns empty array if no editors visible)
|
||||
generator.add('visibleTextEditorsCount', vscode.window.visibleTextEditors.length, false);
|
||||
generator.add("visibleTextEditorsCount", vscode.window.visibleTextEditors.length, false);
|
||||
return projectType;
|
||||
});
|
||||
}).then((projectType) => {
|
||||
|
@ -314,25 +320,25 @@ function launchSimulateCommand(cordovaProjectRoot: string, options: SimulateOpti
|
|||
const workspaceFolder = <vscode.WorkspaceFolder>vscode.workspace.getWorkspaceFolder(uri);
|
||||
return projectsCache[workspaceFolder.uri.fsPath].extensionServer.pluginSimulator.simulate(cordovaProjectRoot, options, projectType);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function registerCordovaCommands(context: vscode.ExtensionContext): void {
|
||||
context.subscriptions.push(vscode.commands.registerCommand('cordova.prepare', () => commandWrapper(CordovaCommandHelper.executeCordovaCommand, ['prepare'])));
|
||||
context.subscriptions.push(vscode.commands.registerCommand('cordova.build', () => commandWrapper(CordovaCommandHelper.executeCordovaCommand, ['build'])));
|
||||
context.subscriptions.push(vscode.commands.registerCommand('cordova.run', () => commandWrapper(CordovaCommandHelper.executeCordovaCommand, ['run'])));
|
||||
context.subscriptions.push(vscode.commands.registerCommand('ionic.prepare', () => commandWrapper(CordovaCommandHelper.executeCordovaCommand, ['prepare', true])));
|
||||
context.subscriptions.push(vscode.commands.registerCommand('ionic.build', () => commandWrapper(CordovaCommandHelper.executeCordovaCommand, ['build', true])));
|
||||
context.subscriptions.push(vscode.commands.registerCommand('ionic.run', () => commandWrapper(CordovaCommandHelper.executeCordovaCommand, ['run', true])));
|
||||
context.subscriptions.push(vscode.commands.registerCommand('cordova.simulate.android', () => {
|
||||
context.subscriptions.push(vscode.commands.registerCommand("cordova.prepare", () => commandWrapper(CordovaCommandHelper.executeCordovaCommand, ["prepare"])));
|
||||
context.subscriptions.push(vscode.commands.registerCommand("cordova.build", () => commandWrapper(CordovaCommandHelper.executeCordovaCommand, ["build"])));
|
||||
context.subscriptions.push(vscode.commands.registerCommand("cordova.run", () => commandWrapper(CordovaCommandHelper.executeCordovaCommand, ["run"])));
|
||||
context.subscriptions.push(vscode.commands.registerCommand("ionic.prepare", () => commandWrapper(CordovaCommandHelper.executeCordovaCommand, ["prepare", true])));
|
||||
context.subscriptions.push(vscode.commands.registerCommand("ionic.build", () => commandWrapper(CordovaCommandHelper.executeCordovaCommand, ["build", true])));
|
||||
context.subscriptions.push(vscode.commands.registerCommand("ionic.run", () => commandWrapper(CordovaCommandHelper.executeCordovaCommand, ["run", true])));
|
||||
context.subscriptions.push(vscode.commands.registerCommand("cordova.simulate.android", () => {
|
||||
return selectProject()
|
||||
.then((project) => {
|
||||
return launchSimulateCommand(project.cordovaProjectRoot, { dir: project.folder.uri.fsPath, target: 'chrome', platform: 'android' });
|
||||
return launchSimulateCommand(project.cordovaProjectRoot, { dir: project.folder.uri.fsPath, target: "chrome", platform: "android" });
|
||||
});
|
||||
}));
|
||||
context.subscriptions.push(vscode.commands.registerCommand('cordova.simulate.ios', () => {
|
||||
context.subscriptions.push(vscode.commands.registerCommand("cordova.simulate.ios", () => {
|
||||
return selectProject()
|
||||
.then((project) => {
|
||||
return launchSimulateCommand(project.cordovaProjectRoot, { dir: project.folder.uri.fsPath, target: 'chrome', platform: 'ios' });
|
||||
return launchSimulateCommand(project.cordovaProjectRoot, { dir: project.folder.uri.fsPath, target: "chrome", platform: "ios" });
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,14 +1,14 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for details.
|
||||
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
import * as child_process from 'child_process';
|
||||
import * as fs from 'fs';
|
||||
import * as net from 'net';
|
||||
import * as path from 'path';
|
||||
import * as pl from 'plist';
|
||||
import * as Q from 'q';
|
||||
import * as child_process from "child_process";
|
||||
import * as fs from "fs";
|
||||
import * as net from "net";
|
||||
import * as path from "path";
|
||||
import * as pl from "plist";
|
||||
import * as Q from "q";
|
||||
|
||||
let promiseExec = Q.denodeify(child_process.exec);
|
||||
|
||||
|
@ -18,7 +18,7 @@ export class CordovaIosDeviceLauncher {
|
|||
|
||||
public static cleanup(): void {
|
||||
if (CordovaIosDeviceLauncher.nativeDebuggerProxyInstance) {
|
||||
CordovaIosDeviceLauncher.nativeDebuggerProxyInstance.kill('SIGHUP');
|
||||
CordovaIosDeviceLauncher.nativeDebuggerProxyInstance.kill("SIGHUP");
|
||||
CordovaIosDeviceLauncher.nativeDebuggerProxyInstance = null;
|
||||
}
|
||||
if (CordovaIosDeviceLauncher.webDebuggerProxyInstance) {
|
||||
|
@ -28,29 +28,29 @@ export class CordovaIosDeviceLauncher {
|
|||
}
|
||||
|
||||
public static getBundleIdentifier(projectRoot: string): Q.Promise<string> {
|
||||
return Q.nfcall(fs.readdir, path.join(projectRoot, 'platforms', 'ios')).then((files: string[]) => {
|
||||
return Q.nfcall(fs.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('Unable to find xcodeproj file');
|
||||
throw new Error("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'));
|
||||
let filepath = path.join(projectRoot, "platforms", "ios", projectName, projectName + "-Info.plist");
|
||||
let plist = pl.parse(fs.readFileSync(filepath, "utf8"));
|
||||
return plist.CFBundleIdentifier;
|
||||
});
|
||||
}
|
||||
|
||||
public static startDebugProxy(proxyPort: number): Q.Promise<child_process.ChildProcess> {
|
||||
if (CordovaIosDeviceLauncher.nativeDebuggerProxyInstance) {
|
||||
CordovaIosDeviceLauncher.nativeDebuggerProxyInstance.kill('SIGHUP'); // idevicedebugserver does not exit from SIGTERM
|
||||
CordovaIosDeviceLauncher.nativeDebuggerProxyInstance.kill("SIGHUP"); // idevicedebugserver does not exit from SIGTERM
|
||||
CordovaIosDeviceLauncher.nativeDebuggerProxyInstance = null;
|
||||
}
|
||||
|
||||
return CordovaIosDeviceLauncher.mountDeveloperImage().then(function (): Q.Promise<child_process.ChildProcess> {
|
||||
let deferred = Q.defer<child_process.ChildProcess>();
|
||||
CordovaIosDeviceLauncher.nativeDebuggerProxyInstance = child_process.spawn('idevicedebugserverproxy', [proxyPort.toString()]);
|
||||
CordovaIosDeviceLauncher.nativeDebuggerProxyInstance.on('error', function (err: any): void {
|
||||
CordovaIosDeviceLauncher.nativeDebuggerProxyInstance = child_process.spawn("idevicedebugserverproxy", [proxyPort.toString()]);
|
||||
CordovaIosDeviceLauncher.nativeDebuggerProxyInstance.on("error", function (err: any): void {
|
||||
deferred.reject(err);
|
||||
});
|
||||
// Allow 200ms for the spawn to error out, ~125ms isn't uncommon for some failures
|
||||
|
@ -68,9 +68,9 @@ export class CordovaIosDeviceLauncher {
|
|||
|
||||
let deferred = Q.defer();
|
||||
let portRange = `null:${proxyPort},:${proxyRangeStart}-${proxyRangeEnd}`;
|
||||
CordovaIosDeviceLauncher.webDebuggerProxyInstance = child_process.spawn('ios_webkit_debug_proxy', ['-c', portRange]);
|
||||
CordovaIosDeviceLauncher.webDebuggerProxyInstance.on('error', function (err: Error) {
|
||||
deferred.reject(new Error('Unable to start ios_webkit_debug_proxy.'));
|
||||
CordovaIosDeviceLauncher.webDebuggerProxyInstance = child_process.spawn("ios_webkit_debug_proxy", ["-c", portRange]);
|
||||
CordovaIosDeviceLauncher.webDebuggerProxyInstance.on("error", function () {
|
||||
deferred.reject(new Error("Unable to start ios_webkit_debug_proxy."));
|
||||
});
|
||||
// Allow some time for the spawned process to error out
|
||||
Q.delay(250).then(() => deferred.resolve({}));
|
||||
|
@ -87,20 +87,20 @@ export class CordovaIosDeviceLauncher {
|
|||
}
|
||||
|
||||
public static getPathOnDevice(packageId: string): Q.Promise<string> {
|
||||
return promiseExec('ideviceinstaller -l -o xml > /tmp/$$.ideviceinstaller && echo /tmp/$$.ideviceinstaller')
|
||||
return promiseExec("ideviceinstaller -l -o xml > /tmp/$$.ideviceinstaller && echo /tmp/$$.ideviceinstaller")
|
||||
.catch(function (err: any): any {
|
||||
if (err.code === 'ENOENT') {
|
||||
throw new Error('Unable to find ideviceinstaller.');
|
||||
if (err.code === "ENOENT") {
|
||||
throw new Error("Unable to find ideviceinstaller.");
|
||||
}
|
||||
throw err;
|
||||
}).spread<string>(function (stdout: string, stderr: string): string {
|
||||
}).spread<string>(function (stdout: string): 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('Unable to list installed applications on device');
|
||||
throw new Error("Unable to list installed applications on device");
|
||||
}
|
||||
|
||||
let list: any[] = pl.parse(fs.readFileSync(filename, 'utf8'));
|
||||
let list: any[] = pl.parse(fs.readFileSync(filename, "utf8"));
|
||||
fs.unlink(filename);
|
||||
for (let i: number = 0; i < list.length; ++i) {
|
||||
if (list[i].CFBundleIdentifier === packageId) {
|
||||
|
@ -109,7 +109,7 @@ export class CordovaIosDeviceLauncher {
|
|||
}
|
||||
}
|
||||
|
||||
throw new Error('Application not installed on the device');
|
||||
throw new Error("Application not installed on the device");
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -131,23 +131,23 @@ export class CordovaIosDeviceLauncher {
|
|||
let deferred2: Q.Deferred<net.Socket> = Q.defer<net.Socket>();
|
||||
let deferred3: Q.Deferred<net.Socket> = Q.defer<net.Socket>();
|
||||
|
||||
socket.on('data', function (data: any): void {
|
||||
socket.on("data", function (data: any): void {
|
||||
data = data.toString();
|
||||
while (data[0] === '+') { data = data.substring(1); }
|
||||
while (data[0] === "+") { data = data.substring(1); }
|
||||
// Acknowledge any packets sent our way
|
||||
if (data[0] === '$') {
|
||||
socket.write('+');
|
||||
if (data[1] === 'W') {
|
||||
if (data[0] === "$") {
|
||||
socket.write("+");
|
||||
if (data[1] === "W") {
|
||||
// The app process has exited, with hex status given by data[2-3]
|
||||
let status: number = parseInt(data.substring(2, 4), 16);
|
||||
endStatus = status;
|
||||
socket.end();
|
||||
} else if (data[1] === 'X') {
|
||||
} else if (data[1] === "X") {
|
||||
// The app rocess exited because of signal given by data[2-3]
|
||||
let signal: number = parseInt(data.substring(2, 4), 16);
|
||||
endSignal = signal;
|
||||
socket.end();
|
||||
} else if (data.substring(1, 3) === 'OK') {
|
||||
} else if (data.substring(1, 3) === "OK") {
|
||||
// last command was received OK;
|
||||
if (initState === 1) {
|
||||
deferred1.resolve(socket);
|
||||
|
@ -158,19 +158,19 @@ export class CordovaIosDeviceLauncher {
|
|||
deferred3.resolve(socket);
|
||||
initState++;
|
||||
}
|
||||
} else if (data[1] === 'O') {
|
||||
} else if (data[1] === "O") {
|
||||
// STDOUT was written to, and the rest of the input until reaching a '#' is a hex-encoded string of that output
|
||||
if (initState === 3) {
|
||||
deferred3.resolve(socket);
|
||||
initState++;
|
||||
}
|
||||
} else if (data[1] === 'E') {
|
||||
} else if (data[1] === "E") {
|
||||
// An error has occurred, with error code given by data[2-3]: parseInt(data.substring(2, 4), 16)
|
||||
deferred1.reject('Unable to launch application.');
|
||||
deferred2.reject('Unable to launch application.');
|
||||
deferred3.reject('Unable to launch application.');
|
||||
deferred1.reject("Unable to launch application.");
|
||||
deferred2.reject("Unable to launch application.");
|
||||
deferred3.reject("Unable to launch application.");
|
||||
}
|
||||
} else if (data === '' && initState === 3) {
|
||||
} else if (data === "" && initState === 3) {
|
||||
// On iOS 10.2.1 (and maybe others) after 'c' message debug server doesn't respond with '$OK', see also
|
||||
// http://www.embecosm.com/appnotes/ean4/embecosm-howto-rsp-server-ean4-issue-2.html#sec_exchange_cont
|
||||
deferred3.resolve(socket);
|
||||
|
@ -178,44 +178,44 @@ export class CordovaIosDeviceLauncher {
|
|||
}
|
||||
});
|
||||
|
||||
socket.on('end', function (): void {
|
||||
deferred1.reject('Unable to launch application.');
|
||||
deferred2.reject('Unable to launch application.');
|
||||
deferred3.reject('Unable to launch application.');
|
||||
socket.on("end", function (): void {
|
||||
deferred1.reject("Unable to launch application.");
|
||||
deferred2.reject("Unable to launch application.");
|
||||
deferred3.reject("Unable to launch application.");
|
||||
});
|
||||
|
||||
socket.on('error', function (err: Error): void {
|
||||
socket.on("error", function (err: Error): void {
|
||||
deferred1.reject(err);
|
||||
deferred2.reject(err);
|
||||
deferred3.reject(err);
|
||||
});
|
||||
|
||||
socket.connect(portNumber, 'localhost', function (): void {
|
||||
socket.connect(portNumber, "localhost", function (): void {
|
||||
// set argument 0 to the (encoded) path of the app
|
||||
let cmd: string = CordovaIosDeviceLauncher.makeGdbCommand('A' + encodedPath.length + ',0,' + encodedPath);
|
||||
let cmd: string = CordovaIosDeviceLauncher.makeGdbCommand("A" + encodedPath.length + ",0," + encodedPath);
|
||||
initState++;
|
||||
socket.write(cmd);
|
||||
setTimeout(function (): void {
|
||||
deferred1.reject('Timeout launching application. Is the device locked?');
|
||||
deferred1.reject("Timeout launching application. Is the device locked?");
|
||||
}, appLaunchStepTimeout);
|
||||
});
|
||||
|
||||
return deferred1.promise.then(function (sock: net.Socket): Q.Promise<net.Socket> {
|
||||
// Set the step and continue thread to any thread
|
||||
let cmd: string = CordovaIosDeviceLauncher.makeGdbCommand('Hc0');
|
||||
let cmd: string = CordovaIosDeviceLauncher.makeGdbCommand("Hc0");
|
||||
initState++;
|
||||
sock.write(cmd);
|
||||
setTimeout(function (): void {
|
||||
deferred2.reject('Timeout launching application. Is the device locked?');
|
||||
deferred2.reject("Timeout launching application. Is the device locked?");
|
||||
}, appLaunchStepTimeout);
|
||||
return deferred2.promise;
|
||||
}).then(function (sock: net.Socket): Q.Promise<net.Socket> {
|
||||
// Continue execution; actually start the app running.
|
||||
let cmd: string = CordovaIosDeviceLauncher.makeGdbCommand('c');
|
||||
let cmd: string = CordovaIosDeviceLauncher.makeGdbCommand("c");
|
||||
initState++;
|
||||
sock.write(cmd);
|
||||
setTimeout(function (): void {
|
||||
deferred3.reject('Timeout launching application. Is the device locked?');
|
||||
deferred3.reject("Timeout launching application. Is the device locked?");
|
||||
}, appLaunchStepTimeout);
|
||||
return deferred3.promise;
|
||||
}).then(() => packagePath);
|
||||
|
@ -223,32 +223,32 @@ export class CordovaIosDeviceLauncher {
|
|||
|
||||
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();
|
||||
return packagePath.split("").map((c: string) => c.charCodeAt(0).toString(16)).join("").toUpperCase();
|
||||
}
|
||||
|
||||
private static mountDeveloperImage(): Q.Promise<any> {
|
||||
return CordovaIosDeviceLauncher.getDiskImage()
|
||||
.then(function (path: string): Q.Promise<any> {
|
||||
let imagemounter: child_process.ChildProcess = child_process.spawn('ideviceimagemounter', [path]);
|
||||
let imagemounter: child_process.ChildProcess = child_process.spawn("ideviceimagemounter", [path]);
|
||||
let deferred: Q.Deferred<any> = Q.defer();
|
||||
let stdout: string = '';
|
||||
imagemounter.stdout.on('data', function (data: any): void {
|
||||
let stdout: string = "";
|
||||
imagemounter.stdout.on("data", function (data: any): void {
|
||||
stdout += data.toString();
|
||||
});
|
||||
imagemounter.on('close', function (code: number): void {
|
||||
imagemounter.on("close", function (code: number): void {
|
||||
if (code !== 0) {
|
||||
if (stdout.indexOf('Error:') !== -1) {
|
||||
if (stdout.indexOf("Error:") !== -1) {
|
||||
deferred.resolve({}); // Technically failed, but likely caused by the image already being mounted.
|
||||
} else if (stdout.indexOf('No device found, is it plugged in?') !== -1) {
|
||||
deferred.reject('Unable to find device. Is the device plugged in?');
|
||||
} else if (stdout.indexOf("No device found, is it plugged in?") !== -1) {
|
||||
deferred.reject("Unable to find device. Is the device plugged in?");
|
||||
}
|
||||
|
||||
deferred.reject('Unable to mount developer disk image.');
|
||||
deferred.reject("Unable to mount developer disk image.");
|
||||
} else {
|
||||
deferred.resolve({});
|
||||
}
|
||||
});
|
||||
imagemounter.on('error', function(err: any): void {
|
||||
imagemounter.on("error", function(err: any): void {
|
||||
deferred.reject(err);
|
||||
});
|
||||
return deferred.promise;
|
||||
|
@ -257,37 +257,37 @@ export class CordovaIosDeviceLauncher {
|
|||
|
||||
private static getDiskImage(): Q.Promise<string> {
|
||||
// Attempt to find the OS version of the iDevice, e.g. 7.1
|
||||
let versionInfo: Q.Promise<any> = promiseExec('ideviceinfo -s -k ProductVersion')
|
||||
.spread<string>(function (stdout: string, stderr: string): string {
|
||||
let versionInfo: Q.Promise<any> = promiseExec("ideviceinfo -s -k ProductVersion")
|
||||
.spread<string>(function (stdout: string): string {
|
||||
// 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(function (e): string {
|
||||
throw new Error('Unable to get device OS version. Details: ${e.message}');
|
||||
throw new Error(`Unable to get device OS version. Details: ${e.message}`);
|
||||
});
|
||||
|
||||
// Attempt to find the path where developer resources exist.
|
||||
let pathInfo: Q.Promise<any> = promiseExec('xcrun -sdk iphoneos --show-sdk-platform-path').spread<string>(function (stdout: string, stderr: string): string {
|
||||
let pathInfo: Q.Promise<any> = promiseExec("xcrun -sdk iphoneos --show-sdk-platform-path").spread<string>(function (stdout: string): string {
|
||||
let sdkpath: string = stdout.trim();
|
||||
return sdkpath;
|
||||
});
|
||||
|
||||
// Attempt to find the developer disk image for the appropriate
|
||||
return Q.all([versionInfo, pathInfo]).spread<string>(function (version: string, sdkpath: string): Q.Promise<string> {
|
||||
let find: child_process.ChildProcess = child_process.spawn('find', [sdkpath, '-path', '*' + version + '*', '-name', 'DeveloperDiskImage.dmg']);
|
||||
let find: child_process.ChildProcess = child_process.spawn("find", [sdkpath, "-path", "*" + version + "*", "-name", "DeveloperDiskImage.dmg"]);
|
||||
let deferred: Q.Deferred<string> = Q.defer<string>();
|
||||
|
||||
find.stdout.on('data', function (data: any): void {
|
||||
find.stdout.on("data", function (data: any): void {
|
||||
let dataStr: string = data.toString();
|
||||
let path: string = dataStr.split('\n')[0].trim();
|
||||
let path: string = dataStr.split("\n")[0].trim();
|
||||
if (!path) {
|
||||
deferred.reject('Unable to find developer disk image');
|
||||
deferred.reject("Unable to find developer disk image");
|
||||
} else {
|
||||
deferred.resolve(path);
|
||||
}
|
||||
});
|
||||
find.on('close', function (code: number): void {
|
||||
deferred.reject('Unable to find developer disk image');
|
||||
find.on("close", function (): void {
|
||||
deferred.reject("Unable to find developer disk image");
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
|
@ -295,7 +295,7 @@ export class CordovaIosDeviceLauncher {
|
|||
}
|
||||
|
||||
private static makeGdbCommand(command: string): string {
|
||||
let commandString: string = '$' + command + '#';
|
||||
let commandString: string = "$" + command + "#";
|
||||
let stringSum: number = 0;
|
||||
for (let i: number = 0; i < command.length; i++) {
|
||||
stringSum += command.charCodeAt(i);
|
||||
|
@ -307,7 +307,7 @@ export class CordovaIosDeviceLauncher {
|
|||
/* tslint:enable:no-bitwise */
|
||||
let checksum: string = stringSum.toString(16).toUpperCase();
|
||||
if (checksum.length < 2) {
|
||||
checksum = '0' + checksum;
|
||||
checksum = "0" + checksum;
|
||||
}
|
||||
|
||||
commandString += checksum;
|
||||
|
@ -315,4 +315,4 @@ export class CordovaIosDeviceLauncher {
|
|||
}
|
||||
}
|
||||
|
||||
process.on('exit', CordovaIosDeviceLauncher.cleanup);
|
||||
process.on("exit", CordovaIosDeviceLauncher.cleanup);
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for details.
|
||||
|
||||
import {DebugProtocol} from 'vscode-debugprotocol';
|
||||
import {utils, logger, chromeUtils, ISetBreakpointsArgs, IDebugTransformer, IStackTraceResponseBody} from 'vscode-chrome-debug-core';
|
||||
import {ICordovaLaunchRequestArgs, ICordovaAttachRequestArgs} from './cordovaDebugAdapter';
|
||||
import {utils, logger, chromeUtils, ISetBreakpointsArgs, IStackTraceResponseBody} from "vscode-chrome-debug-core";
|
||||
import {ICordovaLaunchRequestArgs, ICordovaAttachRequestArgs} from "./cordovaDebugAdapter";
|
||||
|
||||
import {BasePathTransformer} from 'vscode-chrome-debug-core';
|
||||
import {BasePathTransformer} from "vscode-chrome-debug-core";
|
||||
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import * as path from "path";
|
||||
import * as fs from "fs";
|
||||
|
||||
interface IPendingBreakpoint {
|
||||
resolve: () => void;
|
||||
|
@ -130,16 +129,16 @@ export class CordovaPathTransformer extends BasePathTransformer {
|
|||
}
|
||||
|
||||
public getClientPath(sourceUrl: string): string {
|
||||
let wwwRoot = path.join(this._cordovaRoot, 'www');
|
||||
let 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,
|
||||
// since we know that those files are copies of files in the local folder structure.
|
||||
// A simple workaround for this is to convert file:// paths to bogus http:// paths
|
||||
sourceUrl = sourceUrl.replace('file:///', 'http://localhost/');
|
||||
sourceUrl = sourceUrl.replace("file:///", "http://localhost/");
|
||||
|
||||
// Find the mapped local file. Try looking first in the user-specified webRoot, then in the project root, and then in the www folder
|
||||
let defaultPath = '';
|
||||
let defaultPath = "";
|
||||
[this._webRoot, this._cordovaRoot, wwwRoot].find((searchFolder) => {
|
||||
let mappedPath = chromeUtils.targetUrlToClientPath(searchFolder, sourceUrl);
|
||||
|
||||
|
@ -154,7 +153,7 @@ export class CordovaPathTransformer extends BasePathTransformer {
|
|||
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);
|
||||
let mergesPath = path.join(this._cordovaRoot, "merges", this._platform, relativePath);
|
||||
if (fs.existsSync(mergesPath)) {
|
||||
// This file is overriden by a merge: Use that one
|
||||
if (fs.existsSync(defaultPath)) {
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for details.
|
||||
|
||||
import {ChromeDebugSession, BaseSourceMapTransformer, BasePathTransformer} from 'vscode-chrome-debug-core';
|
||||
import {CordovaDebugAdapter} from './cordovaDebugAdapter';
|
||||
import {CordovaPathTransformer} from './cordovaPathTransformer';
|
||||
import {ChromeDebugSession, BaseSourceMapTransformer, BasePathTransformer} from "vscode-chrome-debug-core";
|
||||
import {CordovaDebugAdapter} from "./cordovaDebugAdapter";
|
||||
import {CordovaPathTransformer} from "./cordovaPathTransformer";
|
||||
|
||||
ChromeDebugSession.run(ChromeDebugSession.getSession({
|
||||
adapter: CordovaDebugAdapter,
|
||||
extensionName: 'cordova-tools',
|
||||
extensionName: "cordova-tools",
|
||||
pathTransformer: <typeof BasePathTransformer><any>CordovaPathTransformer,
|
||||
sourceMapTransformer: BaseSourceMapTransformer
|
||||
sourceMapTransformer: BaseSourceMapTransformer,
|
||||
}));
|
|
@ -1,37 +1,34 @@
|
|||
// 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 os from 'os';
|
||||
import * as Q from 'q';
|
||||
import * as path from 'path';
|
||||
import * as util from 'util';
|
||||
import { ExtensionMessageSender, ExtensionMessage } from '../common/extensionMessaging';
|
||||
import { CordovaCommandHelper } from '../utils/cordovaCommandHelper';
|
||||
import * as child_process from "child_process";
|
||||
import {CordovaProjectHelper} from "../utils/cordovaProjectHelper";
|
||||
import * as Q from "q";
|
||||
import * as path from "path";
|
||||
import * as util from "util";
|
||||
|
||||
// suppress the following strings because they are not actual errors:
|
||||
const errorsToSuppress = ['Run an Ionic project on a connected device'];
|
||||
const errorsToSuppress = ["Run an Ionic project on a connected device"];
|
||||
|
||||
export function execCommand(command: string, args: string[], errorLogger: (message: string) => void): Q.Promise<string> {
|
||||
let deferred = Q.defer<string>();
|
||||
let proc = child_process.spawn(command, args, { stdio: 'pipe' });
|
||||
let stderr = '';
|
||||
let stdout = '';
|
||||
proc.stderr.on('data', (data: Buffer) => {
|
||||
let 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) => {
|
||||
proc.stdout.on("data", (data: Buffer) => {
|
||||
stdout += data.toString();
|
||||
});
|
||||
proc.on('error', (err: Error) => {
|
||||
proc.on("error", (err: Error) => {
|
||||
deferred.reject(err);
|
||||
});
|
||||
proc.on('close', (code: number) => {
|
||||
proc.on("close", (code: number) => {
|
||||
if (code !== 0) {
|
||||
errorLogger(stderr);
|
||||
errorLogger(stdout);
|
||||
deferred.reject(`Error running '${command} ${args.join(' ')}'`);
|
||||
deferred.reject(`Error running '${command} ${args.join(" ")}'`);
|
||||
}
|
||||
deferred.resolve(stdout);
|
||||
});
|
||||
|
@ -42,54 +39,54 @@ export function execCommand(command: string, args: string[], errorLogger: (messa
|
|||
export function cordovaRunCommand(args: string[], cordovaRootPath: string): Q.Promise<string[]> {
|
||||
let defer = Q.defer<string[]>();
|
||||
let isIonicProject = CordovaProjectHelper.isIonicProject(cordovaRootPath);
|
||||
let cliName = isIonicProject ? 'ionic' : 'cordova';
|
||||
let output = '';
|
||||
let stderr = '';
|
||||
let cliName = isIonicProject ? "ionic" : "cordova";
|
||||
let output = "";
|
||||
let stderr = "";
|
||||
let cordovaProcess = cordovaStartCommand(args, 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
|
||||
"Running command": false,
|
||||
"cordova prepare": false,
|
||||
"cordova platform add": false,
|
||||
};
|
||||
|
||||
cordovaProcess.stderr.on('data', data => {
|
||||
cordovaProcess.stderr.on("data", data => {
|
||||
stderr += data.toString();
|
||||
for (var i = 0; i < errorsToSuppress.length; i++) {
|
||||
for (let i = 0; i < errorsToSuppress.length; i++) {
|
||||
if (data.toString().indexOf(errorsToSuppress[i]) >= 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
defer.notify([data.toString(), 'stderr']);
|
||||
defer.notify([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
|
||||
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;
|
||||
defer.notify([str, 'stdout']);
|
||||
defer.notify([str, "stdout"]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
defer.notify([str, 'stdout']);
|
||||
defer.notify([str, "stdout"]);
|
||||
|
||||
if (isIonicProject && str.indexOf('LAUNCH SUCCESS') >= 0) {
|
||||
if (isIonicProject && str.indexOf("LAUNCH SUCCESS") >= 0) {
|
||||
defer.resolve([output, stderr]);
|
||||
}
|
||||
});
|
||||
cordovaProcess.on('exit', exitCode => {
|
||||
cordovaProcess.on("exit", exitCode => {
|
||||
if (exitCode) {
|
||||
defer.reject(new Error(util.format('\'%s %s\' failed with exit code %d', cliName, args.join(' '), exitCode)));
|
||||
defer.reject(new Error(util.format("'%s %s' failed with exit code %d", cliName, args.join(" "), exitCode)));
|
||||
} else {
|
||||
defer.resolve([output, stderr]);
|
||||
}
|
||||
});
|
||||
cordovaProcess.on('error', error => {
|
||||
cordovaProcess.on("error", error => {
|
||||
defer.reject(error);
|
||||
});
|
||||
|
||||
|
@ -99,29 +96,29 @@ export function cordovaRunCommand(args: string[], cordovaRootPath: string): Q.Pr
|
|||
export function cordovaStartCommand(args: string[], cordovaRootPath: string): child_process.ChildProcess {
|
||||
const command = CordovaProjectHelper.getCliCommand(cordovaRootPath);
|
||||
const isIonic = CordovaProjectHelper.isIonicProject(cordovaRootPath);
|
||||
const isIonicServe: boolean = args.indexOf('serve') >= 0;
|
||||
const isIonicServe: boolean = args.indexOf("serve") >= 0;
|
||||
|
||||
if (isIonic && !isIonicServe) {
|
||||
const isIonicCliVersionGte3 = CordovaProjectHelper.isIonicCliVersionGte3(cordovaRootPath);
|
||||
|
||||
if (isIonicCliVersionGte3) {
|
||||
args.unshift('cordova');
|
||||
args.unshift("cordova");
|
||||
}
|
||||
}
|
||||
|
||||
if (isIonic) {
|
||||
args.push('--no-interactive');
|
||||
args.push("--no-interactive");
|
||||
} else {
|
||||
args.push('--no-update-notifier');
|
||||
args.push("--no-update-notifier");
|
||||
}
|
||||
|
||||
return child_process.spawn(command, args, { cwd: cordovaRootPath });
|
||||
}
|
||||
|
||||
export function killTree(processId: number): void {
|
||||
const cmd = process.platform === 'win32' ?
|
||||
const cmd = process.platform === "win32" ?
|
||||
`taskkill.exe /F /T /PID` :
|
||||
path.join(__dirname, '../../../scripts/terminateProcess.sh')
|
||||
path.join(__dirname, "../../../scripts/terminateProcess.sh");
|
||||
|
||||
try {
|
||||
child_process.execSync(`${cmd} ${processId}`);
|
||||
|
|
|
@ -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 * as fs from 'fs';
|
||||
import * as fs from "fs";
|
||||
import {
|
||||
CompletionItem, CompletionItemKind, CompletionItemProvider,
|
||||
DocumentSelector, CancellationToken, TextDocument, SnippetString, Position
|
||||
} from 'vscode';
|
||||
DocumentSelector, SnippetString
|
||||
} from "vscode";
|
||||
|
||||
// Types to outline TextMate snippets format, used in this extension's snippet files
|
||||
type TMSnippet = { prefix: string, body: string[], description: string };
|
||||
type TMSnippets = { [name: string]: TMSnippet };
|
||||
|
||||
export class IonicCompletionProvider implements CompletionItemProvider {
|
||||
public static HTML_DOCUMENT_SELECTOR: DocumentSelector = 'html';
|
||||
public static JS_DOCUMENT_SELECTOR: DocumentSelector = 'javascript';
|
||||
public static HTML_DOCUMENT_SELECTOR: DocumentSelector = "html";
|
||||
public static JS_DOCUMENT_SELECTOR: DocumentSelector = "javascript";
|
||||
|
||||
private snippetCompletions: CompletionItem[];
|
||||
|
||||
constructor(private completionsSource: string) { }
|
||||
|
||||
public provideCompletionItems(document: TextDocument,
|
||||
position: Position,
|
||||
token: CancellationToken): CompletionItem[] {
|
||||
public provideCompletionItems(): CompletionItem[] {
|
||||
|
||||
if (this.snippetCompletions) {
|
||||
return this.snippetCompletions;
|
||||
|
@ -30,7 +28,7 @@ export class IonicCompletionProvider implements CompletionItemProvider {
|
|||
this.snippetCompletions = [];
|
||||
|
||||
try {
|
||||
let rawSnippets: TMSnippets = JSON.parse(fs.readFileSync(this.completionsSource, 'utf8'));
|
||||
let rawSnippets: TMSnippets = JSON.parse(fs.readFileSync(this.completionsSource, "utf8"));
|
||||
this.snippetCompletions = Object.keys(rawSnippets)
|
||||
.map(name => makeCompletionItem(rawSnippets[name]));
|
||||
|
||||
|
@ -47,7 +45,7 @@ function makeCompletionItem(rawSnippet: TMSnippet): CompletionItem {
|
|||
const item = new CompletionItem(rawSnippet.prefix);
|
||||
item.documentation = rawSnippet.description;
|
||||
item.kind = CompletionItemKind.Snippet;
|
||||
item.insertText = new SnippetString(rawSnippet.body.join('\n'));
|
||||
item.insertText = new SnippetString(rawSnippet.body.join("\n"));
|
||||
|
||||
return item;
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
import * as net from "net";
|
||||
import * as Q from "q";
|
||||
import {PluginSimulator} from "./simulate";
|
||||
import {SimulationInfo} from '../common/simulationInfo';
|
||||
import {SimulationInfo} from "../common/simulationInfo";
|
||||
import {SimulateOptions} from "cordova-simulate";
|
||||
import * as vscode from "vscode";
|
||||
|
||||
|
@ -20,8 +20,8 @@ import {Telemetry} from "../utils/telemetry";
|
|||
import { CordovaCommandHelper } from "../utils/cordovaCommandHelper";
|
||||
|
||||
export class ExtensionServer implements vscode.Disposable {
|
||||
private serverInstance: net.Server = null;
|
||||
public pluginSimulator: PluginSimulator;
|
||||
private serverInstance: net.Server = null;
|
||||
private messageHandlerDictionary: { [id: number]: ((...argArray: any[]) => Q.Promise<any>) } = {};
|
||||
private pipePath: string;
|
||||
|
||||
|
@ -158,7 +158,7 @@ export class ExtensionServer implements vscode.Disposable {
|
|||
};
|
||||
|
||||
socket.on("data", dataCallback);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Recovers the server in case the named socket we use already exists, but no other instance of VSCode is active.
|
||||
|
|
|
@ -2,17 +2,70 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for details.
|
||||
|
||||
import {Hash} from "../utils/hash";
|
||||
import * as http from "http";
|
||||
import * as Q from "q";
|
||||
import * as cordovaServer from "cordova-serve";
|
||||
import * as path from "path";
|
||||
import {Simulator, SimulateOptions, launchBrowser} from "cordova-simulate";
|
||||
import {CordovaSimulateTelemetry} from "../utils/cordovaSimulateTelemetry";
|
||||
import {IProjectType, CordovaProjectHelper} from '../utils/cordovaProjectHelper';
|
||||
import {SimulationInfo} from '../common/simulationInfo';
|
||||
import {IProjectType, CordovaProjectHelper} from "../utils/cordovaProjectHelper";
|
||||
import {SimulationInfo} from "../common/simulationInfo";
|
||||
import * as vscode from "vscode";
|
||||
import { CordovaCommandHelper } from "../utils/cordovaCommandHelper";
|
||||
|
||||
/**
|
||||
* Content provider hosting the simulation UI inside a document.
|
||||
*/
|
||||
class SimHostContentProvider implements vscode.TextDocumentContentProvider {
|
||||
private simHostUrl: string;
|
||||
private simulateUri: vscode.Uri;
|
||||
private changeEmitter = new vscode.EventEmitter<vscode.Uri>();
|
||||
|
||||
constructor(simHostUrl: string, simulateUri: vscode.Uri) {
|
||||
this.simHostUrl = simHostUrl;
|
||||
this.simulateUri = simulateUri;
|
||||
}
|
||||
|
||||
get onDidChange() {
|
||||
return this.changeEmitter.event;
|
||||
}
|
||||
|
||||
public fireChange() {
|
||||
this.changeEmitter.fire(this.simulateUri);
|
||||
}
|
||||
|
||||
public provideTextDocumentContent(): string {
|
||||
// always return different html so that the tab is properly reloaded and events are fired
|
||||
return `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.intrinsic-container iframe {
|
||||
position: absolute;
|
||||
top:0;
|
||||
left: 0;
|
||||
border: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div style="display: none">
|
||||
Always be changing ${Math.random()}
|
||||
</div>
|
||||
<div class="intrinsic-container">
|
||||
<iframe src="${this.simHostUrl}" ></iframe>
|
||||
</div>
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin simulation entry point.
|
||||
*/
|
||||
|
@ -47,8 +100,8 @@ export class PluginSimulator implements vscode.Disposable {
|
|||
|
||||
const uri = vscode.Uri.file(fsPath);
|
||||
const workspaceFolder = <vscode.WorkspaceFolder>vscode.workspace.getWorkspaceFolder(uri);
|
||||
const simulateProtocol = 'cordova-simulate-' + Hash.hashCode(workspaceFolder.uri.fsPath);
|
||||
const simulateUri = vscode.Uri.parse(simulateProtocol + '://authority/cordova-simulate');
|
||||
const simulateProtocol = "cordova-simulate-" + Hash.hashCode(workspaceFolder.uri.fsPath);
|
||||
const simulateUri = vscode.Uri.parse(simulateProtocol + "://authority/cordova-simulate");
|
||||
|
||||
let provider = new SimHostContentProvider(this.simulator.simHostUrl(), simulateUri);
|
||||
this.registration = vscode.workspace.registerTextDocumentContentProvider(simulateProtocol, provider);
|
||||
|
@ -61,7 +114,7 @@ export class PluginSimulator implements vscode.Disposable {
|
|||
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 Q({}).then(() => {
|
||||
|
@ -102,17 +155,13 @@ export class PluginSimulator implements vscode.Disposable {
|
|||
urlRoot: this.simulator.urlRoot(),
|
||||
};
|
||||
if (projectType.ionic2 && platform && platform !== "browser") {
|
||||
this.simulationInfo.appHostUrl = `${this.simulationInfo.appHostUrl}?ionicplatform=${simulateOptions.platform}`
|
||||
this.simulationInfo.appHostUrl = `${this.simulationInfo.appHostUrl}?ionicplatform=${simulateOptions.platform}`;
|
||||
}
|
||||
return this.simulationInfo;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private isServerRunning(): boolean {
|
||||
return this.simulator && this.simulator.isRunning();
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (this.registration) {
|
||||
this.registration.dispose();
|
||||
|
@ -120,63 +169,12 @@ export class PluginSimulator implements vscode.Disposable {
|
|||
}
|
||||
|
||||
if (this.simulator) {
|
||||
this.simulator.stopSimulation().done(()=>{}, () => {});
|
||||
this.simulator.stopSimulation().done(() => {}, () => {});
|
||||
this.simulator = null;
|
||||
}
|
||||
}
|
||||
|
||||
private isServerRunning(): boolean {
|
||||
return this.simulator && this.simulator.isRunning();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Content provider hosting the simulation UI inside a document.
|
||||
*/
|
||||
class SimHostContentProvider implements vscode.TextDocumentContentProvider {
|
||||
private simHostUrl: string;
|
||||
private simulateUri: vscode.Uri;
|
||||
private changeEmitter = new vscode.EventEmitter<vscode.Uri>();
|
||||
|
||||
constructor(simHostUrl: string, simulateUri: vscode.Uri) {
|
||||
this.simHostUrl = simHostUrl;
|
||||
this.simulateUri = simulateUri;
|
||||
}
|
||||
|
||||
get onDidChange() {
|
||||
return this.changeEmitter.event;
|
||||
}
|
||||
|
||||
public fireChange() {
|
||||
this.changeEmitter.fire(this.simulateUri);
|
||||
}
|
||||
|
||||
public provideTextDocumentContent(uri: vscode.Uri): string {
|
||||
// always return different html so that the tab is properly reloaded and events are fired
|
||||
return `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.intrinsic-container iframe {
|
||||
position: absolute;
|
||||
top:0;
|
||||
left: 0;
|
||||
border: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div style="display: none">
|
||||
Always be changing ${Math.random()}
|
||||
</div>
|
||||
<div class="intrinsic-container">
|
||||
<iframe src="${this.simHostUrl}" ></iframe>
|
||||
</div>
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
}
|
|
@ -1,23 +1,23 @@
|
|||
// 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 Q from 'q';
|
||||
import * as os from 'os';
|
||||
import * as util from 'util';
|
||||
import { window, WorkspaceConfiguration, workspace, Uri } from 'vscode';
|
||||
import * as child_process from "child_process";
|
||||
import * as Q from "q";
|
||||
import * as os from "os";
|
||||
import * as util from "util";
|
||||
import { window, WorkspaceConfiguration, workspace, Uri } from "vscode";
|
||||
|
||||
import { TelemetryHelper } from './telemetryHelper';
|
||||
import { OutputChannelLogger } from './outputChannelLogger';
|
||||
import { CordovaProjectHelper } from './cordovaProjectHelper';
|
||||
import { TelemetryHelper } from "./telemetryHelper";
|
||||
import { OutputChannelLogger } from "./outputChannelLogger";
|
||||
import { CordovaProjectHelper } from "./cordovaProjectHelper";
|
||||
|
||||
export class CordovaCommandHelper {
|
||||
private static CORDOVA_CMD_NAME: string = os.platform() === 'win32' ? 'cordova.cmd' : 'cordova';
|
||||
private static IONIC_CMD_NAME: string = os.platform() === 'win32' ? 'ionic.cmd' : 'ionic';
|
||||
private static CORDOVA_TELEMETRY_EVENT_NAME: string = 'cordovaCommand';
|
||||
private static IONIC_TELEMETRY_EVENT_NAME: string = 'ionicCommand';
|
||||
private static CORDOVA_DISPLAY_NAME: string = 'Cordova';
|
||||
private static IONIC_DISPLAY_NAME: string = 'Ionic';
|
||||
private static CORDOVA_CMD_NAME: string = os.platform() === "win32" ? "cordova.cmd" : "cordova";
|
||||
private static IONIC_CMD_NAME: string = os.platform() === "win32" ? "ionic.cmd" : "ionic";
|
||||
private static CORDOVA_TELEMETRY_EVENT_NAME: string = "cordovaCommand";
|
||||
private static IONIC_TELEMETRY_EVENT_NAME: string = "ionicCommand";
|
||||
private static CORDOVA_DISPLAY_NAME: string = "Cordova";
|
||||
private static IONIC_DISPLAY_NAME: string = "Ionic";
|
||||
|
||||
public static executeCordovaCommand(projectRoot: string, command: string, useIonic: boolean = false) {
|
||||
let telemetryEventName: string = CordovaCommandHelper.CORDOVA_TELEMETRY_EVENT_NAME;
|
||||
|
@ -33,7 +33,7 @@ export class CordovaCommandHelper {
|
|||
return CordovaCommandHelper.selectPlatform(projectRoot, command)
|
||||
.then((platform) => {
|
||||
TelemetryHelper.generate(telemetryEventName, (generator) => {
|
||||
generator.add('command', command, false);
|
||||
generator.add("command", command, false);
|
||||
let logger = OutputChannelLogger.getMainChannel();
|
||||
let commandToExecute = `${cliCommandName} ${command}`;
|
||||
|
||||
|
@ -43,36 +43,36 @@ export class CordovaCommandHelper {
|
|||
|
||||
const runArgs = CordovaCommandHelper.getRunArguments(projectRoot);
|
||||
if (runArgs.length) {
|
||||
commandToExecute += ` ${runArgs.join(' ')}`;
|
||||
commandToExecute += ` ${runArgs.join(" ")}`;
|
||||
}
|
||||
|
||||
logger.log(`########### EXECUTING: ${commandToExecute} ###########`);
|
||||
let process = child_process.exec(commandToExecute, { cwd: projectRoot });
|
||||
|
||||
let deferred = Q.defer();
|
||||
process.on('error', (err: any) => {
|
||||
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(util.format('%s not found, please run "npm install –g %s" to install %s globally', cliDisplayName, cliDisplayName.toLowerCase(), cliDisplayName));
|
||||
if (err.code === "ENOENT") {
|
||||
window.showErrorMessage(util.format("%s not found, please run \"npm install –g %s\" to install %s globally", cliDisplayName, cliDisplayName.toLowerCase(), cliDisplayName));
|
||||
}
|
||||
deferred.reject(err);
|
||||
});
|
||||
|
||||
process.stderr.on('data', (data: any) => {
|
||||
process.stderr.on("data", (data: any) => {
|
||||
logger.append(data);
|
||||
});
|
||||
|
||||
process.stdout.on('data', (data: any) => {
|
||||
process.stdout.on("data", (data: any) => {
|
||||
logger.append(data);
|
||||
});
|
||||
|
||||
process.stdout.on('close', (exitCode: number) => {
|
||||
process.stdout.on("close", () => {
|
||||
logger.log(`########### FINISHED EXECUTING: ${commandToExecute} ###########`);
|
||||
deferred.resolve({});
|
||||
});
|
||||
|
||||
return TelemetryHelper.determineProjectTypes(projectRoot)
|
||||
.then((projectType) => generator.add('projectType', projectType, false))
|
||||
.then((projectType) => generator.add("projectType", projectType, false))
|
||||
.then(() => deferred.promise);
|
||||
});
|
||||
});
|
||||
|
@ -82,16 +82,16 @@ export class CordovaCommandHelper {
|
|||
* Get command line run arguments from settings.json
|
||||
*/
|
||||
public static getRunArguments(fsPath: string): string[] {
|
||||
return CordovaCommandHelper.getSetting(fsPath, 'runArguments') || [];
|
||||
return CordovaCommandHelper.getSetting(fsPath, "runArguments") || [];
|
||||
}
|
||||
|
||||
public static getSimulatorInExternalBrowserSetting(fsPath: string): boolean {
|
||||
return CordovaCommandHelper.getSetting(fsPath, 'simulatorInExternalBrowser') === true;
|
||||
return CordovaCommandHelper.getSetting(fsPath, "simulatorInExternalBrowser") === true;
|
||||
}
|
||||
|
||||
private static getSetting(fsPath: string, configKey: string): any {
|
||||
let uri = Uri.file(fsPath);
|
||||
const workspaceConfiguration: WorkspaceConfiguration = workspace.getConfiguration('cordova', uri);
|
||||
const workspaceConfiguration: WorkspaceConfiguration = workspace.getConfiguration("cordova", uri);
|
||||
if (workspaceConfiguration.has(configKey)) {
|
||||
return workspaceConfiguration.get(configKey);
|
||||
}
|
||||
|
@ -103,17 +103,17 @@ export class CordovaCommandHelper {
|
|||
|
||||
return Q({})
|
||||
.then(() => {
|
||||
if (['prepare', 'build', 'run'].indexOf(command) > -1) {
|
||||
if (["prepare", "build", "run"].indexOf(command) > -1) {
|
||||
if (platforms.length > 1) {
|
||||
platforms.unshift('all');
|
||||
platforms.unshift("all");
|
||||
return window.showQuickPick(platforms)
|
||||
.then((platform) => {
|
||||
if (!platform) {
|
||||
throw new Error('Platform selection was canceled. Please select target platform to continue!');
|
||||
throw new Error("Platform selection was canceled. Please select target platform to continue!");
|
||||
}
|
||||
|
||||
if (platform === 'all') {
|
||||
return '';
|
||||
if (platform === "all") {
|
||||
return "";
|
||||
}
|
||||
|
||||
return platform;
|
||||
|
@ -121,11 +121,11 @@ export class CordovaCommandHelper {
|
|||
} else if (platforms.length === 1) {
|
||||
return platforms[0];
|
||||
} else {
|
||||
throw new Error('No any platforms installed');
|
||||
throw new Error("No any platforms installed");
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
return "";
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -134,11 +134,11 @@ export class CordovaCommandHelper {
|
|||
|
||||
return platforms.filter((platform) => {
|
||||
switch (platform) {
|
||||
case 'ios':
|
||||
case 'osx':
|
||||
return osPlatform === 'darwin';
|
||||
case 'windows':
|
||||
return osPlatform === 'win32';
|
||||
case "ios":
|
||||
case "osx":
|
||||
return osPlatform === "darwin";
|
||||
case "windows":
|
||||
return osPlatform === "win32";
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -1,12 +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 child_process from 'child_process';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as Q from 'q';
|
||||
import * as child_process from "child_process";
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import * as Q from "q";
|
||||
import * as semver from "semver";
|
||||
import * as os from 'os';
|
||||
import * as os from "os";
|
||||
|
||||
export interface IProjectType {
|
||||
ionic: boolean;
|
||||
|
@ -35,7 +35,7 @@ export class CordovaProjectHelper {
|
|||
private static PROJECT_PLUGINS_DIR: string = "plugins";
|
||||
private static IONIC_PROJECT_FILE: string = "ionic.project";
|
||||
|
||||
private static CONFIG_IONIC_FILENAME: string = 'ionic.config.json';
|
||||
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",
|
||||
|
@ -93,7 +93,7 @@ export class CordovaProjectHelper {
|
|||
CordovaProjectHelper.makeDirectoryRecursive(parentPath);
|
||||
}
|
||||
|
||||
fs.mkdirSync(dirPath)
|
||||
fs.mkdirSync(dirPath);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -103,7 +103,7 @@ export class CordovaProjectHelper {
|
|||
if (fs.existsSync(dirPath)) {
|
||||
if (fs.lstatSync(dirPath).isDirectory()) {
|
||||
fs.readdirSync(dirPath).forEach(function (file) {
|
||||
var curPath = path.join(dirPath, file);
|
||||
let curPath = path.join(dirPath, file);
|
||||
CordovaProjectHelper.deleteDirectoryRecursive(curPath);
|
||||
});
|
||||
|
||||
|
@ -112,15 +112,15 @@ export class CordovaProjectHelper {
|
|||
fs.unlinkSync(dirPath);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to asynchronously copy a file
|
||||
*/
|
||||
public static copyFile(from: string, to: string, encoding?: string): Q.Promise<any> {
|
||||
var deferred: Q.Deferred<any> = Q.defer();
|
||||
var destFile: fs.WriteStream = fs.createWriteStream(to, { encoding: encoding });
|
||||
var srcFile: fs.ReadStream = fs.createReadStream(from, { encoding: encoding });
|
||||
let deferred: Q.Deferred<any> = Q.defer();
|
||||
let destFile: fs.WriteStream = fs.createWriteStream(to, { encoding: encoding });
|
||||
let srcFile: fs.ReadStream = fs.createReadStream(from, { encoding: encoding });
|
||||
destFile.on("finish", function (): void {
|
||||
deferred.resolve({});
|
||||
});
|
||||
|
@ -148,7 +148,7 @@ export class CordovaProjectHelper {
|
|||
}
|
||||
|
||||
try {
|
||||
let fetchJsonContents = fs.readFileSync(fetchJsonPath, 'utf8');
|
||||
let fetchJsonContents = fs.readFileSync(fetchJsonPath, "utf8");
|
||||
let fetchJson = JSON.parse(fetchJsonContents);
|
||||
return Object.keys(fetchJson);
|
||||
} catch (error) {
|
||||
|
@ -170,7 +170,7 @@ export class CordovaProjectHelper {
|
|||
try {
|
||||
let platformsDirContents = fs.readdirSync(platformsPath);
|
||||
return platformsDirContents.filter((platform) => {
|
||||
return platform.charAt(0) !== '.';
|
||||
return platform.charAt(0) !== ".";
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
@ -179,19 +179,19 @@ export class CordovaProjectHelper {
|
|||
}
|
||||
|
||||
public static getInstalledPluginDetails(projectRoot: string, pluginId: string): IPluginDetails {
|
||||
let packageJsonPath: string = path.resolve(projectRoot, CordovaProjectHelper.PROJECT_PLUGINS_DIR, pluginId, 'package.json');
|
||||
let 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'));
|
||||
let packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
||||
|
||||
let details: IPluginDetails = {
|
||||
PluginId: packageJson.name,
|
||||
Version: packageJson.version,
|
||||
PluginType: CordovaProjectHelper.CORE_PLUGIN_LIST.indexOf(pluginId) >= 0 ? 'Core' : 'Npm'
|
||||
PluginType: CordovaProjectHelper.CORE_PLUGIN_LIST.indexOf(pluginId) >= 0 ? "Core" : "Npm",
|
||||
};
|
||||
|
||||
return details;
|
||||
|
@ -213,7 +213,7 @@ export class CordovaProjectHelper {
|
|||
while (!CordovaProjectHelper.existsSync(path.join(projectRoot, CordovaProjectHelper.CONFIG_XML_FILENAME))
|
||||
&& !CordovaProjectHelper.existsSync(path.join(projectRoot, CordovaProjectHelper.CONFIG_IONIC_FILENAME))) {
|
||||
// Navigate up one level until either config.xml is found
|
||||
parentPath = path.resolve(projectRoot, '..');
|
||||
parentPath = path.resolve(projectRoot, "..");
|
||||
if (parentPath !== projectRoot) {
|
||||
projectRoot = parentPath;
|
||||
} else {
|
||||
|
@ -287,11 +287,11 @@ export class CordovaProjectHelper {
|
|||
* Helper function to determine whether the project is an Ionic 2 project or no. NOTE: we currently rely on "ionic.config.js" file, which may change as Ionic 2 continues development.
|
||||
*/
|
||||
public static isIonic2Project(projectRoot: string): boolean {
|
||||
const packageJsonPath = path.join(projectRoot, 'package.json');
|
||||
const packageJsonPath = path.join(projectRoot, "package.json");
|
||||
if (!fs.existsSync(packageJsonPath)) {
|
||||
return false;
|
||||
}
|
||||
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
||||
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
||||
const dependencies = packageJson.dependencies || {};
|
||||
const devDependencies = packageJson.devDependencies || {};
|
||||
const highestNotSupportedIonic2BetaVersion = "2.0.0-beta.9";
|
||||
|
@ -299,7 +299,7 @@ export class CordovaProjectHelper {
|
|||
const ionicVersion = dependencies["ionic-angular"];
|
||||
|
||||
// Assuming for now that latest version is > 3
|
||||
if (ionicVersion === 'latest' || ionicVersion === 'nightly') {
|
||||
if (ionicVersion === "latest" || ionicVersion === "nightly") {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -320,35 +320,35 @@ export class CordovaProjectHelper {
|
|||
* manifest and can be considered as a typescript project.
|
||||
*/
|
||||
public static isTypescriptProject(projectRoot: string): boolean {
|
||||
return fs.existsSync(path.resolve(projectRoot, 'tsconfig.json'));
|
||||
return fs.existsSync(path.resolve(projectRoot, "tsconfig.json"));
|
||||
}
|
||||
|
||||
public static getCliCommand(fsPath: string) {
|
||||
const cliName = CordovaProjectHelper.isIonicProject(fsPath) ? 'ionic' : 'cordova';
|
||||
const commandExtension = os.platform() === 'win32' ? '.cmd' : '';
|
||||
const cliName = CordovaProjectHelper.isIonicProject(fsPath) ? "ionic" : "cordova";
|
||||
const commandExtension = os.platform() === "win32" ? ".cmd" : "";
|
||||
const command = cliName + commandExtension;
|
||||
return command;
|
||||
}
|
||||
|
||||
public static getIonicCliVersion(fsPath: string): string {
|
||||
const command = CordovaProjectHelper.getCliCommand(fsPath);
|
||||
const ionicInfo = child_process.spawnSync(command, ['-v', '--quiet'], {
|
||||
const ionicInfo = child_process.spawnSync(command, ["-v", "--quiet"], {
|
||||
cwd: fsPath,
|
||||
env: {
|
||||
...process.env,
|
||||
CI: "Hack to disable Ionic autoupdate prompt"
|
||||
}
|
||||
CI: "Hack to disable Ionic autoupdate prompt",
|
||||
},
|
||||
});
|
||||
// A warning might appear on second line
|
||||
return ionicInfo.stdout.toString().split('\n')[0].trim();
|
||||
return ionicInfo.stdout.toString().split("\n")[0].trim();
|
||||
}
|
||||
|
||||
public static isIonicCliVersionGte3(fsPath: string): boolean {
|
||||
try {
|
||||
const ionicVersion = CordovaProjectHelper.getIonicCliVersion(fsPath);
|
||||
return semver.gte(ionicVersion, '3.0.0');
|
||||
return semver.gte(ionicVersion, "3.0.0");
|
||||
} catch (err) {
|
||||
console.error('Error while detecting Ionic CLI version', err);
|
||||
console.error("Error while detecting Ionic CLI version", err);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
||||
import {Telemetry} from './telemetry';
|
||||
import {TelemetryGenerator, TelemetryHelper} from './telemetryHelper';
|
||||
import {Telemetry} from "./telemetry";
|
||||
import {TelemetryGenerator, TelemetryHelper} from "./telemetryHelper";
|
||||
|
||||
/**
|
||||
* This class is a telemetry wrapper compatible with cordova-simulate's telemetry. Cordova-simulate expects an object with a sendTelemetry() function, and calls it every time there is a
|
||||
|
@ -10,7 +10,7 @@ import {TelemetryGenerator, TelemetryHelper} from './telemetryHelper';
|
|||
*/
|
||||
export class CordovaSimulateTelemetry {
|
||||
public sendTelemetry(eventName: string, props: Telemetry.ITelemetryProperties, piiProps: Telemetry.ITelemetryProperties): void {
|
||||
let fullEventName = 'cordova-simulate-' + eventName;
|
||||
let fullEventName = "cordova-simulate-" + eventName;
|
||||
let generator = new TelemetryGenerator(fullEventName);
|
||||
let telemetryEvent = new Telemetry.TelemetryEvent(fullEventName);
|
||||
|
||||
|
|
|
@ -1,20 +1,19 @@
|
|||
// 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 vscode from "vscode";
|
||||
|
||||
const channels: { [channelName: string]: OutputChannelLogger } = {};
|
||||
|
||||
export class OutputChannelLogger {
|
||||
public static MAIN_CHANNEL_NAME: string = 'Cordova Tools';
|
||||
public static MAIN_CHANNEL_NAME: string = "Cordova Tools";
|
||||
private outputChannel: vscode.OutputChannel;
|
||||
|
||||
private static purify(message: string): string {
|
||||
return message
|
||||
.toString()
|
||||
.replace(/\u001b/g, '')
|
||||
.replace(/\[2K\[G/g, '') // Erasing `[2K[G` artifacts from output
|
||||
.replace(/\[\d+m/g, ''); // Erasing "colors" from output
|
||||
constructor(public readonly channelName: string, lazy: boolean = false, private preserveFocus: boolean = false) {
|
||||
if (!lazy) {
|
||||
this.channel = vscode.window.createOutputChannel(this.channelName);
|
||||
this.channel.show(this.preserveFocus);
|
||||
}
|
||||
}
|
||||
|
||||
public static disposeChannel(channelName: string): void {
|
||||
|
@ -36,11 +35,12 @@ export class OutputChannelLogger {
|
|||
return channels[channelName];
|
||||
}
|
||||
|
||||
constructor(public readonly channelName: string, lazy: boolean = false, private preserveFocus: boolean = false) {
|
||||
if (!lazy) {
|
||||
this.channel = vscode.window.createOutputChannel(this.channelName);
|
||||
this.channel.show(this.preserveFocus);
|
||||
}
|
||||
private static purify(message: string): string {
|
||||
return message
|
||||
.toString()
|
||||
.replace(/\u001b/g, "")
|
||||
.replace(/\[2K\[G/g, "") // Erasing `[2K[G` artifacts from output
|
||||
.replace(/\[\d+m/g, ""); // Erasing "colors" from output
|
||||
}
|
||||
|
||||
public log(message: string): void {
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for details.
|
||||
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import * as os from "os";
|
||||
import * as path from "path";
|
||||
|
||||
export function settingsHome(): string {
|
||||
switch (os.platform()) {
|
||||
case 'win32':
|
||||
return path.join(process.env['APPDATA'], 'vscode-cordova');
|
||||
case 'darwin':
|
||||
case 'linux':
|
||||
return path.join(process.env['HOME'], '.vscode-cordova');
|
||||
case "win32":
|
||||
return path.join(process.env["APPDATA"], "vscode-cordova");
|
||||
case "darwin":
|
||||
case "linux":
|
||||
return path.join(process.env["HOME"], ".vscode-cordova");
|
||||
default:
|
||||
throw new Error('UnexpectedPlatform');
|
||||
throw new Error("UnexpectedPlatform");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,39 +3,234 @@
|
|||
|
||||
/// <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 Q from 'q';
|
||||
import * as readline from 'readline';
|
||||
import * as winreg from 'winreg';
|
||||
import * as crypto from "crypto";
|
||||
import * as fs from "fs";
|
||||
import * as os from "os";
|
||||
import * as path from "path";
|
||||
import * as Q from "q";
|
||||
import * as winreg from "winreg";
|
||||
|
||||
import {
|
||||
ExtensionMessage,
|
||||
ExtensionMessageSender
|
||||
} from '../common/extensionMessaging';
|
||||
} from "../common/extensionMessaging";
|
||||
|
||||
import { settingsHome } from './settingsHelper'
|
||||
import { settingsHome } from "./settingsHelper";
|
||||
|
||||
/**
|
||||
* Telemetry module specialized for vscode integration.
|
||||
*/
|
||||
export module Telemetry {
|
||||
export var appName: string;
|
||||
export var isOptedIn: boolean = false;
|
||||
export var reporter: ITelemetryReporter;
|
||||
export var reporterDictionary: { [key: string]: ITelemetryReporter } = {};
|
||||
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 extensionMessageSender: ExtensionMessageSender;
|
||||
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;
|
||||
this.extensionMessageSender = new ExtensionMessageSender(projectRoot);
|
||||
}
|
||||
|
||||
public sendTelemetryEvent(eventName: string, properties?: ITelemetryEventProperties, measures?: ITelemetryEventMeasures) {
|
||||
this.extensionMessageSender.sendMessage(ExtensionMessage.SEND_TELEMETRY, [this.extensionId, this.extensionVersion, this.appInsightsKey, eventName, properties, measures])
|
||||
.catch(function () { })
|
||||
.done();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
public static initDeferred: Q.Deferred<any> = Q.defer<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 init(appVersion: string, initOptions: ITelemetryInitOptions): Q.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): Q.Promise<string> {
|
||||
let deferred: Q.Deferred<string> = Q.defer<string>();
|
||||
let regKey = new winreg({
|
||||
hive: hive,
|
||||
key: key,
|
||||
});
|
||||
regKey.get(value, function (err: any, itemValue: winreg.RegistryItem) {
|
||||
if (err) {
|
||||
// Fail gracefully by returning null if there was an error.
|
||||
deferred.resolve(null);
|
||||
} else {
|
||||
deferred.resolve(itemValue.value);
|
||||
}
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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): Q.Promise<any> {
|
||||
let uniqueId: string;
|
||||
if (os.platform() === "win32") {
|
||||
return TelemetryUtils.getRegistryValue(TelemetryUtils.REGISTRY_SQMCLIENT_NODE, regValue, regHive)
|
||||
.then(function (id: string): Q.Promise<string> {
|
||||
if (id) {
|
||||
uniqueId = id.replace(/[{}]/g, "");
|
||||
return Q.resolve(uniqueId);
|
||||
} else {
|
||||
return Q.resolve(fallback());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return Q.resolve(fallback());
|
||||
}
|
||||
}
|
||||
|
||||
private static getUserId(): Q.Promise<string> {
|
||||
let userId: string = TelemetryUtils.telemetrySettings.userId;
|
||||
if (!userId) {
|
||||
return TelemetryUtils.getUniqueId(TelemetryUtils.REGISTRY_USERID_VALUE, winreg.HKCU, TelemetryUtils.generateGuid)
|
||||
.then(function (id: string): Q.Promise<string> {
|
||||
TelemetryUtils.telemetrySettings.userId = id;
|
||||
return Q.resolve(id);
|
||||
});
|
||||
} else {
|
||||
TelemetryUtils.telemetrySettings.userId = userId;
|
||||
return Q.resolve(userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TelemetryEvent represents a basic telemetry data point
|
||||
*/
|
||||
export class TelemetryEvent {
|
||||
private static PII_HASH_KEY: string = '959069c9-9e93-4fa1-bf16-3f8120d7db0c';
|
||||
private static PII_HASH_KEY: string = "959069c9-9e93-4fa1-bf16-3f8120d7db0c";
|
||||
public name: string;
|
||||
public properties: ITelemetryProperties;
|
||||
private eventId: string;
|
||||
|
@ -48,16 +243,16 @@ export module Telemetry {
|
|||
}
|
||||
|
||||
public setPiiProperty(name: string, value: string): void {
|
||||
var hmac: any = crypto.createHmac('sha256', new Buffer(TelemetryEvent.PII_HASH_KEY, 'utf8'));
|
||||
var hashedValue: any = hmac.update(value).digest('hex');
|
||||
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;
|
||||
this.properties[name + ".nothashed"] = value;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export interface ITelemetryInitOptions {
|
||||
isExtensionProcess: boolean;
|
||||
|
@ -80,11 +275,11 @@ export module Telemetry {
|
|||
|
||||
try {
|
||||
if (Telemetry.reporter) {
|
||||
var properties: ITelemetryEventProperties = {};
|
||||
var measures: ITelemetryEventMeasures = {};
|
||||
let properties: ITelemetryEventProperties = {};
|
||||
let measures: ITelemetryEventMeasures = {};
|
||||
|
||||
Object.keys(event.properties || {}).forEach(function (key: string) {
|
||||
var propertyValue = event.properties[key];
|
||||
let propertyValue = event.properties[key];
|
||||
|
||||
switch (typeof propertyValue) {
|
||||
case "string":
|
||||
|
@ -130,181 +325,6 @@ export module Telemetry {
|
|||
userType?: string;
|
||||
}
|
||||
|
||||
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;
|
||||
public static initDeferred: Q.Deferred<any> = Q.defer<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 init(appVersion: string, initOptions: ITelemetryInitOptions): Q.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 {
|
||||
var 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)
|
||||
var oct: string = '';
|
||||
var tmp: number;
|
||||
/* tslint:disable:no-bitwise */
|
||||
for (var 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'
|
||||
var 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 {
|
||||
var 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') {
|
||||
var 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): Q.Promise<string> {
|
||||
var deferred: Q.Deferred<string> = Q.defer<string>();
|
||||
var regKey = new winreg({
|
||||
hive: hive,
|
||||
key: key
|
||||
});
|
||||
regKey.get(value, function (err: any, itemValue: winreg.RegistryItem) {
|
||||
if (err) {
|
||||
// Fail gracefully by returning null if there was an error.
|
||||
deferred.resolve(null);
|
||||
}
|
||||
else {
|
||||
deferred.resolve(itemValue.value);
|
||||
}
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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): Q.Promise<any> {
|
||||
var uniqueId: string;
|
||||
var deferred: Q.Deferred<string> = Q.defer<string>();
|
||||
if (os.platform() === 'win32') {
|
||||
return TelemetryUtils.getRegistryValue(TelemetryUtils.REGISTRY_SQMCLIENT_NODE, regValue, regHive)
|
||||
.then(function (id: string): Q.Promise<string> {
|
||||
if (id) {
|
||||
uniqueId = id.replace(/[{}]/g, '');
|
||||
return Q.resolve(uniqueId);
|
||||
} else {
|
||||
return Q.resolve(fallback());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return Q.resolve(fallback());
|
||||
}
|
||||
}
|
||||
|
||||
private static getUserId(): Q.Promise<string> {
|
||||
var userId: string = TelemetryUtils.telemetrySettings.userId;
|
||||
if (!userId) {
|
||||
return TelemetryUtils.getUniqueId(TelemetryUtils.REGISTRY_USERID_VALUE, winreg.HKCU, TelemetryUtils.generateGuid)
|
||||
.then(function (id: string): Q.Promise<string> {
|
||||
TelemetryUtils.telemetrySettings.userId = id;
|
||||
return Q.resolve(id);
|
||||
});
|
||||
} else {
|
||||
TelemetryUtils.telemetrySettings.userId = userId;
|
||||
return Q.resolve(userId);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export interface ITelemetryEventProperties {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
@ -313,39 +333,15 @@ export module Telemetry {
|
|||
[key: string]: number;
|
||||
}
|
||||
|
||||
export interface ITelemetryReporter {
|
||||
sendTelemetryEvent(eventName: string, properties?: ITelemetryEventProperties, measures?: ITelemetryEventMeasures);
|
||||
}
|
||||
|
||||
class ExtensionTelemetryReporter implements ITelemetryReporter {
|
||||
private extensionMessageSender: ExtensionMessageSender;
|
||||
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;
|
||||
this.extensionMessageSender = new ExtensionMessageSender(projectRoot);
|
||||
}
|
||||
|
||||
sendTelemetryEvent(eventName: string, properties?: ITelemetryEventProperties, measures?: ITelemetryEventMeasures) {
|
||||
this.extensionMessageSender.sendMessage(ExtensionMessage.SEND_TELEMETRY, [this.extensionId, this.extensionVersion, this.appInsightsKey, eventName, properties, measures])
|
||||
.catch(function () { })
|
||||
.done();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
let TelemetryReporter = require("vscode-extension-telemetry").default;
|
||||
Telemetry.reporterDictionary[extensionId] = new TelemetryReporter(extensionId, extensionVersion, appInsightsKey);
|
||||
reporter = Telemetry.reporterDictionary[extensionId];
|
||||
}
|
||||
|
||||
reporter.sendTelemetryEvent(eventName, properties, measures);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for details.
|
||||
|
||||
import {CordovaProjectHelper, IPluginDetails} from './cordovaProjectHelper';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as Q from 'q';
|
||||
import {Telemetry} from './telemetry';
|
||||
import {IProjectType} from './cordovaProjectHelper';
|
||||
/* tslint:disable:no-use-before-declare */
|
||||
import {CordovaProjectHelper, IPluginDetails} from "./cordovaProjectHelper";
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import * as Q from "q";
|
||||
import {Telemetry} from "./telemetry";
|
||||
import {IProjectType} from "./cordovaProjectHelper";
|
||||
|
||||
export interface ITelemetryPropertyInfo {
|
||||
value: any;
|
||||
|
@ -33,7 +34,7 @@ export abstract class TelemetryGeneratorBase {
|
|||
protected telemetryProperties: ICommandTelemetryProperties = {};
|
||||
private componentName: string;
|
||||
private currentStepStartTime: [number, number];
|
||||
private currentStep: string = 'initialStep';
|
||||
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) {
|
||||
|
@ -41,8 +42,6 @@ export abstract class TelemetryGeneratorBase {
|
|||
this.currentStepStartTime = process.hrtime();
|
||||
}
|
||||
|
||||
protected abstract sendTelemetryEvent(telemetryEvent: Telemetry.TelemetryEvent): void;
|
||||
|
||||
public add(baseName: string, value: any, isPii: boolean): TelemetryGeneratorBase {
|
||||
return this.addWithPiiEvaluator(baseName, value, () => isPii);
|
||||
}
|
||||
|
@ -55,7 +54,7 @@ export abstract class TelemetryGeneratorBase {
|
|||
try {
|
||||
if (Array.isArray(value)) {
|
||||
this.addArray(baseName, <any[]>value, piiEvaluator);
|
||||
} else if (!!value && (typeof value === 'object' || typeof value === 'function')) {
|
||||
} else if (!!value && (typeof value === "object" || typeof value === "function")) {
|
||||
this.addHash(baseName, <IDictionary<any>>value, piiEvaluator);
|
||||
} else {
|
||||
this.addString(baseName, String(value), piiEvaluator);
|
||||
|
@ -63,24 +62,24 @@ export abstract class TelemetryGeneratorBase {
|
|||
} 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);
|
||||
this.addString("telemetryGenerationError." + baseName, String(error), () => false);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public addError(error: Error): TelemetryGeneratorBase {
|
||||
this.add('error.message' + ++this.errorIndex, error.message, /*isPii*/ true);
|
||||
var errorWithErrorCode: IHasErrorCode = <IHasErrorCode><Object>error;
|
||||
this.add("error.message" + ++this.errorIndex, error.message, /*isPii*/ true);
|
||||
let errorWithErrorCode: IHasErrorCode = <IHasErrorCode><Object>error;
|
||||
if (errorWithErrorCode.errorCode) {
|
||||
this.add('error.code' + this.errorIndex, errorWithErrorCode.errorCode, /*isPii*/ false);
|
||||
this.add("error.code" + this.errorIndex, errorWithErrorCode.errorCode, /*isPii*/ false);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public time<T>(name: string, codeToMeasure: { (): Thenable<T> }): Q.Promise<T> {
|
||||
var startTime: [number, number] = process.hrtime();
|
||||
let startTime: [number, number] = process.hrtime();
|
||||
return Q(codeToMeasure()).finally(() => this.finishTime(name, startTime)).fail((reason: any): Q.Promise<T> => {
|
||||
this.addError(reason);
|
||||
throw reason;
|
||||
|
@ -101,7 +100,7 @@ export abstract class TelemetryGeneratorBase {
|
|||
|
||||
public send(): void {
|
||||
if (this.currentStep) {
|
||||
this.add('lastStepExecuted', this.currentStep, /*isPii*/ false);
|
||||
this.add("lastStepExecuted", this.currentStep, /*isPii*/ false);
|
||||
}
|
||||
|
||||
this.step(null); // Send the last step
|
||||
|
@ -111,22 +110,24 @@ export abstract class TelemetryGeneratorBase {
|
|||
return this.telemetryProperties;
|
||||
}
|
||||
|
||||
protected abstract sendTelemetryEvent(telemetryEvent: Telemetry.TelemetryEvent): void;
|
||||
|
||||
private sendCurrentStep(): void {
|
||||
this.add('step', this.currentStep, /*isPii*/ false);
|
||||
var telemetryEvent: Telemetry.TelemetryEvent = new Telemetry.TelemetryEvent(this.componentName);
|
||||
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
|
||||
var elementIndex: number = 1; // We send telemetry properties in a one-based index
|
||||
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));
|
||||
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 {
|
||||
|
@ -134,13 +135,13 @@ export abstract class TelemetryGeneratorBase {
|
|||
}
|
||||
|
||||
private combine(...components: string[]): string {
|
||||
var nonNullComponents: string[] = components.filter((component: string) => component !== null);
|
||||
return nonNullComponents.join('.');
|
||||
let nonNullComponents: string[] = components.filter((component: string) => component !== null);
|
||||
return nonNullComponents.join(".");
|
||||
}
|
||||
|
||||
private finishTime(name: string, startTime: [number, number]): void {
|
||||
var endTime: [number, number] = process.hrtime(startTime);
|
||||
this.add(this.combine(name, 'time'), String(endTime[0] * 1000 + endTime[1] / 1000000), /*isPii*/ false);
|
||||
let endTime: [number, number] = process.hrtime(startTime);
|
||||
this.add(this.combine(name, "time"), String(endTime[0] * 1000 + endTime[1] / 1000000), /*isPii*/ false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -150,15 +151,6 @@ export class TelemetryGenerator extends TelemetryGeneratorBase {
|
|||
}
|
||||
}
|
||||
|
||||
export interface ISimulateTelemetryProperties {
|
||||
platform?: string;
|
||||
target: string;
|
||||
port: number;
|
||||
simulatePort?: number;
|
||||
livereload?: boolean;
|
||||
forceprepare?: boolean;
|
||||
}
|
||||
|
||||
export class TelemetryHelper {
|
||||
public static createTelemetryEvent(eventName: string): Telemetry.TelemetryEvent {
|
||||
return new Telemetry.TelemetryEvent(eventName);
|
||||
|
@ -166,17 +158,17 @@ export class TelemetryHelper {
|
|||
|
||||
public static determineProjectTypes(projectRoot: string): Q.Promise<IProjectType> {
|
||||
let promiseExists = (file: string) => {
|
||||
var deferred = Q.defer<boolean>();
|
||||
let deferred = Q.defer<boolean>();
|
||||
fs.exists(file, (exist: boolean) => deferred.resolve(exist));
|
||||
return deferred.promise;
|
||||
}
|
||||
};
|
||||
|
||||
let isIonic1 = CordovaProjectHelper.isIonic1Project(projectRoot);
|
||||
let isIonic2 = CordovaProjectHelper.isIonic2Project(projectRoot);
|
||||
let meteor = promiseExists(path.join(projectRoot, '.meteor'));
|
||||
let mobilefirst = promiseExists(path.join(projectRoot, '.project'));
|
||||
let phonegap = promiseExists(path.join(projectRoot, 'www', 'res', '.pgbomit'));
|
||||
let cordova = promiseExists(path.join(projectRoot, 'config.xml'));
|
||||
let meteor = promiseExists(path.join(projectRoot, ".meteor"));
|
||||
let mobilefirst = promiseExists(path.join(projectRoot, ".project"));
|
||||
let phonegap = promiseExists(path.join(projectRoot, "www", "res", ".pgbomit"));
|
||||
let cordova = promiseExists(path.join(projectRoot, "config.xml"));
|
||||
return Q.all([meteor, mobilefirst, phonegap, cordova])
|
||||
.spread((isMeteor: boolean, isMobilefirst: boolean, isPhonegap: boolean, isCordova: boolean) => {
|
||||
return { ionic: isIonic1, ionic2: isIonic2, meteor: isMeteor, mobilefirst: isMobilefirst, phonegap: isPhonegap, cordova: isCordova };
|
||||
|
@ -206,18 +198,18 @@ export class TelemetryHelper {
|
|||
}
|
||||
|
||||
public static generate<T>(name: string, codeGeneratingTelemetry: { (telemetry: TelemetryGenerator): Thenable<T> }): Q.Promise<T> {
|
||||
var generator: TelemetryGenerator = new TelemetryGenerator(name);
|
||||
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
|
||||
var pluginFilePath = path.join(projectRoot, ".vscode", "plugins.json");
|
||||
var pluginFileJson : any;
|
||||
let pluginFilePath = path.join(projectRoot, ".vscode", "plugins.json");
|
||||
let pluginFileJson: any;
|
||||
|
||||
if (CordovaProjectHelper.existsSync(pluginFilePath)) {
|
||||
try {
|
||||
let pluginFileJsonContents = fs.readFileSync(pluginFilePath, 'utf8').toString();
|
||||
let pluginFileJsonContents = fs.readFileSync(pluginFilePath, "utf8").toString();
|
||||
pluginFileJson = JSON.parse(pluginFileJsonContents);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
@ -225,14 +217,14 @@ export class TelemetryHelper {
|
|||
}
|
||||
|
||||
// Get list of plugins in pluginsList but not in previousPlugins
|
||||
var pluginsFileList : string[] = new Array<string>();
|
||||
let pluginsFileList: string[] = new Array<string>();
|
||||
if (pluginFileJson && pluginFileJson.plugins) {
|
||||
pluginsFileList = pluginFileJson.plugins;
|
||||
} else {
|
||||
pluginFileJson = new Object();
|
||||
}
|
||||
|
||||
var newPlugins : string[] = new Array<string>();
|
||||
let newPlugins: string[] = new Array<string>();
|
||||
pluginsList.forEach(plugin => {
|
||||
if (pluginsFileList.indexOf(plugin) < 0) {
|
||||
newPlugins.push(plugin);
|
||||
|
@ -241,7 +233,7 @@ export class TelemetryHelper {
|
|||
});
|
||||
|
||||
// If none, return
|
||||
if (newPlugins.length == 0) {
|
||||
if (newPlugins.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -250,12 +242,12 @@ export class TelemetryHelper {
|
|||
newPlugins.map(pluginName => CordovaProjectHelper.getInstalledPluginDetails(projectRoot, pluginName))
|
||||
.filter(detail => !!detail);
|
||||
|
||||
let pluginEvent = new Telemetry.TelemetryEvent('plugins', { plugins: JSON.stringify(pluginDetails) });
|
||||
let pluginEvent = new Telemetry.TelemetryEvent("plugins", { plugins: JSON.stringify(pluginDetails) });
|
||||
Telemetry.send(pluginEvent);
|
||||
|
||||
// Write out new list of previousPlugins
|
||||
pluginFileJson.plugins = pluginsFileList;
|
||||
fs.writeFileSync(pluginFilePath, JSON.stringify(pluginFileJson), 'utf8');
|
||||
fs.writeFileSync(pluginFilePath, JSON.stringify(pluginFileJson), "utf8");
|
||||
}
|
||||
|
||||
private static setTelemetryEventProperty(event: Telemetry.TelemetryEvent, propertyName: string, propertyValue: string, isPii: boolean): void {
|
||||
|
@ -267,8 +259,18 @@ export class TelemetryHelper {
|
|||
}
|
||||
|
||||
private static addMultiValuedTelemetryEventProperty(event: Telemetry.TelemetryEvent, propertyName: string, propertyValue: string[], isPii: boolean): void {
|
||||
for (var i: number = 0; i < propertyValue.length; i++) {
|
||||
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;
|
||||
forceprepare?: boolean;
|
||||
}
|
||||
/* tslint:enable */
|
||||
|
|
|
@ -1,27 +1,17 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for details.
|
||||
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import * as Q from 'q';
|
||||
import {TelemetryHelper} from './telemetryHelper';
|
||||
import {CordovaProjectHelper} from './cordovaProjectHelper';
|
||||
import * as path from "path";
|
||||
import * as fs from "fs";
|
||||
import * as Q from "q";
|
||||
import {TelemetryHelper} from "./telemetryHelper";
|
||||
import {CordovaProjectHelper} from "./cordovaProjectHelper";
|
||||
|
||||
export class TsdHelper {
|
||||
private static CORDOVA_TYPINGS_FOLDERNAME = "CordovaTypings";
|
||||
private static CORDOVA_TYPINGS_PATH = path.resolve(__dirname, "..", "..", "..", TsdHelper.CORDOVA_TYPINGS_FOLDERNAME);
|
||||
private static USER_TYPINGS_FOLDERNAME = "typings";
|
||||
|
||||
private static installTypeDefinitionFile(src: string, dest: string): Q.Promise<any> {
|
||||
// Ensure that the parent folder exits; if not, create the hierarchy of directories
|
||||
let parentFolder = path.resolve(dest, "..");
|
||||
if (!CordovaProjectHelper.existsSync(parentFolder)) {
|
||||
CordovaProjectHelper.makeDirectoryRecursive(parentFolder);
|
||||
}
|
||||
|
||||
return CordovaProjectHelper.copyFile(src, dest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to install type defintion files for Cordova plugins and Ionic projects.
|
||||
* {typingsFolderPath} - the parent folder where the type definitions need to be installed
|
||||
|
@ -31,8 +21,8 @@ export class TsdHelper {
|
|||
public static installTypings(typingsFolderPath: string, typeDefsPath: string[], projectRoot?: string): void {
|
||||
let installedTypeDefs: string[] = [];
|
||||
|
||||
TelemetryHelper.generate('addTypings', (generator) => {
|
||||
generator.add('addedTypeDefinitions', typeDefsPath, false);
|
||||
TelemetryHelper.generate("addTypings", (generator) => {
|
||||
generator.add("addedTypeDefinitions", typeDefsPath, false);
|
||||
return Q.all(typeDefsPath.map((relativePath: string): Q.Promise<any> => {
|
||||
let src = path.resolve(TsdHelper.CORDOVA_TYPINGS_PATH, relativePath);
|
||||
let dest = path.resolve(typingsFolderPath, relativePath);
|
||||
|
@ -62,14 +52,14 @@ export class TsdHelper {
|
|||
if (installedTypeDefs.length === 0) return;
|
||||
|
||||
let typingsFolder = path.resolve(projectRoot, TsdHelper.USER_TYPINGS_FOLDERNAME);
|
||||
let indexFile = path.resolve(typingsFolder, 'cordova-typings.d.ts');
|
||||
let 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 references = CordovaProjectHelper.existsSync(indexFile) ? fs.readFileSync(indexFile, "utf8") : "";
|
||||
let referencesToAdd = installedTypeDefs
|
||||
// Do not add references to typedefs that are not exist,
|
||||
// this rarely happens if typedef file fails to copy
|
||||
|
@ -81,7 +71,7 @@ export class TsdHelper {
|
|||
|
||||
if (referencesToAdd.length === 0) return;
|
||||
|
||||
fs.writeFileSync(indexFile, [references].concat(referencesToAdd).join('\n'), 'utf8');
|
||||
fs.writeFileSync(indexFile, [references].concat(referencesToAdd).join("\n"), "utf8");
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -95,9 +85,9 @@ export class TsdHelper {
|
|||
});
|
||||
|
||||
let references = [];
|
||||
let indexFile = path.resolve(projectRoot, TsdHelper.USER_TYPINGS_FOLDERNAME, 'cordova-typings.d.ts');
|
||||
let indexFile = path.resolve(projectRoot, TsdHelper.USER_TYPINGS_FOLDERNAME, "cordova-typings.d.ts");
|
||||
try {
|
||||
references = fs.readFileSync(indexFile, 'utf8').split('\n');
|
||||
references = fs.readFileSync(indexFile, "utf8").split("\n");
|
||||
} catch (e) {
|
||||
// We failed to read index file - it might not exist of
|
||||
// blocked by other process - can't do anything here
|
||||
|
@ -111,6 +101,16 @@ export class TsdHelper {
|
|||
referencesToPersist.length === 0 ?
|
||||
fs.unlink(indexFile) :
|
||||
// Write filtered references back to index file
|
||||
fs.writeFileSync(indexFile, referencesToPersist.join('\n'), 'utf8');
|
||||
fs.writeFileSync(indexFile, referencesToPersist.join("\n"), "utf8");
|
||||
}
|
||||
|
||||
private static installTypeDefinitionFile(src: string, dest: string): Q.Promise<any> {
|
||||
// Ensure that the parent folder exits; if not, create the hierarchy of directories
|
||||
let parentFolder = path.resolve(dest, "..");
|
||||
if (!CordovaProjectHelper.existsSync(parentFolder)) {
|
||||
CordovaProjectHelper.makeDirectoryRecursive(parentFolder);
|
||||
}
|
||||
|
||||
return CordovaProjectHelper.copyFile(src, dest);
|
||||
}
|
||||
}
|
||||
|
|
30
tslint.json
30
tslint.json
|
@ -19,16 +19,12 @@
|
|||
],
|
||||
"jsdoc-format": true,
|
||||
"label-position": true,
|
||||
"label-undefined": true,
|
||||
"max-line-length": [
|
||||
false,
|
||||
140
|
||||
],
|
||||
"member-access": true,
|
||||
"member-ordering": [
|
||||
true,
|
||||
"variables-before-functions"
|
||||
],
|
||||
"member-ordering": [true, {"order": "fields-first"}],
|
||||
"no-any": false,
|
||||
"no-arg": true,
|
||||
"no-bitwise": true,
|
||||
|
@ -43,25 +39,22 @@
|
|||
],
|
||||
"no-construct": true,
|
||||
"no-debugger": true,
|
||||
"no-duplicate-key": true,
|
||||
"no-duplicate-variable": true,
|
||||
"no-empty": false,
|
||||
"no-eval": true,
|
||||
"no-inferrable-types": false,
|
||||
"no-internal-module": true,
|
||||
"no-keyword-named-variables": true,
|
||||
"no-internal-module": false,
|
||||
"no-require-imports": false,
|
||||
"no-shadowed-variable": true,
|
||||
"no-shadowed-variable": false,
|
||||
"no-string-literal": false,
|
||||
"no-switch-case-fall-through": true,
|
||||
"no-switch-case-fall-through": false,
|
||||
"no-trailing-whitespace": true,
|
||||
"no-unreachable": true,
|
||||
"no-unused-expression": true,
|
||||
"no-unused-variable": true,
|
||||
"no-use-before-declare": true,
|
||||
"no-var-keyword": true,
|
||||
"no-var-requires": true,
|
||||
"object-literal-sort-keys": true,
|
||||
"no-var-requires": false,
|
||||
"object-literal-sort-keys": false,
|
||||
"one-line": [
|
||||
true,
|
||||
"check-open-brace",
|
||||
|
@ -71,7 +64,7 @@
|
|||
],
|
||||
"quotemark": [
|
||||
true,
|
||||
"single"
|
||||
"double"
|
||||
],
|
||||
"radix": true,
|
||||
"semicolon": true,
|
||||
|
@ -79,7 +72,11 @@
|
|||
"trailing-comma": [
|
||||
true,
|
||||
{
|
||||
"multiline": "always",
|
||||
"multiline": {
|
||||
"objects": "always",
|
||||
"arrays": "always",
|
||||
"functions": "never"
|
||||
},
|
||||
"singleline": "never"
|
||||
}
|
||||
],
|
||||
|
@ -107,5 +104,4 @@
|
|||
"check-type"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Загрузка…
Ссылка в новой задаче