Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"menus": {
"commandPalette": [
{
"command": "salesforcedx-vscode-offline-app.configureLintingTools",
"command": "salesforcedx-vscode-offline-app.configure-linting-tools",
"when": "sfdx_project_opened"
}
]
Expand All @@ -53,7 +53,7 @@
"category": "%extension.commands.config-wizard.category%"
},
{
"command": "salesforcedx-vscode-offline-app.configureLintingTools",
"command": "salesforcedx-vscode-offline-app.configure-linting-tools",
"title": "%extension.commands.config-linting-tools.title%",
"category": "%extension.commands.salesforce-mobile-offline.category%"
}
Expand Down Expand Up @@ -98,8 +98,8 @@
"lint": "eslint src --ext ts",
"test": "node ./out/test/runTest.js",
"test-coverage": "node ./out/test/runTest.js --coverage",
"prettier:write": "prettier --write \"src/**/*.{ts, js}\" \"resources/instructions/**/*.html\"",
"prettier:verify": "prettier --list-different \"src/**/*.{ts, js}\" \"resources/instructions/**/*.html\"",
"prettier:write": "prettier --write \"src/**/*.{ts, js}\" \"resources/instructions/**/*.html\" \"src/lsp/server/**/*.{ts, js}\" \"src/lsp/client/**/*.{ts, js}\"",
"prettier:verify": "prettier --list-different \"src/**/*.{ts, js}\" \"resources/instructions/**/*.html\" \"src/lsp/server/**/*.{ts, js}\" \"src/lsp/client/**/*.{ts, js}\"",
"bundle:extension": "esbuild ./src/extension.ts --bundle --outdir=out/src --format=cjs --target=es2020 --platform=node --external:vscode --external:@salesforce/core --external:@oclif/core --external:@salesforce/lwc-dev-mobile-core --minify --sourcemap",
"vscode:prepublish": "npm run clean && npm run compile && npm run bundle:extension"
},
Expand Down
110 changes: 79 additions & 31 deletions src/commands/lint/configureLintingToolsCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,21 @@ import * as fs from 'fs';
import * as path from 'path';
import { WorkspaceUtils } from '../../utils/workspaceUtils';
import { JSON_INDENTATION_SPACES } from '../../utils/constants';

const configureLintingToolsCommand =
'salesforcedx-vscode-offline-app.configureLintingTools';
import { CoreExtensionService } from '../../services/CoreExtensionService';

const commandName = 'salesforcedx-vscode-offline-app.configure-linting-tools';

enum MetricEvents {
CONFIGURE_LINTING_TOOLS_COMMAND_STARTED = 'configure-linting-tools-command-started',
UPDATED_PACKAGE_JSON = 'updated-package-json',
UPDATED_ESLINTRC_JSON = 'updated-eslintrc-json',
ALREADY_CONFIGURED = 'already-configured',
LWC_FOLDER_DOES_NOT_EXIST = 'lwc-folder-does-not-exist',
PACKAGE_JSON_DOES_NOT_EXIST = 'package-json-does-not-exist',
ERROR_UPDATING_PACKAGE_JSON = 'error-updating-package-json',
ERROR_UPDATING_ESLINTRC_JSON = 'error-updating-eslintrc-json',
GENERAL_ERROR = 'general-error'
}

const config = workspace.getConfiguration();

Expand Down Expand Up @@ -66,19 +78,34 @@ enum MessageType {

export class ConfigureLintingToolsCommand {
static async configure(): Promise<boolean> {
const telemetryService = CoreExtensionService.getTelemetryService();

// Send marker to record that the command got executed.
telemetryService.sendCommandEvent(commandName, process.hrtime(), {
metricEvents: MetricEvents.CONFIGURE_LINTING_TOOLS_COMMAND_STARTED
});

try {
if (!WorkspaceUtils.lwcFolderExists()) {
await this.showMessage(
'The "force-app/main/default/lwc" folder does not exist in your project. This folder is required to create a configuration file for ESLint.'
);
return Promise.resolve(false);
const event = `${commandName}.${MetricEvents.LWC_FOLDER_DOES_NOT_EXIST}`;
const message =
'The "force-app/main/default/lwc" folder does not exist in your project. This folder is required to create a configuration file for ESLint.';

await this.showMessage(message);
telemetryService.sendException(event, message);

return false;
}

if (!WorkspaceUtils.packageJsonExists()) {
await this.showMessage(
'Your project does not contain a "package.json" specification. You must have a package specification to configure these ESLint packages and their dependencies in your project.'
);
return Promise.resolve(false);
const event = `${commandName}.${MetricEvents.PACKAGE_JSON_DOES_NOT_EXIST}`;
const message =
'Your project does not contain a "package.json" specification. You must have a package specification to configure these ESLint packages and their dependencies in your project.';

await this.showMessage(message);
telemetryService.sendException(event, message);

return false;
}

// Ask user to add eslint plugin
Expand All @@ -88,36 +115,52 @@ export class ConfigureLintingToolsCommand {
);

if (!result || result.title === l10n.t('No')) {
return Promise.resolve(false);
return false;
} else {
let modifiedDevDependencies = false;
try {
modifiedDevDependencies = this.updateDevDependencies();
} catch (error) {
await this.showMessage(
`Error updating package.json: ${error}`
);
return Promise.resolve(false);
const event = `${commandName}.${MetricEvents.ERROR_UPDATING_PACKAGE_JSON}`;
const message = `Error updating package.json: ${error}`;

await this.showMessage(message);
telemetryService.sendException(event, message);

return false;
}

let modifiedEslintrc = false;
try {
modifiedEslintrc = this.updateEslintrc();
} catch (error) {
await this.showMessage(
`Error updating .eslintrc.json: ${error}`
);
return Promise.resolve(false);
const event = `${commandName}.${MetricEvents.ERROR_UPDATING_ESLINTRC_JSON}`;
const message = `Error updating .eslintrc.json: ${error}`;

await this.showMessage(message);
telemetryService.sendException(event, message);

return false;
}

if (modifiedDevDependencies) {
telemetryService.sendCommandEvent(
commandName,
process.hrtime(),
{ metricEvents: MetricEvents.UPDATED_PACKAGE_JSON }
);
this.showMessage(
`Updated package.json to include offline linting packages and dependencies.`,
MessageType.InformationOk
);
}

if (modifiedEslintrc) {
telemetryService.sendCommandEvent(
commandName,
process.hrtime(),
{ metricEvents: MetricEvents.UPDATED_ESLINTRC_JSON }
);
this.showMessage(
`Updated .eslintrc.json to include recommended linting rules.`,
MessageType.InformationOk
Expand All @@ -132,19 +175,27 @@ export class ConfigureLintingToolsCommand {
}

if (!modifiedDevDependencies && !modifiedEslintrc) {
telemetryService.sendCommandEvent(
commandName,
process.hrtime(),
{ metricEvents: MetricEvents.ALREADY_CONFIGURED }
);
this.showMessage(
`All offline linting packages and dependencies are already configured in your project. No update has been made to package.json.`,
MessageType.InformationOk
);
}

return Promise.resolve(true);
return true;
}
} catch (error) {
await this.showMessage(
`There was an error trying to update either the offline linting dependencies or linting configuration: ${error}`
);
return Promise.resolve(false);
const event = `${commandName}.${MetricEvents.GENERAL_ERROR}`;
const message = `There was an error trying to update either the offline linting dependencies or linting configuration: ${error}`;

await this.showMessage(message);
telemetryService.sendException(event, message);

return false;
}
}

Expand Down Expand Up @@ -250,11 +301,8 @@ export class ConfigureLintingToolsCommand {
}

export function registerCommand(context: ExtensionContext) {
const disposable = commands.registerCommand(
configureLintingToolsCommand,
async () => {
await ConfigureLintingToolsCommand.configure();
}
);
const disposable = commands.registerCommand(commandName, async () => {
await ConfigureLintingToolsCommand.configure();
});
context.subscriptions.push(disposable);
}
7 changes: 7 additions & 0 deletions src/commands/wizard/configureProjectCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { Uri, WebviewPanel, commands, l10n, window } from 'vscode';
import * as process from 'process';
import { CommonUtils } from '@salesforce/lwc-dev-mobile-core/lib/common/CommonUtils';
import { InstructionsWebviewProvider } from '../../webviews/instructions';
import { wizardCommand } from './onboardingWizard';
import { CoreExtensionService } from '../../services/CoreExtensionService';

export type ProjectManagementChoiceAction = (panel?: WebviewPanel) => void;

Expand Down Expand Up @@ -162,6 +164,11 @@ export class ConfigureProjectCommand {
}

async configureProject(): Promise<string | undefined> {
const telemetryService = CoreExtensionService.getTelemetryService();

// Send marker to record that the command got executed.
telemetryService.sendCommandEvent(wizardCommand, process.hrtime());

return new Promise((resolve) => {
this.projectConfigurationProcessor.getProjectManagementChoice(
async (panel) => {
Expand Down
2 changes: 1 addition & 1 deletion src/commands/wizard/onboardingWizard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { AuthorizeCommand } from './authorizeCommand';
import { InstructionsWebviewProvider } from '../../webviews/instructions';
import { LwcGenerationCommand } from './lwcGenerationCommand';

const wizardCommand = 'salesforcedx-vscode-offline-app.onboardingWizard';
export const wizardCommand = 'salesforcedx-vscode-offline-app.onboardingWizard';
const onboardingWizardStateKey =
'salesforcedx-vscode-offline-app.onboardingWizard.projectCreationState';

Expand Down
2 changes: 1 addition & 1 deletion src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
export function activate(context: vscode.ExtensionContext) {
// We need to do this first in case any other services need access to those provided by the core extension
try {
CoreExtensionService.loadDependencies();
CoreExtensionService.loadDependencies(context);
} catch (err) {
console.error(err);
vscode.window.showErrorMessage(
Expand Down
65 changes: 62 additions & 3 deletions src/services/CoreExtensionService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
*/

import { extensions } from 'vscode';
import { ExtensionContext, extensions } from 'vscode';
import { satisfies, valid } from 'semver';
import type { CoreExtensionApi, WorkspaceContext } from '../types';
import type {
CoreExtensionApi,
WorkspaceContext,
SalesforceProjectConfig,
TelemetryService
} from '../types';
import {
CORE_EXTENSION_ID,
MINIMUM_REQUIRED_VERSION_CORE_EXTENSION
Expand All @@ -16,14 +21,18 @@ import {
const NOT_INITIALIZED_ERROR = 'CoreExtensionService not initialized';
const CORE_EXTENSION_NOT_FOUND = 'Core extension not found';
const WORKSPACE_CONTEXT_NOT_FOUND = 'Workspace Context not found';
const SALESFORCE_PROJECT_CONFIG_NOT_FOUND = 'SalesforceProjectConfig not found';
const TELEMETRY_SERVICE_NOT_FOUND = 'TelemetryService not found';
const coreExtensionMinRequiredVersionError =
'You are running an older version of the Salesforce CLI Integration VSCode Extension. Please update the Salesforce Extension pack and try again.';

export class CoreExtensionService {
private static initialized = false;
private static workspaceContext: WorkspaceContext;
private static salesforceProjectConfig: SalesforceProjectConfig;
private static telemetryService: TelemetryService;

static loadDependencies() {
static loadDependencies(context: ExtensionContext) {
if (!CoreExtensionService.initialized) {
const coreExtension = extensions.getExtension(CORE_EXTENSION_ID);
if (!coreExtension) {
Expand All @@ -45,6 +54,15 @@ export class CoreExtensionService {
coreExtensionApi?.services.WorkspaceContext
);

CoreExtensionService.initializeSalesforceProjectConfig(
coreExtensionApi?.services.SalesforceProjectConfig
);

CoreExtensionService.initializeTelemetryService(
coreExtensionApi?.services.TelemetryService,
context
);

CoreExtensionService.initialized = true;
}
}
Expand All @@ -59,6 +77,33 @@ export class CoreExtensionService {
workspaceContext.getInstance(false);
}

private static initializeSalesforceProjectConfig(
salesforceProjectConfig: SalesforceProjectConfig | undefined
) {
if (!salesforceProjectConfig) {
throw new Error(SALESFORCE_PROJECT_CONFIG_NOT_FOUND);
}
CoreExtensionService.salesforceProjectConfig = salesforceProjectConfig;
}

private static initializeTelemetryService(
telemetryService: TelemetryService | undefined,
context: ExtensionContext
) {
if (!telemetryService) {
throw new Error(TELEMETRY_SERVICE_NOT_FOUND);
}
const { aiKey, name, version } = context.extension.packageJSON;
CoreExtensionService.telemetryService =
telemetryService.getInstance(name);
CoreExtensionService.telemetryService.initializeService(
context,
name,
aiKey,
version
);
}

private static isAboveMinimumRequiredVersion(
minRequiredVersion: string,
actualVersion: string
Expand All @@ -80,4 +125,18 @@ export class CoreExtensionService {
}
throw new Error(NOT_INITIALIZED_ERROR);
}

static getSalesforceProjectConfig(): SalesforceProjectConfig {
if (CoreExtensionService.initialized) {
return CoreExtensionService.salesforceProjectConfig;
}
throw new Error(NOT_INITIALIZED_ERROR);
}

static getTelemetryService(): TelemetryService {
if (CoreExtensionService.initialized) {
return CoreExtensionService.telemetryService;
}
throw new Error(NOT_INITIALIZED_ERROR);
}
}
6 changes: 6 additions & 0 deletions src/types/CoreExtensionApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,16 @@
*/

import { WorkspaceContext } from './WorkspaceContext';
import { SalesforceProjectConfig } from './SalesforceProjectConfig';
import { TelemetryService } from './TelemetryService';

export interface CoreExtensionApi {
services: {
// eslint-disable-next-line @typescript-eslint/naming-convention
WorkspaceContext: WorkspaceContext;
// eslint-disable-next-line @typescript-eslint/naming-convention
SalesforceProjectConfig: SalesforceProjectConfig;
// eslint-disable-next-line @typescript-eslint/naming-convention
TelemetryService: TelemetryService;
};
}
12 changes: 12 additions & 0 deletions src/types/SalesforceProjectConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* Copyright (c) 2024, salesforce.com, inc.
* All rights reserved.
* SPDX-License-Identifier: MIT
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
*/
import { SfProjectJson } from '@salesforce/core';

export interface SalesforceProjectConfig {
getInstance(): Promise<SfProjectJson>;
getValue(key: string): Promise<any>;
}
Loading