diff --git a/package-lock.json b/package-lock.json index cd8bc00..fbcb398 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ }, "devDependencies": { "@istanbuljs/nyc-config-typescript": "^1.0.2", + "@types/cli-progress": "^3.11.5", "@types/glob": "^8.1.0", "@types/inquirer": "^9.0.6", "@types/mocha": "^10.0.3", @@ -1528,6 +1529,15 @@ "node": ">= 6" } }, + "node_modules/@types/cli-progress": { + "version": "3.11.5", + "resolved": "https://registry.npmjs.org/@types/cli-progress/-/cli-progress-3.11.5.tgz", + "integrity": "sha512-D4PbNRbviKyppS5ivBGyFO29POlySLmA2HyUFE4p5QGazAMM3CwkKWcvTl8gvElSuxRh6FPKL8XmidX873ou4g==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/glob": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz", diff --git a/package.json b/package.json index 753b3f1..06106d9 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ }, "devDependencies": { "@istanbuljs/nyc-config-typescript": "^1.0.2", + "@types/cli-progress": "^3.11.5", "@types/glob": "^8.1.0", "@types/inquirer": "^9.0.6", "@types/mocha": "^10.0.3", diff --git a/resources/instructions/createSObjectLwcQuickActions.html b/resources/instructions/createSObjectLwcQuickActions.html index 69d1c9e..973f646 100644 --- a/resources/instructions/createSObjectLwcQuickActions.html +++ b/resources/instructions/createSObjectLwcQuickActions.html @@ -35,76 +35,76 @@

Create sObject LWC Quick Actions

-

- The following sObjects are present in your configured landing page: -

- - - - - - - - - - - - - - - - -
sObjectLWC Quick Actions
vieweditcreate
- - - - - - - - - \ No newline at end of file + + + + diff --git a/src/commands/wizard/lwcGenerationCommand.ts b/src/commands/wizard/lwcGenerationCommand.ts index 2f109b1..bf8092e 100644 --- a/src/commands/wizard/lwcGenerationCommand.ts +++ b/src/commands/wizard/lwcGenerationCommand.ts @@ -1,6 +1,11 @@ import { Uri, l10n } from 'vscode'; +import { access } from 'fs/promises'; import { InstructionsWebviewProvider } from '../../webviews/instructions'; +import { UEMParser } from '../../utils/uemParser'; +import { WorkspaceUtils } from '../../utils/workspaceUtils'; +import { CommonUtils } from '@salesforce/lwc-dev-mobile-core'; import * as fs from 'fs'; +import * as path from 'path'; export type QuickActionStatus = { view: boolean; @@ -9,11 +14,17 @@ export type QuickActionStatus = { }; export type SObjectQuickActionStatus = { + error?: string; sobjects: { [name: string]: QuickActionStatus; }; }; +export type GetSObjectsStatus = { + error?: string; + sobjects: string[]; +}; + export class LwcGenerationCommand { extensionUri: Uri; @@ -21,6 +32,35 @@ export class LwcGenerationCommand { this.extensionUri = extensionUri; } + static async getSObjectsFromLandingPage(): Promise { + return new Promise(async (resolve) => { + const staticResourcesPath = + await WorkspaceUtils.getStaticResourcesDir(); + const landingPageJson = 'landing_page.json'; + const landingPagePath = path.join( + staticResourcesPath, + landingPageJson + ); + + const getSObjectsStatus: GetSObjectsStatus = { + sobjects: [] + }; + + try { + await access(landingPagePath); + const uem = CommonUtils.loadJsonFromFile(landingPagePath); + getSObjectsStatus.sobjects = UEMParser.findSObjects(uem); + } catch (err) { + console.warn( + `File '${landingPageJson}' does not exist at '${staticResourcesPath}'.` + ); + getSObjectsStatus.error = (err as Error).message; + } + + resolve(getSObjectsStatus); + }); + } + async createSObjectLwcQuickActions() { return new Promise((resolve) => { new InstructionsWebviewProvider( @@ -39,18 +79,9 @@ export class LwcGenerationCommand { { type: 'getQuickActionStatus', action: async (_panel, _data, callback) => { - // TODO: Hook this up to function that parses landing_page.json. - const sobjects = [ - 'Account', - 'Contact', - 'Opportunity', - 'SomeOther' - ]; if (callback) { const quickActionStatus = - await LwcGenerationCommand.checkForExistingQuickActions( - sobjects - ); + await LwcGenerationCommand.checkForExistingQuickActions(); callback(quickActionStatus); } } @@ -60,13 +91,17 @@ export class LwcGenerationCommand { }); } - static async checkForExistingQuickActions( - sobjects: string[] - ): Promise { + static async checkForExistingQuickActions(): Promise { return new Promise(async (resolve) => { const results: SObjectQuickActionStatus = { sobjects: {} }; - sobjects.forEach((sobject) => { + const sObjectsStatus = await this.getSObjectsFromLandingPage(); + if (sObjectsStatus.error) { + results.error = sObjectsStatus.error; + return resolve(results); + } + + sObjectsStatus.sobjects.forEach((sobject) => { const quickActionStatus: QuickActionStatus = { view: false, edit: false, diff --git a/src/commands/wizard/onboardingWizard.ts b/src/commands/wizard/onboardingWizard.ts index f128aeb..f27f258 100644 --- a/src/commands/wizard/onboardingWizard.ts +++ b/src/commands/wizard/onboardingWizard.ts @@ -12,7 +12,6 @@ import { DeployToOrgCommand } from './deployToOrgCommand'; import { ConfigureProjectCommand } from './configureProjectCommand'; import { AuthorizeCommand } from './authorizeCommand'; import { InstructionsWebviewProvider } from '../../webviews/instructions'; -import { LwcGenerationCommand } from './lwcGenerationCommand'; const wizardCommand = 'salesforcedx-vscode-offline-app.onboardingWizard'; const onboardingWizardStateKey = diff --git a/src/commands/wizard/templateChooserCommand.ts b/src/commands/wizard/templateChooserCommand.ts index 06b4ad4..4b9367d 100644 --- a/src/commands/wizard/templateChooserCommand.ts +++ b/src/commands/wizard/templateChooserCommand.ts @@ -10,6 +10,7 @@ import { ProgressLocation, window, workspace } from 'vscode'; import * as path from 'path'; import { access, copyFile } from 'fs/promises'; import { InstructionsWebviewProvider } from '../../webviews/instructions'; +import { WorkspaceUtils } from '../../utils/workspaceUtils'; export type LandingPageStatus = { exists: boolean; @@ -35,12 +36,6 @@ export type LandingPageCollectionStatus = { * When the project is deployed to the user's org, this file will also be copied into static resources and picked up by SApp+. */ export class TemplateChooserCommand { - static readonly STATIC_RESOURCES_PATH = path.join( - 'force-app', - 'main', - 'default', - 'staticresources' - ); static readonly LANDING_PAGE_FILENAME_PREFIX = 'landing_page'; static readonly LANDING_PAGE_JSON_FILE_EXTENSION = '.json'; static readonly LANDING_PAGE_METADATA_FILE_EXTENSION = '.resource-meta.xml'; @@ -110,7 +105,8 @@ export class TemplateChooserCommand { } // If a landing page exists, warn about overwriting it. - const staticResourcesPath = await this.getStaticResourcesDir(); + const staticResourcesPath = + await WorkspaceUtils.getStaticResourcesDir(); const existingLandingPageFiles = await this.landingPageFilesExist( staticResourcesPath, 'existing' @@ -186,7 +182,8 @@ export class TemplateChooserCommand { let staticResourcesPath: string; try { - staticResourcesPath = await this.getStaticResourcesDir(); + staticResourcesPath = + await WorkspaceUtils.getStaticResourcesDir(); } catch (err) { landingPageCollectionStatus.error = (err as Error).message; return resolve(landingPageCollectionStatus); @@ -243,31 +240,6 @@ export class TemplateChooserCommand { return workspaceFolders[0].uri.fsPath; } - static async getStaticResourcesDir(): Promise { - return new Promise(async (resolve, reject) => { - let projectPath: string; - try { - projectPath = this.getWorkspaceDir(); - } catch (err) { - return reject(err); - } - const staticResourcesPath = path.join( - projectPath, - this.STATIC_RESOURCES_PATH - ); - try { - await access(staticResourcesPath); - } catch (err) { - const accessErrorObj = err as Error; - const noAccessError = new NoStaticResourcesDirError( - `Could not read landing page directory at '${staticResourcesPath}': ${accessErrorObj.message}` - ); - return reject(noAccessError); - } - return resolve(staticResourcesPath); - }); - } - static async landingPageFilesExist( staticResourcesPath: string, landingPageType: LandingPageType diff --git a/src/test/suite/commands/wizard/lwcGenerationCommand.test.ts b/src/test/suite/commands/wizard/lwcGenerationCommand.test.ts index d9707cb..88a50a6 100644 --- a/src/test/suite/commands/wizard/lwcGenerationCommand.test.ts +++ b/src/test/suite/commands/wizard/lwcGenerationCommand.test.ts @@ -11,9 +11,9 @@ import * as fs from 'fs'; import { afterEach, beforeEach } from 'mocha'; import { LwcGenerationCommand, - SObjectQuickActionStatus, - QuickActionStatus + SObjectQuickActionStatus } from '../../../../commands/wizard/lwcGenerationCommand'; +import { WorkspaceUtils } from '../../../../utils/workspaceUtils'; suite('LWC Generation Command Test Suite', () => { beforeEach(function () {}); @@ -49,11 +49,16 @@ suite('LWC Generation Command Test Suite', () => { .withArgs(`${baseDir}/sobject2.create.quickAction-meta.xml`) .throws('error'); + const getSObjectsStub = sinon.stub( + LwcGenerationCommand, + 'getSObjectsFromLandingPage' + ); + getSObjectsStub.returns( + Promise.resolve({ sobjects: ['sobject1', 'sobject2'] }) + ); + const result: SObjectQuickActionStatus = - await LwcGenerationCommand.checkForExistingQuickActions([ - 'sobject1', - 'sobject2' - ]); + await LwcGenerationCommand.checkForExistingQuickActions(); assert.equal( result.sobjects['sobject1'].view, @@ -87,4 +92,45 @@ suite('LWC Generation Command Test Suite', () => { 'sobject2.create should NOT exist' ); }); + + test('Should return error status for landing page with invalid json', async () => { + const getWorkspaceDirStub = sinon.stub( + WorkspaceUtils, + 'getStaticResourcesDir' + ); + getWorkspaceDirStub.returns(Promise.resolve('.')); + const fsAccess = sinon.stub(fs, 'access'); + fsAccess.returns(); + const invalidJsonFile = 'landing_page.json'; + const invalidJsonContents = 'invalid_json_here'; + fs.writeFileSync(invalidJsonFile, invalidJsonContents, 'utf8'); + + const status = await LwcGenerationCommand.getSObjectsFromLandingPage(); + + assert.ok(status.error && status.error.length > 0); + + fs.unlinkSync(invalidJsonFile); + }); + + test('Should return 2 sObjects', async () => { + const getWorkspaceDirStub = sinon.stub( + WorkspaceUtils, + 'getStaticResourcesDir' + ); + getWorkspaceDirStub.returns(Promise.resolve('.')); + const fsAccess = sinon.stub(fs, 'access'); + fsAccess.returns(); + const validJsonFile = 'landing_page.json'; + const jsonContents = + '{ "definition": "mcf/list", "properties": { "objectApiName": "Account" }, "nested": { "definition": "mcf/timedList", "properties": { "objectApiName": "Contact"} } }'; + fs.writeFileSync(validJsonFile, jsonContents, 'utf8'); + + const status = await LwcGenerationCommand.getSObjectsFromLandingPage(); + + assert.equal(status.sobjects.length, 2); + assert.equal(status.sobjects[0], 'Account'); + assert.equal(status.sobjects[1], 'Contact'); + + fs.unlinkSync(validJsonFile); + }); }); diff --git a/src/test/suite/commands/wizard/templateChooserCommand.test.ts b/src/test/suite/commands/wizard/templateChooserCommand.test.ts index 9e29403..3e762bf 100644 --- a/src/test/suite/commands/wizard/templateChooserCommand.test.ts +++ b/src/test/suite/commands/wizard/templateChooserCommand.test.ts @@ -8,16 +8,16 @@ import * as assert from 'assert'; import * as sinon from 'sinon'; import * as fs from 'fs'; -import { mkdir } from 'fs/promises'; import * as path from 'path'; +import { mkdir } from 'fs/promises'; import { afterEach, beforeEach } from 'mocha'; import { TemplateChooserCommand, - NoWorkspaceError, - NoStaticResourcesDirError, LandingPageType } from '../../../../commands/wizard/templateChooserCommand'; import { TempProjectDirManager } from '../../../TestHelper'; +import { UIUtils } from '../../../../utils/uiUtils'; +import { WorkspaceUtils } from '../../../../utils/workspaceUtils'; type LandingPageTestIOConfig = { [landingPageType in LandingPageType]?: { @@ -32,76 +32,17 @@ suite('Template Chooser Command Test Suite', () => { sinon.restore(); }); - test('Static resources dir: workspace does not exist', async () => { - try { - await TemplateChooserCommand.getStaticResourcesDir(); - assert.fail('There should have been an error thrown.'); - } catch (noWorkspaceErr) { - assert.ok( - noWorkspaceErr instanceof NoWorkspaceError, - 'No workspace should be defined in this test.' - ); - } - }); - - test('Static resources dir: static resources dir does not exist', async () => { - const projectDirMgr = - await TempProjectDirManager.createTempProjectDir(); - const getWorkspaceDirStub = sinon.stub( - TemplateChooserCommand, - 'getWorkspaceDir' - ); - getWorkspaceDirStub.returns(projectDirMgr.projectDir); - try { - await TemplateChooserCommand.getStaticResourcesDir(); - assert.fail('There should have been an error thrown.'); - } catch (noStaticDirErr) { - assert.ok( - noStaticDirErr instanceof NoStaticResourcesDirError, - 'No static resources dir should be defined in this test.' - ); - } finally { - await projectDirMgr.removeDir(); - getWorkspaceDirStub.restore(); - } - }); - - test('Static resources dir: static resources dir exists', async () => { - const projectDirMgr = - await TempProjectDirManager.createTempProjectDir(); - const getWorkspaceDirStub = sinon.stub( - TemplateChooserCommand, - 'getWorkspaceDir' - ); - getWorkspaceDirStub.returns(projectDirMgr.projectDir); - - const staticResourcesAbsPath = path.join( - projectDirMgr.projectDir, - TemplateChooserCommand.STATIC_RESOURCES_PATH - ); - await mkdir(staticResourcesAbsPath, { recursive: true }); - - try { - const outputDir = - await TemplateChooserCommand.getStaticResourcesDir(); - assert.equal(outputDir, staticResourcesAbsPath); - } finally { - await projectDirMgr.removeDir(); - getWorkspaceDirStub.restore(); - } - }); - test('Landing pages exist: existing landing page file combinations', async () => { const projectDirMgr = await TempProjectDirManager.createTempProjectDir(); const getWorkspaceDirStub = sinon.stub( - TemplateChooserCommand, + WorkspaceUtils, 'getWorkspaceDir' ); getWorkspaceDirStub.returns(projectDirMgr.projectDir); const staticResourcesAbsPath = path.join( projectDirMgr.projectDir, - TemplateChooserCommand.STATIC_RESOURCES_PATH + WorkspaceUtils.STATIC_RESOURCES_PATH ); await mkdir(staticResourcesAbsPath, { recursive: true }); @@ -175,13 +116,13 @@ suite('Template Chooser Command Test Suite', () => { const projectDirMgr = await TempProjectDirManager.createTempProjectDir(); const getWorkspaceDirStub = sinon.stub( - TemplateChooserCommand, + WorkspaceUtils, 'getWorkspaceDir' ); getWorkspaceDirStub.returns(projectDirMgr.projectDir); const staticResourcesAbsPath = path.join( projectDirMgr.projectDir, - TemplateChooserCommand.STATIC_RESOURCES_PATH + WorkspaceUtils.STATIC_RESOURCES_PATH ); await mkdir(staticResourcesAbsPath, { recursive: true }); const config: LandingPageTestIOConfig = { @@ -225,13 +166,13 @@ suite('Template Chooser Command Test Suite', () => { const projectDirMgr = await TempProjectDirManager.createTempProjectDir(); const getWorkspaceDirStub = sinon.stub( - TemplateChooserCommand, + WorkspaceUtils, 'getWorkspaceDir' ); getWorkspaceDirStub.returns(projectDirMgr.projectDir); const staticResourcesAbsPath = path.join( projectDirMgr.projectDir, - TemplateChooserCommand.STATIC_RESOURCES_PATH + WorkspaceUtils.STATIC_RESOURCES_PATH ); await mkdir(staticResourcesAbsPath, { recursive: true }); @@ -300,13 +241,13 @@ suite('Template Chooser Command Test Suite', () => { const projectDirMgr = await TempProjectDirManager.createTempProjectDir(); const getWorkspaceDirStub = sinon.stub( - TemplateChooserCommand, + WorkspaceUtils, 'getWorkspaceDir' ); getWorkspaceDirStub.returns(projectDirMgr.projectDir); const staticResourcesAbsPath = path.join( projectDirMgr.projectDir, - TemplateChooserCommand.STATIC_RESOURCES_PATH + WorkspaceUtils.STATIC_RESOURCES_PATH ); await mkdir(staticResourcesAbsPath, { recursive: true }); const landingPageConfig: LandingPageTestIOConfig = { diff --git a/src/test/suite/utils/uemParser.test.ts b/src/test/suite/utils/uemParser.test.ts new file mode 100644 index 0000000..1e35c98 --- /dev/null +++ b/src/test/suite/utils/uemParser.test.ts @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2023, 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 * as assert from 'assert'; +import { UEMParser } from '../../../utils/uemParser'; + +suite('UEM Parser Test Suite', () => { + test('Empty object returns empty array', async () => { + const sObjects = UEMParser.findSObjects({}); + assert.equal(sObjects.length, 0); + }); + + test('Object with a target field returns array size of one', async () => { + const landingPage = { + definition: 'mcf/list', + properties: { + objectApiName: 'foo' + } + }; + + const sObjects = UEMParser.findSObjects(landingPage); + assert.equal(sObjects.length, 1); + assert.equal(sObjects[0], 'foo'); + }); + + test('Nested object returns all values of the target field', async () => { + const landingPage = { + definition: 'mcf/list', + properties: { + objectApiName: 'foo' + }, + nested: { + definition: 'mcf/list', + properties: { + objectApiName: 'bar' + } + } + }; + + const sObjects = UEMParser.findSObjects(landingPage); + assert.equal(sObjects.length, 2); + assert.equal(sObjects[0], 'foo'); + assert.equal(sObjects[1], 'bar'); + }); + + test('Duplicate field values are omitted', async () => { + const landingPage = { + definition: 'mcf/list', + properties: { + objectApiName: 'foo' + }, + nested: { + definition: 'mcf/list', + properties: { + objectApiName: 'bar' + }, + anotherNested: { + definition: 'mcf/list', + properties: { + objectApiName: 'bar' + } + } + } + }; + + const sObjects = UEMParser.findSObjects(landingPage); + assert.equal(sObjects.length, 2); + assert.equal(sObjects[0], 'foo'); + assert.equal(sObjects[1], 'bar'); + }); + + test('Duplicat field values are omitted', async () => { + const landingPage = { + definition: 'mcf/list', + properties: { + objectApiName: 'plain' + }, + nested: { + definition: 'mcf/timedList', + properties: { + objectApiName: 'timed' + }, + anotherNested: { + definition: 'mcf/genericLists', + properties: { + objectApiName: 'generic' + } + } + } + }; + + const sObjects = UEMParser.findSObjects(landingPage); + assert.equal(sObjects.length, 3); + assert.equal(sObjects[0], 'plain'); + assert.equal(sObjects[1], 'timed'); + assert.equal(sObjects[2], 'generic'); + }); +}); diff --git a/src/test/suite/utils/workspaceUtils.test.ts b/src/test/suite/utils/workspaceUtils.test.ts new file mode 100644 index 0000000..2c49aaf --- /dev/null +++ b/src/test/suite/utils/workspaceUtils.test.ts @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2023, 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 * as assert from 'assert'; +import * as path from 'path'; +import { mkdir } from 'fs/promises'; +import { + NoStaticResourcesDirError, + NoWorkspaceError, + WorkspaceUtils +} from '../../../utils/workspaceUtils'; +import { TempProjectDirManager } from '../../TestHelper'; +import { afterEach, beforeEach } from 'mocha'; +import sinon = require('sinon'); + +suite('Workspace Test Suite', () => { + beforeEach(function () {}); + + afterEach(function () { + sinon.restore(); + }); + + test('Static resources dir: workspace does not exist', async () => { + try { + await WorkspaceUtils.getStaticResourcesDir(); + assert.fail('There should have been an error thrown.'); + } catch (noWorkspaceErr) { + assert.ok( + noWorkspaceErr instanceof NoWorkspaceError, + 'No workspace should be defined in this test.' + ); + } + }); + + test('Static resources dir: static resources dir does not exist', async () => { + const projectDirMgr = + await TempProjectDirManager.createTempProjectDir(); + const getWorkspaceDirStub = sinon.stub( + WorkspaceUtils, + 'getWorkspaceDir' + ); + getWorkspaceDirStub.returns(projectDirMgr.projectDir); + try { + await WorkspaceUtils.getStaticResourcesDir(); + assert.fail('There should have been an error thrown.'); + } catch (noStaticDirErr) { + assert.ok( + noStaticDirErr instanceof NoStaticResourcesDirError, + 'No static resources dir should be defined in this test.' + ); + } finally { + await projectDirMgr.removeDir(); + getWorkspaceDirStub.restore(); + } + }); + + test('Static resources dir: static resources dir exists', async () => { + const projectDirMgr = + await TempProjectDirManager.createTempProjectDir(); + const getWorkspaceDirStub = sinon.stub( + WorkspaceUtils, + 'getWorkspaceDir' + ); + getWorkspaceDirStub.returns(projectDirMgr.projectDir); + + const staticResourcesAbsPath = path.join( + projectDirMgr.projectDir, + WorkspaceUtils.STATIC_RESOURCES_PATH + ); + await mkdir(staticResourcesAbsPath, { recursive: true }); + + try { + const outputDir = await WorkspaceUtils.getStaticResourcesDir(); + assert.equal(outputDir, staticResourcesAbsPath); + } finally { + await projectDirMgr.removeDir(); + getWorkspaceDirStub.restore(); + } + }); +}); diff --git a/src/utils/uemParser.ts b/src/utils/uemParser.ts new file mode 100644 index 0000000..cd53bd9 --- /dev/null +++ b/src/utils/uemParser.ts @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2023, 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 + */ + +export class UEMParser { + public static findSObjects(json: Object): Array { + const sObjects = UEMParser.findObjectsWithKey(json, 'objectApiName'); + + return sObjects; + } + + static findObjectsWithKey( + nestedJsonBlock: any, + keyToMatch: string + ): Array { + const results: Array = []; + + if (typeof nestedJsonBlock === 'object') { + for (const key in nestedJsonBlock) { + const value = nestedJsonBlock[key]; + if (key === keyToMatch && typeof value === 'string') { + results.push(nestedJsonBlock[key as keyof Object]); + } else { + results.push( + ...UEMParser.findObjectsWithKey( + nestedJsonBlock[key as keyof Object], + keyToMatch + ) + ); + } + } + } + + // Clean the array to return. Remove duplicate values. + return [...new Set(results)]; + } +} diff --git a/src/utils/uiUtils.ts b/src/utils/uiUtils.ts index 5411974..de8be4f 100644 --- a/src/utils/uiUtils.ts +++ b/src/utils/uiUtils.ts @@ -5,7 +5,7 @@ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT */ -import { window, QuickPickItem, QuickPickItemKind, QuickPick } from 'vscode'; +import { window, QuickPickItem } from 'vscode'; /** * Convenience wrapper for VS Code UI Extension methods such as showQuickPick(). diff --git a/src/utils/workspaceUtils.ts b/src/utils/workspaceUtils.ts new file mode 100644 index 0000000..cfdf3a2 --- /dev/null +++ b/src/utils/workspaceUtils.ts @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2023, 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 { workspace } from 'vscode'; +import { access } from 'fs/promises'; +import * as path from 'path'; + +export class WorkspaceUtils { + static readonly STATIC_RESOURCES_PATH = path.join( + 'force-app', + 'main', + 'default', + 'staticresources' + ); + + static getWorkspaceDir(): string { + const workspaceFolders = workspace.workspaceFolders; + if (!workspaceFolders || workspaceFolders.length === 0) { + throw new NoWorkspaceError( + 'No workspace defined for this project.' + ); + } + return workspaceFolders[0].uri.fsPath; + } + + static async getStaticResourcesDir(): Promise { + return new Promise(async (resolve, reject) => { + let projectPath: string; + try { + projectPath = this.getWorkspaceDir(); + } catch (err) { + return reject(err); + } + const staticResourcesPath = path.join( + projectPath, + this.STATIC_RESOURCES_PATH + ); + try { + await access(staticResourcesPath); + } catch (err) { + const noAccessError = new NoStaticResourcesDirError( + `Could not read static resources directory at '${staticResourcesPath}'` + ); + + return reject(noAccessError); + } + return resolve(staticResourcesPath); + }); + } +} + +export class NoWorkspaceError extends Error { + constructor(message?: string) { + super(message); + this.name = this.constructor.name; + Object.setPrototypeOf(this, NoWorkspaceError.prototype); + } +} + +export class NoStaticResourcesDirError extends Error { + constructor(message?: string) { + super(message); + this.name = this.constructor.name; + Object.setPrototypeOf(this, NoStaticResourcesDirError.prototype); + } +}