From 0ad42f045633ee1576dffe0bca33eac66d426948 Mon Sep 17 00:00:00 2001 From: Takashi Arai Date: Thu, 6 Jun 2024 14:17:36 -0700 Subject: [PATCH 01/26] feat: create and add web server token --- src/shared/identityUtils.ts | 44 +++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/shared/identityUtils.ts diff --git a/src/shared/identityUtils.ts b/src/shared/identityUtils.ts new file mode 100644 index 00000000..1ec39d56 --- /dev/null +++ b/src/shared/identityUtils.ts @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2024, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ + +import { randomBytes } from 'node:crypto'; +import path from 'node:path'; +import fs from 'node:fs'; +import process from 'node:process'; +import { CommonUtils } from '@salesforce/lwc-dev-mobile-core'; + +class LwrConfigFile { + public identityToken?: string; +} + +export class IdentityUtils { + public static async createIdentityToken(): Promise { + const rootDir = path.resolve(process.cwd()); + const lwrConfigFile = path.join(rootDir, 'lwr.config.json'); + if (fs.existsSync(lwrConfigFile)) { + const config = this.loadConfigFile(lwrConfigFile); + if (config?.identityToken == null) { + config.identityToken = randomBytes(256).toString(); + try { + await CommonUtils.createTextFile(lwrConfigFile, JSON.stringify(config)); + } catch (err) { + const error = err as Error; + throw new Error(`Error thrown while trying to write identity token to lwr.config.js: ${error.message}`); + } + } + } + } + + private static loadConfigFile(file: string): LwrConfigFile { + const json = CommonUtils.loadJsonFromFile(file); + const configFile = Object.assign(new LwrConfigFile(), json); + return configFile; + } +} From e03b07a477dd6fb5d4bcd9e97a325d53828a722a Mon Sep 17 00:00:00 2001 From: Takashi Arai Date: Fri, 7 Jun 2024 18:06:34 -0700 Subject: [PATCH 02/26] feat: refactor. add dev server utils --- src/shared/devServerUtils.ts | 27 +++++++++++++++++++++++++++ src/shared/identityUtils.ts | 19 +++++-------------- 2 files changed, 32 insertions(+), 14 deletions(-) create mode 100644 src/shared/devServerUtils.ts diff --git a/src/shared/devServerUtils.ts b/src/shared/devServerUtils.ts new file mode 100644 index 00000000..e90dc1b8 --- /dev/null +++ b/src/shared/devServerUtils.ts @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2024, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import path from 'node:path'; +import { CommonUtils } from '@salesforce/lwc-dev-mobile-core'; + +export class DevServerUtils { + public static getServerConfigFileLocation(): string { + // TODO: update this after dev server is integrated into this repo + return path.join(process.cwd(), 'lwr.config.json'); + } + + public static fetchServerConfigFileContent(): unknown { + const filePath = DevServerUtils.getServerConfigFileLocation(); + return CommonUtils.loadJsonFromFile(filePath) as unknown; + } + + public static async writeServerConfigFileContent(config: unknown): Promise { + const filePath = DevServerUtils.getServerConfigFileLocation(); + // create or overwrite the config file + return CommonUtils.createTextFile(filePath, JSON.stringify(config, undefined, 2)); + } +} diff --git a/src/shared/identityUtils.ts b/src/shared/identityUtils.ts index 1ec39d56..08943f17 100644 --- a/src/shared/identityUtils.ts +++ b/src/shared/identityUtils.ts @@ -8,11 +8,9 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-return */ -import { randomBytes } from 'node:crypto'; -import path from 'node:path'; import fs from 'node:fs'; -import process from 'node:process'; -import { CommonUtils } from '@salesforce/lwc-dev-mobile-core'; +import { randomBytes } from 'node:crypto'; +import { DevServerUtils } from './devServerUtils.js'; class LwrConfigFile { public identityToken?: string; @@ -20,14 +18,13 @@ class LwrConfigFile { export class IdentityUtils { public static async createIdentityToken(): Promise { - const rootDir = path.resolve(process.cwd()); - const lwrConfigFile = path.join(rootDir, 'lwr.config.json'); + const lwrConfigFile = DevServerUtils.getServerConfigFileLocation(); if (fs.existsSync(lwrConfigFile)) { - const config = this.loadConfigFile(lwrConfigFile); + const config = DevServerUtils.fetchServerConfigFileContent() as LwrConfigFile; if (config?.identityToken == null) { config.identityToken = randomBytes(256).toString(); try { - await CommonUtils.createTextFile(lwrConfigFile, JSON.stringify(config)); + await DevServerUtils.writeServerConfigFileContent(config); } catch (err) { const error = err as Error; throw new Error(`Error thrown while trying to write identity token to lwr.config.js: ${error.message}`); @@ -35,10 +32,4 @@ export class IdentityUtils { } } } - - private static loadConfigFile(file: string): LwrConfigFile { - const json = CommonUtils.loadJsonFromFile(file); - const configFile = Object.assign(new LwrConfigFile(), json); - return configFile; - } } From 68692eaf313382607fa67b91c014cf3b70836bb1 Mon Sep 17 00:00:00 2001 From: Takashi Arai Date: Fri, 7 Jun 2024 18:53:32 -0700 Subject: [PATCH 03/26] feat: dev server utils test --- test/shared/devServerUtils.test.ts | 38 ++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 test/shared/devServerUtils.test.ts diff --git a/test/shared/devServerUtils.test.ts b/test/shared/devServerUtils.test.ts new file mode 100644 index 00000000..34bb4479 --- /dev/null +++ b/test/shared/devServerUtils.test.ts @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2024, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { TestContext } from '@salesforce/core/testSetup'; +import { expect } from 'chai'; +import { CommonUtils } from '@salesforce/lwc-dev-mobile-core'; +import { DevServerUtils } from '../../src/shared/devServerUtils.js'; + +describe('devServerUtils', () => { + const $$ = new TestContext(); + + afterEach(() => { + $$.restore(); + }); + + it('getServerConfigFileLocation returns joined path of current working directory and lwr.config.json', async () => { + $$.SANDBOX.stub(process, 'cwd').returns('desktop'); + const configFileLocation = DevServerUtils.getServerConfigFileLocation(); + expect(configFileLocation).to.be.equal('desktop/lwr.config.json'); + }); + + it('fetchServerConfigFileContent returns expected object', async () => { + $$.SANDBOX.stub(DevServerUtils, 'getServerConfigFileLocation').returns('foo'); + $$.SANDBOX.stub(CommonUtils, 'loadJsonFromFile').withArgs('foo').returns({ foo: 'bar' }); + const config = DevServerUtils.fetchServerConfigFileContent(); + expect(config).to.deep.equal({ foo: 'bar' }); + }); + + it('writeServerConfigFileContent calls CommonUtils.createTextFile', async () => { + $$.SANDBOX.stub(DevServerUtils, 'getServerConfigFileLocation').returns('foo'); + const stub = $$.SANDBOX.stub(CommonUtils, 'createTextFile').resolves(); + expect(stub.calledOnce); + }); +}); From fd8ec844eb7e57aa77cfe863d49417377b28682a Mon Sep 17 00:00:00 2001 From: Takashi Arai Date: Fri, 7 Jun 2024 18:54:45 -0700 Subject: [PATCH 04/26] chore: nit --- test/shared/identityUtils.test.s | 53 ++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 test/shared/identityUtils.test.s diff --git a/test/shared/identityUtils.test.s b/test/shared/identityUtils.test.s new file mode 100644 index 00000000..a5e22f9c --- /dev/null +++ b/test/shared/identityUtils.test.s @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2024, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { TestContext } from '@salesforce/core/testSetup'; +import { expect } from 'chai'; +import { DevServerUtils } from '../../src/shared/devServerUtils.js'; +// import { AuthInfo, Connection } from '@salesforce/core'; +// import { OrgUtils } from '../../src/shared/orgUtils.js'; + +describe('lightning preview app', () => { + const $$ = new TestContext(); + + afterEach(() => { + $$.restore(); + }); + + it('getAppId returns undefined when no matches found', async () => { + $$.SANDBOX.stub(process, 'cwd').resolves('\\Users\\joe\\desktop'); + const configFileLocation = DevServerUtils.getServerConfigFileLocation(); + expect(configFileLocation).to.be.equal('\\Users\\joe\\desktop\\lwr.config.json'); + // $$.SANDBOX.stub(Connection.prototype, 'query').resolves({ records: [], done: true, totalSize: 0 }); + // const appId = await OrgUtils.getAppId(new Connection({ authInfo: new AuthInfo() }), 'blah'); + // expect(appId).to.be.undefined; + }); + + // it('getAppId returns first match when multiple matches found', async () => { + // $$.SANDBOX.stub(Connection.prototype, 'query').resolves({ + // records: [{ DurableId: 'id1' }, { DurableId: 'id2' }], + // done: true, + // totalSize: 2, + // }); + // const appId = await OrgUtils.getAppId(new Connection({ authInfo: new AuthInfo() }), 'Sales'); + // expect(appId).to.be.equal('id1'); + // }); + + // it('getAppId uses Label if DeveloperName produces no matches', async () => { + // const noMatches = { records: [], done: true, totalSize: 0 }; + // const matches = { records: [{ DurableId: 'id1' }, { DurableId: 'id2' }], done: true, totalSize: 2 }; + // const stub = $$.SANDBOX.stub(Connection.prototype, 'query') + // .onFirstCall() + // .resolves(noMatches) + // .onSecondCall() + // .resolves(matches); + // const appId = await OrgUtils.getAppId(new Connection({ authInfo: new AuthInfo() }), 'Sales'); + // expect(appId).to.be.equal('id1'); + // expect(stub.getCall(0).args[0]).to.include('DeveloperName'); + // expect(stub.getCall(1).args[0]).to.include('Label'); + // }); +}); From afd38348f223b9af4a65da141a800f4459b5e916 Mon Sep 17 00:00:00 2001 From: Takashi Arai Date: Fri, 7 Jun 2024 20:39:35 -0700 Subject: [PATCH 05/26] feat: test. nit --- src/shared/identityUtils.ts | 4 +- test/shared/devServerUtils.test.ts | 4 +- test/shared/identityUtils.test.s | 53 ---------------------- test/shared/identityUtils.test.ts | 73 ++++++++++++++++++++++++++++++ 4 files changed, 78 insertions(+), 56 deletions(-) delete mode 100644 test/shared/identityUtils.test.s create mode 100644 test/shared/identityUtils.test.ts diff --git a/src/shared/identityUtils.ts b/src/shared/identityUtils.ts index 08943f17..af37bccf 100644 --- a/src/shared/identityUtils.ts +++ b/src/shared/identityUtils.ts @@ -16,13 +16,15 @@ class LwrConfigFile { public identityToken?: string; } +const BYTES_TO_GENERATE = 256; + export class IdentityUtils { public static async createIdentityToken(): Promise { const lwrConfigFile = DevServerUtils.getServerConfigFileLocation(); if (fs.existsSync(lwrConfigFile)) { const config = DevServerUtils.fetchServerConfigFileContent() as LwrConfigFile; if (config?.identityToken == null) { - config.identityToken = randomBytes(256).toString(); + config.identityToken = randomBytes(BYTES_TO_GENERATE).toString(); try { await DevServerUtils.writeServerConfigFileContent(config); } catch (err) { diff --git a/test/shared/devServerUtils.test.ts b/test/shared/devServerUtils.test.ts index 34bb4479..0409db24 100644 --- a/test/shared/devServerUtils.test.ts +++ b/test/shared/devServerUtils.test.ts @@ -17,13 +17,13 @@ describe('devServerUtils', () => { $$.restore(); }); - it('getServerConfigFileLocation returns joined path of current working directory and lwr.config.json', async () => { + it('getServerConfigFileLocation returns a path joined from current working directory and lwr.config.json', async () => { $$.SANDBOX.stub(process, 'cwd').returns('desktop'); const configFileLocation = DevServerUtils.getServerConfigFileLocation(); expect(configFileLocation).to.be.equal('desktop/lwr.config.json'); }); - it('fetchServerConfigFileContent returns expected object', async () => { + it('fetchServerConfigFileContent returns an expected object', async () => { $$.SANDBOX.stub(DevServerUtils, 'getServerConfigFileLocation').returns('foo'); $$.SANDBOX.stub(CommonUtils, 'loadJsonFromFile').withArgs('foo').returns({ foo: 'bar' }); const config = DevServerUtils.fetchServerConfigFileContent(); diff --git a/test/shared/identityUtils.test.s b/test/shared/identityUtils.test.s deleted file mode 100644 index a5e22f9c..00000000 --- a/test/shared/identityUtils.test.s +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2024, salesforce.com, inc. - * All rights reserved. - * Licensed under the BSD 3-Clause license. - * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause - */ - -import { TestContext } from '@salesforce/core/testSetup'; -import { expect } from 'chai'; -import { DevServerUtils } from '../../src/shared/devServerUtils.js'; -// import { AuthInfo, Connection } from '@salesforce/core'; -// import { OrgUtils } from '../../src/shared/orgUtils.js'; - -describe('lightning preview app', () => { - const $$ = new TestContext(); - - afterEach(() => { - $$.restore(); - }); - - it('getAppId returns undefined when no matches found', async () => { - $$.SANDBOX.stub(process, 'cwd').resolves('\\Users\\joe\\desktop'); - const configFileLocation = DevServerUtils.getServerConfigFileLocation(); - expect(configFileLocation).to.be.equal('\\Users\\joe\\desktop\\lwr.config.json'); - // $$.SANDBOX.stub(Connection.prototype, 'query').resolves({ records: [], done: true, totalSize: 0 }); - // const appId = await OrgUtils.getAppId(new Connection({ authInfo: new AuthInfo() }), 'blah'); - // expect(appId).to.be.undefined; - }); - - // it('getAppId returns first match when multiple matches found', async () => { - // $$.SANDBOX.stub(Connection.prototype, 'query').resolves({ - // records: [{ DurableId: 'id1' }, { DurableId: 'id2' }], - // done: true, - // totalSize: 2, - // }); - // const appId = await OrgUtils.getAppId(new Connection({ authInfo: new AuthInfo() }), 'Sales'); - // expect(appId).to.be.equal('id1'); - // }); - - // it('getAppId uses Label if DeveloperName produces no matches', async () => { - // const noMatches = { records: [], done: true, totalSize: 0 }; - // const matches = { records: [{ DurableId: 'id1' }, { DurableId: 'id2' }], done: true, totalSize: 2 }; - // const stub = $$.SANDBOX.stub(Connection.prototype, 'query') - // .onFirstCall() - // .resolves(noMatches) - // .onSecondCall() - // .resolves(matches); - // const appId = await OrgUtils.getAppId(new Connection({ authInfo: new AuthInfo() }), 'Sales'); - // expect(appId).to.be.equal('id1'); - // expect(stub.getCall(0).args[0]).to.include('DeveloperName'); - // expect(stub.getCall(1).args[0]).to.include('Label'); - // }); -}); diff --git a/test/shared/identityUtils.test.ts b/test/shared/identityUtils.test.ts new file mode 100644 index 00000000..d60894f3 --- /dev/null +++ b/test/shared/identityUtils.test.ts @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2024, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import fs from 'node:fs'; +import { expect } from 'chai'; +import { TestContext } from '@salesforce/core/testSetup'; +import { DevServerUtils } from '../../src/shared/devServerUtils.js'; +import { IdentityUtils } from '../../src/shared/identityUtils.js'; + +describe('identityUtils', () => { + const $$ = new TestContext(); + + afterEach(() => { + $$.restore(); + }); + + it('createIdentityToken resolves if lwr.config.json is not found', async () => { + $$.SANDBOX.stub(DevServerUtils, 'getServerConfigFileLocation').returns('lwr.config.json'); + $$.SANDBOX.stub(fs, 'existsSync').returns(false); + + const resolved = await IdentityUtils.createIdentityToken(); + expect(resolved).to.equal(undefined); + }); + + it('createIdentityToken resolves if lwr.config.json has identity token', async () => { + $$.SANDBOX.stub(DevServerUtils, 'getServerConfigFileLocation').returns('lwr.config.json'); + $$.SANDBOX.stub(fs, 'existsSync').returns(true); + $$.SANDBOX.stub(DevServerUtils, 'fetchServerConfigFileContent').returns({ identityToken: 'foo' }); + + const resolved = await IdentityUtils.createIdentityToken(); + expect(resolved).to.equal(undefined); + }); + + it('createIdentityToken resolves if lwr.config.json has identity token', async () => { + $$.SANDBOX.stub(DevServerUtils, 'getServerConfigFileLocation').returns('lwr.config.json'); + $$.SANDBOX.stub(fs, 'existsSync').returns(true); + $$.SANDBOX.stub(DevServerUtils, 'fetchServerConfigFileContent').returns({ identityToken: 'foo' }); + + const resolved = await IdentityUtils.createIdentityToken(); + expect(resolved).to.equal(undefined); + }); + + it('createIdentityToken resolves if lwr.config.json has no idenity token and creates one', async () => { + $$.SANDBOX.stub(DevServerUtils, 'getServerConfigFileLocation').returns('lwr.config.json'); + $$.SANDBOX.stub(fs, 'existsSync').returns(true); + $$.SANDBOX.stub(DevServerUtils, 'fetchServerConfigFileContent').returns({}); + $$.SANDBOX.stub(DevServerUtils, 'writeServerConfigFileContent').resolves(); + + const resolved = await IdentityUtils.createIdentityToken(); + expect(resolved).to.equal(undefined); + }); + + it('createIdentityToken rejects if identity token can not be written to lwr.config.json', async () => { + const errorMessage = 'foo bar'; + $$.SANDBOX.stub(DevServerUtils, 'getServerConfigFileLocation').returns('lwr.config.json'); + $$.SANDBOX.stub(fs, 'existsSync').returns(true); + $$.SANDBOX.stub(DevServerUtils, 'fetchServerConfigFileContent').returns({}); + $$.SANDBOX.stub(DevServerUtils, 'writeServerConfigFileContent').throws(new Error(errorMessage)); + + try { + await IdentityUtils.createIdentityToken(); + } catch (err) { + const error = err as Error; + expect(error.message).to.equal( + `Error thrown while trying to write identity token to lwr.config.js: ${errorMessage}` + ); + } + }); +}); From d084ea4efc2fbbf92e377f909e38a801e457628c Mon Sep 17 00:00:00 2001 From: Takashi Arai Date: Fri, 7 Jun 2024 20:52:48 -0700 Subject: [PATCH 06/26] feat: 256 bit --- src/shared/identityUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/identityUtils.ts b/src/shared/identityUtils.ts index af37bccf..993e52c6 100644 --- a/src/shared/identityUtils.ts +++ b/src/shared/identityUtils.ts @@ -16,7 +16,7 @@ class LwrConfigFile { public identityToken?: string; } -const BYTES_TO_GENERATE = 256; +const BYTES_TO_GENERATE = 32; export class IdentityUtils { public static async createIdentityToken(): Promise { From dd402b980979e6e3f6672877c2bc76fd423db1f9 Mon Sep 17 00:00:00 2001 From: Takashi Arai Date: Tue, 11 Jun 2024 15:42:22 -0700 Subject: [PATCH 07/26] chore: update after code review --- src/shared/identityUtils.ts | 25 +++++++++---------------- test/shared/identityUtils.test.ts | 31 +++++++++++-------------------- 2 files changed, 20 insertions(+), 36 deletions(-) diff --git a/src/shared/identityUtils.ts b/src/shared/identityUtils.ts index 993e52c6..6a14ed9c 100644 --- a/src/shared/identityUtils.ts +++ b/src/shared/identityUtils.ts @@ -8,7 +8,6 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-return */ -import fs from 'node:fs'; import { randomBytes } from 'node:crypto'; import { DevServerUtils } from './devServerUtils.js'; @@ -16,22 +15,16 @@ class LwrConfigFile { public identityToken?: string; } -const BYTES_TO_GENERATE = 32; - +// ********************************************************************************************** +// * TODO: When we finalize the implementation for the preview commands and things settle down, * +// * consider moving this class into CryptoUtils of lwc-dev-mobile-core instead. * +// ********************************************************************************************** export class IdentityUtils { - public static async createIdentityToken(): Promise { - const lwrConfigFile = DevServerUtils.getServerConfigFileLocation(); - if (fs.existsSync(lwrConfigFile)) { - const config = DevServerUtils.fetchServerConfigFileContent() as LwrConfigFile; - if (config?.identityToken == null) { - config.identityToken = randomBytes(BYTES_TO_GENERATE).toString(); - try { - await DevServerUtils.writeServerConfigFileContent(config); - } catch (err) { - const error = err as Error; - throw new Error(`Error thrown while trying to write identity token to lwr.config.js: ${error.message}`); - } - } + public static async updateServerConfigFileWithIdentityToken(byteSize = 32): Promise { + const config = await DevServerUtils.fetchServerConfigFileContent() as LwrConfigFile; + if (config && !config.identityToken) { + config.identityToken = randomBytes(byteSize).toString(); + await DevServerUtils.writeServerConfigFileContent(config); } } } diff --git a/test/shared/identityUtils.test.ts b/test/shared/identityUtils.test.ts index d60894f3..ceb6e1c5 100644 --- a/test/shared/identityUtils.test.ts +++ b/test/shared/identityUtils.test.ts @@ -5,7 +5,6 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import fs from 'node:fs'; import { expect } from 'chai'; import { TestContext } from '@salesforce/core/testSetup'; import { DevServerUtils } from '../../src/shared/devServerUtils.js'; @@ -18,56 +17,48 @@ describe('identityUtils', () => { $$.restore(); }); - it('createIdentityToken resolves if lwr.config.json is not found', async () => { + it('updateServerConfigFileWithIdentityToken resolves if lwr.config.json is not found', async () => { $$.SANDBOX.stub(DevServerUtils, 'getServerConfigFileLocation').returns('lwr.config.json'); - $$.SANDBOX.stub(fs, 'existsSync').returns(false); - const resolved = await IdentityUtils.createIdentityToken(); + const resolved = await IdentityUtils.updateServerConfigFileWithIdentityToken(); expect(resolved).to.equal(undefined); }); - it('createIdentityToken resolves if lwr.config.json has identity token', async () => { + it('updateServerConfigFileWithIdentityToken resolves if lwr.config.json has identity token', async () => { $$.SANDBOX.stub(DevServerUtils, 'getServerConfigFileLocation').returns('lwr.config.json'); - $$.SANDBOX.stub(fs, 'existsSync').returns(true); $$.SANDBOX.stub(DevServerUtils, 'fetchServerConfigFileContent').returns({ identityToken: 'foo' }); - const resolved = await IdentityUtils.createIdentityToken(); + const resolved = await IdentityUtils.updateServerConfigFileWithIdentityToken(); expect(resolved).to.equal(undefined); }); - it('createIdentityToken resolves if lwr.config.json has identity token', async () => { + it('updateServerConfigFileWithIdentityToken resolves if lwr.config.json has identity token', async () => { $$.SANDBOX.stub(DevServerUtils, 'getServerConfigFileLocation').returns('lwr.config.json'); - $$.SANDBOX.stub(fs, 'existsSync').returns(true); $$.SANDBOX.stub(DevServerUtils, 'fetchServerConfigFileContent').returns({ identityToken: 'foo' }); - const resolved = await IdentityUtils.createIdentityToken(); + const resolved = await IdentityUtils.updateServerConfigFileWithIdentityToken(); expect(resolved).to.equal(undefined); }); - it('createIdentityToken resolves if lwr.config.json has no idenity token and creates one', async () => { + it('updateServerConfigFileWithIdentityToken resolves if lwr.config.json has no idenity token and creates one', async () => { $$.SANDBOX.stub(DevServerUtils, 'getServerConfigFileLocation').returns('lwr.config.json'); - $$.SANDBOX.stub(fs, 'existsSync').returns(true); $$.SANDBOX.stub(DevServerUtils, 'fetchServerConfigFileContent').returns({}); $$.SANDBOX.stub(DevServerUtils, 'writeServerConfigFileContent').resolves(); - const resolved = await IdentityUtils.createIdentityToken(); + const resolved = await IdentityUtils.updateServerConfigFileWithIdentityToken(); expect(resolved).to.equal(undefined); }); - it('createIdentityToken rejects if identity token can not be written to lwr.config.json', async () => { + it('updateServerConfigFileWithIdentityToken rejects if identity token can not be written to lwr.config.json', async () => { const errorMessage = 'foo bar'; - $$.SANDBOX.stub(DevServerUtils, 'getServerConfigFileLocation').returns('lwr.config.json'); - $$.SANDBOX.stub(fs, 'existsSync').returns(true); $$.SANDBOX.stub(DevServerUtils, 'fetchServerConfigFileContent').returns({}); $$.SANDBOX.stub(DevServerUtils, 'writeServerConfigFileContent').throws(new Error(errorMessage)); try { - await IdentityUtils.createIdentityToken(); + await IdentityUtils.updateServerConfigFileWithIdentityToken(); } catch (err) { const error = err as Error; - expect(error.message).to.equal( - `Error thrown while trying to write identity token to lwr.config.js: ${errorMessage}` - ); + expect(error.message).to.equal(errorMessage); } }); }); From dba95d43ed348301690f16366a34ddfda0d4b4dc Mon Sep 17 00:00:00 2001 From: Takashi Arai Date: Wed, 12 Jun 2024 11:27:24 -0700 Subject: [PATCH 08/26] chore: hex --- src/shared/identityUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shared/identityUtils.ts b/src/shared/identityUtils.ts index 6a14ed9c..ff2afbf6 100644 --- a/src/shared/identityUtils.ts +++ b/src/shared/identityUtils.ts @@ -21,9 +21,9 @@ class LwrConfigFile { // ********************************************************************************************** export class IdentityUtils { public static async updateServerConfigFileWithIdentityToken(byteSize = 32): Promise { - const config = await DevServerUtils.fetchServerConfigFileContent() as LwrConfigFile; + const config = (await DevServerUtils.fetchServerConfigFileContent()) as LwrConfigFile; if (config && !config.identityToken) { - config.identityToken = randomBytes(byteSize).toString(); + config.identityToken = randomBytes(byteSize).toString('hex'); await DevServerUtils.writeServerConfigFileContent(config); } } From f7b98ab4524e685fb4739cc029811d6949a83af7 Mon Sep 17 00:00:00 2001 From: Takashi Arai Date: Wed, 12 Jun 2024 11:37:48 -0700 Subject: [PATCH 09/26] chore: base64. update todo comment --- src/shared/identityUtils.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/shared/identityUtils.ts b/src/shared/identityUtils.ts index ff2afbf6..18963ab8 100644 --- a/src/shared/identityUtils.ts +++ b/src/shared/identityUtils.ts @@ -16,14 +16,15 @@ class LwrConfigFile { } // ********************************************************************************************** -// * TODO: When we finalize the implementation for the preview commands and things settle down, * -// * consider moving this class into CryptoUtils of lwc-dev-mobile-core instead. * +// * TODO: Move the functionality of creating the token as a method in CryptoUtils of * +// * lwc-dev-mobile-core instead. Do so when it's finalized where to stash the token: * +// * lwr.config.json vs sfdx-project.json * // ********************************************************************************************** export class IdentityUtils { public static async updateServerConfigFileWithIdentityToken(byteSize = 32): Promise { const config = (await DevServerUtils.fetchServerConfigFileContent()) as LwrConfigFile; if (config && !config.identityToken) { - config.identityToken = randomBytes(byteSize).toString('hex'); + config.identityToken = randomBytes(byteSize).toString('base64'); await DevServerUtils.writeServerConfigFileContent(config); } } From fd4c5f802fce3b0e1f46b0333f62637af743846d Mon Sep 17 00:00:00 2001 From: Takashi Arai Date: Thu, 13 Jun 2024 12:34:32 -0700 Subject: [PATCH 10/26] chore: use updated CryptoUtils --- package.json | 2 +- src/shared/devServerUtils.ts | 4 +++- src/shared/identityUtils.ts | 15 ++++----------- yarn.lock | 8 ++++---- 4 files changed, 12 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index 91aa99e8..952d7750 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "@oclif/core": "^3.26.6", "@salesforce/core": "^7.3.9", "@salesforce/kit": "^3.1.2", - "@salesforce/lwc-dev-mobile-core": "4.0.0-alpha.3", + "@salesforce/lwc-dev-mobile-core": "4.0.0-alpha.4", "@salesforce/sf-plugins-core": "^9.1.1", "@inquirer/select": "^2.3.5", "chalk": "^5.3.0", diff --git a/src/shared/devServerUtils.ts b/src/shared/devServerUtils.ts index e90dc1b8..80d4d904 100644 --- a/src/shared/devServerUtils.ts +++ b/src/shared/devServerUtils.ts @@ -10,7 +10,9 @@ import { CommonUtils } from '@salesforce/lwc-dev-mobile-core'; export class DevServerUtils { public static getServerConfigFileLocation(): string { - // TODO: update this after dev server is integrated into this repo + // TODO: When extensibility of sfdx-project.json becomes clear, + // use that as a place for reading and writing this plugin's + // related values instead of lwr.config.json. return path.join(process.cwd(), 'lwr.config.json'); } diff --git a/src/shared/identityUtils.ts b/src/shared/identityUtils.ts index 18963ab8..1cfcc1aa 100644 --- a/src/shared/identityUtils.ts +++ b/src/shared/identityUtils.ts @@ -5,26 +5,19 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/no-unsafe-return */ - -import { randomBytes } from 'node:crypto'; +import { CryptoUtils } from '@salesforce/lwc-dev-mobile-core'; import { DevServerUtils } from './devServerUtils.js'; class LwrConfigFile { public identityToken?: string; } -// ********************************************************************************************** -// * TODO: Move the functionality of creating the token as a method in CryptoUtils of * -// * lwc-dev-mobile-core instead. Do so when it's finalized where to stash the token: * -// * lwr.config.json vs sfdx-project.json * -// ********************************************************************************************** export class IdentityUtils { - public static async updateServerConfigFileWithIdentityToken(byteSize = 32): Promise { + public static async updateServerConfigFileWithIdentityToken(): Promise { const config = (await DevServerUtils.fetchServerConfigFileContent()) as LwrConfigFile; if (config && !config.identityToken) { - config.identityToken = randomBytes(byteSize).toString('base64'); + // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment + config.identityToken = CryptoUtils.generateIdentityToken(); await DevServerUtils.writeServerConfigFileContent(config); } } diff --git a/yarn.lock b/yarn.lock index 455cd7ce..e22b8854 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4304,10 +4304,10 @@ "@salesforce/ts-types" "^2.0.9" tslib "^2.6.3" -"@salesforce/lwc-dev-mobile-core@4.0.0-alpha.3": - version "4.0.0-alpha.3" - resolved "https://registry.yarnpkg.com/@salesforce/lwc-dev-mobile-core/-/lwc-dev-mobile-core-4.0.0-alpha.3.tgz#7485689c78e97b53ba88ce714db11bbeacfde192" - integrity sha512-7o7n6tuTshqwmfym2vy89IvMB8rKUcqDL7mg/nQ77wP4OxAjtj+mQzamcLz1nQU7QI4529RbkBvXe/2R0zbXBA== +"@salesforce/lwc-dev-mobile-core@4.0.0-alpha.4": + version "4.0.0-alpha.4" + resolved "https://registry.yarnpkg.com/@salesforce/lwc-dev-mobile-core/-/lwc-dev-mobile-core-4.0.0-alpha.4.tgz#5e020cb3222f2603a01248359d43b3d6ca4062e3" + integrity sha512-gNEQQr4QIIdXgGmSr6820Y2/HR7YqwxBjf0Z/PKr/iNoQ24Agc1QGMfUysc5H7/Bmx69GPF9wq7ahbNjSvB+Kg== dependencies: "@oclif/core" "^3.26.6" "@salesforce/core" "^7.3.6" From 7ce311ec20e174a151d10f88b0d8132c06519ab6 Mon Sep 17 00:00:00 2001 From: Takashi Arai Date: Wed, 19 Jun 2024 00:03:13 -0700 Subject: [PATCH 11/26] chore: use sf config --- package.json | 1 + src/configMeta.ts | 40 +++++++++++++++++++ src/shared/identityUtils.ts | 47 ++++++++++++++++++----- src/shared/lightningDevConfig.ts | 16 ++++++++ test/shared/identityUtils.test.ts | 53 ++++++++++++-------------- test/shared/lightningDevConfig.test.ts | 17 +++++++++ 6 files changed, 136 insertions(+), 38 deletions(-) create mode 100644 src/configMeta.ts create mode 100644 src/shared/lightningDevConfig.ts create mode 100644 test/shared/lightningDevConfig.test.ts diff --git a/package.json b/package.json index 952d7750..256df665 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "oclif": { "commands": "./lib/commands", "bin": "sf", + "configMeta": "./lib/configMeta", "topicSeparator": " ", "devPlugins": [ "@oclif/plugin-help", diff --git a/src/configMeta.ts b/src/configMeta.ts new file mode 100644 index 00000000..0b070ffc --- /dev/null +++ b/src/configMeta.ts @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import type { ConfigValue, ConfigPropertyMeta } from '@salesforce/core'; + +export enum ConfigVars { + /** + * The Base64-encoded identity token of the local web server, used to + * validate the web server's identity to the hmr-client. + */ + LOCAL_WEB_SERVER_IDENTITY_TOKEN = 'local-web-server-identity-token', +} + +export default [ + { + key: ConfigVars.LOCAL_WEB_SERVER_IDENTITY_TOKEN, + description: 'The Base64-encoded identity token of the local web server', + hidden: true, + encrypted: true, + input: { + validator: (value: ConfigValue): boolean => { + // Ensure that `value` is a Base64-encoded string. + if (typeof value !== 'string') { + return false; + } + try { + const decoded = Buffer.from(value, 'base64').toString('base64'); + return decoded === value; + } catch (e) { + return false; + } + }, + failedMessage: `${ConfigVars.LOCAL_WEB_SERVER_IDENTITY_TOKEN} is not a Base64-encoded string!`, + }, + }, +] as ConfigPropertyMeta[]; diff --git a/src/shared/identityUtils.ts b/src/shared/identityUtils.ts index 1cfcc1aa..363dcd67 100644 --- a/src/shared/identityUtils.ts +++ b/src/shared/identityUtils.ts @@ -5,20 +5,47 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import { CryptoUtils } from '@salesforce/lwc-dev-mobile-core'; -import { DevServerUtils } from './devServerUtils.js'; +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -class LwrConfigFile { - public identityToken?: string; -} +import { CryptoUtils } from '@salesforce/lwc-dev-mobile-core'; +import { LightningDevConfig } from './lightningDevConfig.js'; +import { ConfigVars } from './../configMeta.js'; export class IdentityUtils { - public static async updateServerConfigFileWithIdentityToken(): Promise { - const config = (await DevServerUtils.fetchServerConfigFileContent()) as LwrConfigFile; - if (config && !config.identityToken) { + public static async updateConfigWithIdentityToken(): Promise { + const token = await this.getIdentityToken(); + if (!token) { // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment - config.identityToken = CryptoUtils.generateIdentityToken(); - await DevServerUtils.writeServerConfigFileContent(config); + const generatedIdentityToken = CryptoUtils.generateIdentityToken(); + await this.writeIdentityToken(generatedIdentityToken); + } + return Promise.resolve(); + } + + public static async getIdentityToken(): Promise { + const config = await LightningDevConfig.create({ + isGlobal: false, + }); + await config.read(); + const identityToken = config.get(ConfigVars.LOCAL_WEB_SERVER_IDENTITY_TOKEN); + + if (identityToken) { + const identityTokenAsString = identityToken as string; + return Promise.resolve(identityTokenAsString); } + + return Promise.resolve(undefined); + } + + public static async writeIdentityToken(token: string): Promise { + const config = await LightningDevConfig.create({ + isGlobal: false, + }); + await config.read(); + config.set(ConfigVars.LOCAL_WEB_SERVER_IDENTITY_TOKEN, token); + config.writeSync(); + return Promise.resolve(); } } diff --git a/src/shared/lightningDevConfig.ts b/src/shared/lightningDevConfig.ts new file mode 100644 index 00000000..a0a8ae23 --- /dev/null +++ b/src/shared/lightningDevConfig.ts @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2024, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { ConfigFile } from '@salesforce/core'; + +export const LIGHTNING_DEV_CONFIG = 'lightningDevConfig.json'; + +export class LightningDevConfig extends ConfigFile { + public static getFileName(): string { + return LIGHTNING_DEV_CONFIG; + } +} diff --git a/test/shared/identityUtils.test.ts b/test/shared/identityUtils.test.ts index ceb6e1c5..64541e4d 100644 --- a/test/shared/identityUtils.test.ts +++ b/test/shared/identityUtils.test.ts @@ -5,10 +5,11 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ + import { expect } from 'chai'; import { TestContext } from '@salesforce/core/testSetup'; -import { DevServerUtils } from '../../src/shared/devServerUtils.js'; import { IdentityUtils } from '../../src/shared/identityUtils.js'; +import { LightningDevConfig } from '../../src/shared/lightningDevConfig.js'; describe('identityUtils', () => { const $$ = new TestContext(); @@ -17,48 +18,44 @@ describe('identityUtils', () => { $$.restore(); }); - it('updateServerConfigFileWithIdentityToken resolves if lwr.config.json is not found', async () => { - $$.SANDBOX.stub(DevServerUtils, 'getServerConfigFileLocation').returns('lwr.config.json'); + it('updateConfigWithIdentityToken resolves if token is found', async () => { + const fakeIdentityToken = 'fake identity token'; + $$.SANDBOX.stub(IdentityUtils, 'getIdentityToken').resolves(fakeIdentityToken); - const resolved = await IdentityUtils.updateServerConfigFileWithIdentityToken(); + const resolved = await IdentityUtils.updateConfigWithIdentityToken(); expect(resolved).to.equal(undefined); }); - it('updateServerConfigFileWithIdentityToken resolves if lwr.config.json has identity token', async () => { - $$.SANDBOX.stub(DevServerUtils, 'getServerConfigFileLocation').returns('lwr.config.json'); - $$.SANDBOX.stub(DevServerUtils, 'fetchServerConfigFileContent').returns({ identityToken: 'foo' }); + it('updateConfigWithIdentityToken resolves and writeIdentityToken is called when there is no token', async () => { + $$.SANDBOX.stub(IdentityUtils, 'getIdentityToken').resolves(undefined); + $$.SANDBOX.stub(IdentityUtils, 'writeIdentityToken').resolves(); - const resolved = await IdentityUtils.updateServerConfigFileWithIdentityToken(); + const resolved = await IdentityUtils.updateConfigWithIdentityToken(); expect(resolved).to.equal(undefined); }); - it('updateServerConfigFileWithIdentityToken resolves if lwr.config.json has identity token', async () => { - $$.SANDBOX.stub(DevServerUtils, 'getServerConfigFileLocation').returns('lwr.config.json'); - $$.SANDBOX.stub(DevServerUtils, 'fetchServerConfigFileContent').returns({ identityToken: 'foo' }); + it('getIdentityToken resolves to undefined when identity token is not available', async () => { + $$.SANDBOX.stub(LightningDevConfig.prototype, 'get').returns(undefined); - const resolved = await IdentityUtils.updateServerConfigFileWithIdentityToken(); + const resolved = await IdentityUtils.getIdentityToken(); expect(resolved).to.equal(undefined); }); - it('updateServerConfigFileWithIdentityToken resolves if lwr.config.json has no idenity token and creates one', async () => { - $$.SANDBOX.stub(DevServerUtils, 'getServerConfigFileLocation').returns('lwr.config.json'); - $$.SANDBOX.stub(DevServerUtils, 'fetchServerConfigFileContent').returns({}); - $$.SANDBOX.stub(DevServerUtils, 'writeServerConfigFileContent').resolves(); + it('getIdentityToken resolves to a string when identity token is available', async () => { + const fakeIdentityToken = 'fake identity token'; + $$.SANDBOX.stub(LightningDevConfig.prototype, 'get').returns(fakeIdentityToken); - const resolved = await IdentityUtils.updateServerConfigFileWithIdentityToken(); - expect(resolved).to.equal(undefined); + const resolved = await IdentityUtils.getIdentityToken(); + expect(resolved).to.equal(fakeIdentityToken); }); - it('updateServerConfigFileWithIdentityToken rejects if identity token can not be written to lwr.config.json', async () => { - const errorMessage = 'foo bar'; - $$.SANDBOX.stub(DevServerUtils, 'fetchServerConfigFileContent').returns({}); - $$.SANDBOX.stub(DevServerUtils, 'writeServerConfigFileContent').throws(new Error(errorMessage)); + it('writeIdentityToken resolves', async () => { + const fakeIdentityToken = 'fake identity token'; + $$.SANDBOX.stub(LightningDevConfig.prototype, 'get').returns(fakeIdentityToken); + $$.SANDBOX.stub(LightningDevConfig.prototype, 'set').returns(); + $$.SANDBOX.stub(LightningDevConfig.prototype, 'writeSync').returns({}); - try { - await IdentityUtils.updateServerConfigFileWithIdentityToken(); - } catch (err) { - const error = err as Error; - expect(error.message).to.equal(errorMessage); - } + const resolved = await IdentityUtils.getIdentityToken(); + expect(resolved).to.equal(fakeIdentityToken); }); }); diff --git a/test/shared/lightningDevConfig.test.ts b/test/shared/lightningDevConfig.test.ts new file mode 100644 index 00000000..eafa1115 --- /dev/null +++ b/test/shared/lightningDevConfig.test.ts @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2024, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { expect } from 'chai'; +import { LightningDevConfig, LIGHTNING_DEV_CONFIG } from '../../src/shared/lightningDevConfig.js'; + +describe('lightningDevConfig', () => { + it('getFileName returns LIGHTNING_DEV_CONFIG', async () => { + const config = LightningDevConfig.getFileName(); + + expect(config).to.equal(LIGHTNING_DEV_CONFIG); + }); +}); From d13752ce32a45e0709ee3bc7bd83c620112f4e78 Mon Sep 17 00:00:00 2001 From: Takashi Arai Date: Wed, 19 Jun 2024 00:04:20 -0700 Subject: [PATCH 12/26] chore: nit --- test/shared/identityUtils.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/test/shared/identityUtils.test.ts b/test/shared/identityUtils.test.ts index 64541e4d..83684fd6 100644 --- a/test/shared/identityUtils.test.ts +++ b/test/shared/identityUtils.test.ts @@ -5,7 +5,6 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ - import { expect } from 'chai'; import { TestContext } from '@salesforce/core/testSetup'; import { IdentityUtils } from '../../src/shared/identityUtils.js'; From 0c52aa9fbb7813002321ce8eacc280c32360b474 Mon Sep 17 00:00:00 2001 From: Takashi Arai Date: Wed, 19 Jun 2024 15:53:34 -0700 Subject: [PATCH 13/26] chore: remove unused files --- src/shared/devServerUtils.ts | 29 -------------------- src/shared/lightningDevConfig.ts | 16 ----------- test/shared/devServerUtils.test.ts | 38 -------------------------- test/shared/lightningDevConfig.test.ts | 17 ------------ 4 files changed, 100 deletions(-) delete mode 100644 src/shared/devServerUtils.ts delete mode 100644 src/shared/lightningDevConfig.ts delete mode 100644 test/shared/devServerUtils.test.ts delete mode 100644 test/shared/lightningDevConfig.test.ts diff --git a/src/shared/devServerUtils.ts b/src/shared/devServerUtils.ts deleted file mode 100644 index 80d4d904..00000000 --- a/src/shared/devServerUtils.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2024, salesforce.com, inc. - * All rights reserved. - * Licensed under the BSD 3-Clause license. - * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause - */ - -import path from 'node:path'; -import { CommonUtils } from '@salesforce/lwc-dev-mobile-core'; - -export class DevServerUtils { - public static getServerConfigFileLocation(): string { - // TODO: When extensibility of sfdx-project.json becomes clear, - // use that as a place for reading and writing this plugin's - // related values instead of lwr.config.json. - return path.join(process.cwd(), 'lwr.config.json'); - } - - public static fetchServerConfigFileContent(): unknown { - const filePath = DevServerUtils.getServerConfigFileLocation(); - return CommonUtils.loadJsonFromFile(filePath) as unknown; - } - - public static async writeServerConfigFileContent(config: unknown): Promise { - const filePath = DevServerUtils.getServerConfigFileLocation(); - // create or overwrite the config file - return CommonUtils.createTextFile(filePath, JSON.stringify(config, undefined, 2)); - } -} diff --git a/src/shared/lightningDevConfig.ts b/src/shared/lightningDevConfig.ts deleted file mode 100644 index a0a8ae23..00000000 --- a/src/shared/lightningDevConfig.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright (c) 2024, salesforce.com, inc. - * All rights reserved. - * Licensed under the BSD 3-Clause license. - * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause - */ - -import { ConfigFile } from '@salesforce/core'; - -export const LIGHTNING_DEV_CONFIG = 'lightningDevConfig.json'; - -export class LightningDevConfig extends ConfigFile { - public static getFileName(): string { - return LIGHTNING_DEV_CONFIG; - } -} diff --git a/test/shared/devServerUtils.test.ts b/test/shared/devServerUtils.test.ts deleted file mode 100644 index 0409db24..00000000 --- a/test/shared/devServerUtils.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2024, salesforce.com, inc. - * All rights reserved. - * Licensed under the BSD 3-Clause license. - * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause - */ - -import { TestContext } from '@salesforce/core/testSetup'; -import { expect } from 'chai'; -import { CommonUtils } from '@salesforce/lwc-dev-mobile-core'; -import { DevServerUtils } from '../../src/shared/devServerUtils.js'; - -describe('devServerUtils', () => { - const $$ = new TestContext(); - - afterEach(() => { - $$.restore(); - }); - - it('getServerConfigFileLocation returns a path joined from current working directory and lwr.config.json', async () => { - $$.SANDBOX.stub(process, 'cwd').returns('desktop'); - const configFileLocation = DevServerUtils.getServerConfigFileLocation(); - expect(configFileLocation).to.be.equal('desktop/lwr.config.json'); - }); - - it('fetchServerConfigFileContent returns an expected object', async () => { - $$.SANDBOX.stub(DevServerUtils, 'getServerConfigFileLocation').returns('foo'); - $$.SANDBOX.stub(CommonUtils, 'loadJsonFromFile').withArgs('foo').returns({ foo: 'bar' }); - const config = DevServerUtils.fetchServerConfigFileContent(); - expect(config).to.deep.equal({ foo: 'bar' }); - }); - - it('writeServerConfigFileContent calls CommonUtils.createTextFile', async () => { - $$.SANDBOX.stub(DevServerUtils, 'getServerConfigFileLocation').returns('foo'); - const stub = $$.SANDBOX.stub(CommonUtils, 'createTextFile').resolves(); - expect(stub.calledOnce); - }); -}); diff --git a/test/shared/lightningDevConfig.test.ts b/test/shared/lightningDevConfig.test.ts deleted file mode 100644 index eafa1115..00000000 --- a/test/shared/lightningDevConfig.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright (c) 2024, salesforce.com, inc. - * All rights reserved. - * Licensed under the BSD 3-Clause license. - * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause - */ - -import { expect } from 'chai'; -import { LightningDevConfig, LIGHTNING_DEV_CONFIG } from '../../src/shared/lightningDevConfig.js'; - -describe('lightningDevConfig', () => { - it('getFileName returns LIGHTNING_DEV_CONFIG', async () => { - const config = LightningDevConfig.getFileName(); - - expect(config).to.equal(LIGHTNING_DEV_CONFIG); - }); -}); From c6ed97be9f3dda63b232e212839c44c152449260 Mon Sep 17 00:00:00 2001 From: Takashi Arai Date: Wed, 19 Jun 2024 15:57:49 -0700 Subject: [PATCH 14/26] chore: remove validation --- src/configMeta.ts | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/src/configMeta.ts b/src/configMeta.ts index 0b070ffc..944e3ca9 100644 --- a/src/configMeta.ts +++ b/src/configMeta.ts @@ -5,7 +5,7 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import type { ConfigValue, ConfigPropertyMeta } from '@salesforce/core'; +import type { ConfigPropertyMeta } from '@salesforce/core'; export enum ConfigVars { /** @@ -21,20 +21,5 @@ export default [ description: 'The Base64-encoded identity token of the local web server', hidden: true, encrypted: true, - input: { - validator: (value: ConfigValue): boolean => { - // Ensure that `value` is a Base64-encoded string. - if (typeof value !== 'string') { - return false; - } - try { - const decoded = Buffer.from(value, 'base64').toString('base64'); - return decoded === value; - } catch (e) { - return false; - } - }, - failedMessage: `${ConfigVars.LOCAL_WEB_SERVER_IDENTITY_TOKEN} is not a Base64-encoded string!`, - }, }, ] as ConfigPropertyMeta[]; From 6e2707db49621edeba4cad515365d65b57a595a4 Mon Sep 17 00:00:00 2001 From: Takashi Arai Date: Wed, 19 Jun 2024 17:16:25 -0700 Subject: [PATCH 15/26] chore: use default local config. update tests --- src/shared/identityUtils.ts | 25 +++++++++---------------- test/shared/identityUtils.test.ts | 30 +++++++++++++++++++++--------- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/src/shared/identityUtils.ts b/src/shared/identityUtils.ts index 363dcd67..a335b678 100644 --- a/src/shared/identityUtils.ts +++ b/src/shared/identityUtils.ts @@ -5,13 +5,9 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ - import { CryptoUtils } from '@salesforce/lwc-dev-mobile-core'; -import { LightningDevConfig } from './lightningDevConfig.js'; -import { ConfigVars } from './../configMeta.js'; +import { Config, ConfigAggregator } from '@salesforce/core'; +import configMeta, { ConfigVars } from './../configMeta.js'; export class IdentityUtils { public static async updateConfigWithIdentityToken(): Promise { @@ -25,11 +21,10 @@ export class IdentityUtils { } public static async getIdentityToken(): Promise { - const config = await LightningDevConfig.create({ - isGlobal: false, - }); - await config.read(); - const identityToken = config.get(ConfigVars.LOCAL_WEB_SERVER_IDENTITY_TOKEN); + const config = await ConfigAggregator.create({ customConfigMeta: configMeta }); + // Need to reload to make sure the values read are decrypted + await config.reload(); + const identityToken = config.getPropertyValue(ConfigVars.LOCAL_WEB_SERVER_IDENTITY_TOKEN); if (identityToken) { const identityTokenAsString = identityToken as string; @@ -40,12 +35,10 @@ export class IdentityUtils { } public static async writeIdentityToken(token: string): Promise { - const config = await LightningDevConfig.create({ - isGlobal: false, - }); - await config.read(); + const config = await Config.create({ isGlobal: false }); + Config.addAllowedProperties(configMeta); config.set(ConfigVars.LOCAL_WEB_SERVER_IDENTITY_TOKEN, token); - config.writeSync(); + await config.write(); return Promise.resolve(); } } diff --git a/test/shared/identityUtils.test.ts b/test/shared/identityUtils.test.ts index 83684fd6..d4e5ed72 100644 --- a/test/shared/identityUtils.test.ts +++ b/test/shared/identityUtils.test.ts @@ -5,10 +5,14 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ + import { expect } from 'chai'; +import { Config, ConfigAggregator } from '@salesforce/core'; import { TestContext } from '@salesforce/core/testSetup'; import { IdentityUtils } from '../../src/shared/identityUtils.js'; -import { LightningDevConfig } from '../../src/shared/lightningDevConfig.js'; +import { ConfigVars } from '../../src/configMeta.js'; describe('identityUtils', () => { const $$ = new TestContext(); @@ -34,15 +38,19 @@ describe('identityUtils', () => { }); it('getIdentityToken resolves to undefined when identity token is not available', async () => { - $$.SANDBOX.stub(LightningDevConfig.prototype, 'get').returns(undefined); - + $$.SANDBOX.stub(ConfigAggregator, 'create').resolves(ConfigAggregator.prototype); + $$.SANDBOX.stub(ConfigAggregator.prototype, 'reload').resolves(); + $$.SANDBOX.stub(ConfigAggregator.prototype, 'getPropertyValue').returns(undefined); const resolved = await IdentityUtils.getIdentityToken(); + expect(resolved).to.equal(undefined); }); it('getIdentityToken resolves to a string when identity token is available', async () => { const fakeIdentityToken = 'fake identity token'; - $$.SANDBOX.stub(LightningDevConfig.prototype, 'get').returns(fakeIdentityToken); + $$.SANDBOX.stub(ConfigAggregator, 'create').resolves(ConfigAggregator.prototype); + $$.SANDBOX.stub(ConfigAggregator.prototype, 'reload').resolves(); + $$.SANDBOX.stub(ConfigAggregator.prototype, 'getPropertyValue').returns(fakeIdentityToken); const resolved = await IdentityUtils.getIdentityToken(); expect(resolved).to.equal(fakeIdentityToken); @@ -50,11 +58,15 @@ describe('identityUtils', () => { it('writeIdentityToken resolves', async () => { const fakeIdentityToken = 'fake identity token'; - $$.SANDBOX.stub(LightningDevConfig.prototype, 'get').returns(fakeIdentityToken); - $$.SANDBOX.stub(LightningDevConfig.prototype, 'set').returns(); - $$.SANDBOX.stub(LightningDevConfig.prototype, 'writeSync').returns({}); + $$.SANDBOX.stub(Config, 'create').withArgs($$.SANDBOX.match.any).resolves(Config.prototype); + $$.SANDBOX.stub(Config, 'addAllowedProperties').withArgs($$.SANDBOX.match.any); + $$.SANDBOX.stub(Config.prototype, 'set').withArgs( + ConfigVars.LOCAL_WEB_SERVER_IDENTITY_TOKEN, + $$.SANDBOX.match.string + ); + $$.SANDBOX.stub(Config.prototype, 'write').resolves(); - const resolved = await IdentityUtils.getIdentityToken(); - expect(resolved).to.equal(fakeIdentityToken); + const resolved = await IdentityUtils.writeIdentityToken(fakeIdentityToken); + expect(resolved).to.equal(undefined); }); }); From 803a2f6597a6fbf681c652ddbb48d8ce9e4d25ef Mon Sep 17 00:00:00 2001 From: Takashi Arai Date: Wed, 19 Jun 2024 17:26:21 -0700 Subject: [PATCH 16/26] chore: update after code review --- src/configMeta.ts | 2 +- src/shared/identityUtils.ts | 9 +-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/configMeta.ts b/src/configMeta.ts index 944e3ca9..a0015ea3 100644 --- a/src/configMeta.ts +++ b/src/configMeta.ts @@ -7,7 +7,7 @@ import type { ConfigPropertyMeta } from '@salesforce/core'; -export enum ConfigVars { +export const enum ConfigVars { /** * The Base64-encoded identity token of the local web server, used to * validate the web server's identity to the hmr-client. diff --git a/src/shared/identityUtils.ts b/src/shared/identityUtils.ts index a335b678..821d8490 100644 --- a/src/shared/identityUtils.ts +++ b/src/shared/identityUtils.ts @@ -17,7 +17,6 @@ export class IdentityUtils { const generatedIdentityToken = CryptoUtils.generateIdentityToken(); await this.writeIdentityToken(generatedIdentityToken); } - return Promise.resolve(); } public static async getIdentityToken(): Promise { @@ -26,12 +25,7 @@ export class IdentityUtils { await config.reload(); const identityToken = config.getPropertyValue(ConfigVars.LOCAL_WEB_SERVER_IDENTITY_TOKEN); - if (identityToken) { - const identityTokenAsString = identityToken as string; - return Promise.resolve(identityTokenAsString); - } - - return Promise.resolve(undefined); + return identityToken as string; } public static async writeIdentityToken(token: string): Promise { @@ -39,6 +33,5 @@ export class IdentityUtils { Config.addAllowedProperties(configMeta); config.set(ConfigVars.LOCAL_WEB_SERVER_IDENTITY_TOKEN, token); await config.write(); - return Promise.resolve(); } } From eec1bba0a49b2f86b62b468415beb0719d70721e Mon Sep 17 00:00:00 2001 From: Takashi Arai Date: Thu, 20 Jun 2024 10:44:49 -0700 Subject: [PATCH 17/26] Update src/shared/identityUtils.ts Co-authored-by: Abdulsattar Mohammed --- src/shared/identityUtils.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/shared/identityUtils.ts b/src/shared/identityUtils.ts index 821d8490..aea292c8 100644 --- a/src/shared/identityUtils.ts +++ b/src/shared/identityUtils.ts @@ -10,13 +10,14 @@ import { Config, ConfigAggregator } from '@salesforce/core'; import configMeta, { ConfigVars } from './../configMeta.js'; export class IdentityUtils { - public static async updateConfigWithIdentityToken(): Promise { - const token = await this.getIdentityToken(); + public static async getOrCreateIdentityToken(): Promise { + let token = await this.getIdentityToken(); if (!token) { // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment - const generatedIdentityToken = CryptoUtils.generateIdentityToken(); - await this.writeIdentityToken(generatedIdentityToken); + token = CryptoUtils.generateIdentityToken(); + await this.writeIdentityToken(token); } + return token; } public static async getIdentityToken(): Promise { From 84de0e7765e29cab6f5b5ca2c300def0ca49ff41 Mon Sep 17 00:00:00 2001 From: Takashi Arai Date: Thu, 20 Jun 2024 10:54:44 -0700 Subject: [PATCH 18/26] chore: update tests --- test/shared/identityUtils.test.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/test/shared/identityUtils.test.ts b/test/shared/identityUtils.test.ts index d4e5ed72..6ca502ff 100644 --- a/test/shared/identityUtils.test.ts +++ b/test/shared/identityUtils.test.ts @@ -11,6 +11,7 @@ import { expect } from 'chai'; import { Config, ConfigAggregator } from '@salesforce/core'; import { TestContext } from '@salesforce/core/testSetup'; +import { CryptoUtils } from '@salesforce/lwc-dev-mobile-core'; import { IdentityUtils } from '../../src/shared/identityUtils.js'; import { ConfigVars } from '../../src/configMeta.js'; @@ -21,20 +22,23 @@ describe('identityUtils', () => { $$.restore(); }); - it('updateConfigWithIdentityToken resolves if token is found', async () => { + it('getOrCreateIdentityToken resolves if token is found', async () => { const fakeIdentityToken = 'fake identity token'; $$.SANDBOX.stub(IdentityUtils, 'getIdentityToken').resolves(fakeIdentityToken); - const resolved = await IdentityUtils.updateConfigWithIdentityToken(); - expect(resolved).to.equal(undefined); + const resolved = await IdentityUtils.getOrCreateIdentityToken(); + expect(resolved).to.equal(fakeIdentityToken); }); - it('updateConfigWithIdentityToken resolves and writeIdentityToken is called when there is no token', async () => { + it('getOrCreateIdentityToken resolves and writeIdentityToken is called when there is no token', async () => { + const fakeIdentityToken = 'fake identity token'; $$.SANDBOX.stub(IdentityUtils, 'getIdentityToken').resolves(undefined); - $$.SANDBOX.stub(IdentityUtils, 'writeIdentityToken').resolves(); + $$.SANDBOX.stub(CryptoUtils, 'generateIdentityToken').resolves(fakeIdentityToken); + const writeIdentityTokenStub = $$.SANDBOX.stub(IdentityUtils, 'writeIdentityToken').resolves(); - const resolved = await IdentityUtils.updateConfigWithIdentityToken(); - expect(resolved).to.equal(undefined); + const resolved = await IdentityUtils.getOrCreateIdentityToken(); + expect(resolved).to.equal(fakeIdentityToken); + expect(writeIdentityTokenStub.calledOnce).to.be.true; }); it('getIdentityToken resolves to undefined when identity token is not available', async () => { From f842060f496cbc25a54e7d7c69fb23213dd0dafb Mon Sep 17 00:00:00 2001 From: rax-it Date: Thu, 20 Jun 2024 15:59:17 -0700 Subject: [PATCH 19/26] feat: add local dev server port config --- src/configMeta.ts | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/configMeta.ts b/src/configMeta.ts index a0015ea3..8c78ab57 100644 --- a/src/configMeta.ts +++ b/src/configMeta.ts @@ -5,7 +5,7 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import type { ConfigPropertyMeta } from '@salesforce/core'; +import type { ConfigPropertyMeta, ConfigValue } from '@salesforce/core'; export const enum ConfigVars { /** @@ -13,6 +13,11 @@ export const enum ConfigVars { * validate the web server's identity to the hmr-client. */ LOCAL_WEB_SERVER_IDENTITY_TOKEN = 'local-web-server-identity-token', + + /** + * The port number of the local dev server. + */ + LOCAL_DEV_SERVER_PORT = 'local-dev-server-port', } export default [ @@ -22,4 +27,24 @@ export default [ hidden: true, encrypted: true, }, + { + key: ConfigVars.LOCAL_DEV_SERVER_PORT, + description: 'The port number of the local dev server', + input: { + validator: (value: ConfigValue): boolean => { + // eslint-disable-next-line no-console + console.log('Validating: ', value, typeof value); + const parsedPort = parseInt(value as string, 10); + + if (isNaN(parsedPort)) { + return false; + } + if (parsedPort < 1 || parsedPort > 65535) { + return false; + } + return true; + }, + failedMessage: 'Must be a number between 1 and 65535', + }, + }, ] as ConfigPropertyMeta[]; From 3122528f8678f3526a9a9ae8c2ccdb128a64bb93 Mon Sep 17 00:00:00 2001 From: rax-it Date: Thu, 20 Jun 2024 16:00:21 -0700 Subject: [PATCH 20/26] feat: add local dev server port config --- src/shared/lwcDevServerUtils.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/shared/lwcDevServerUtils.ts diff --git a/src/shared/lwcDevServerUtils.ts b/src/shared/lwcDevServerUtils.ts new file mode 100644 index 00000000..b105119e --- /dev/null +++ b/src/shared/lwcDevServerUtils.ts @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2024, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { Config } from '@salesforce/core'; +import configMeta, { ConfigVars } from '../configMeta.js'; + +export const LOCAL_DEV_SERVER_DEFAULT_PORT = 8081; + +export class LwcDevServerUtils { + public static async getLocalDevServerPort(): Promise { + // Should this be global or local? + const config = await Config.create({ isGlobal: true }); + Config.addAllowedProperties(configMeta); + const configPort = config.get(ConfigVars.LOCAL_DEV_SERVER_PORT) as number; + + return configPort || LOCAL_DEV_SERVER_DEFAULT_PORT; + } +} From 953e825c15c7aa0c7e1e59e76cd8b8524dccea36 Mon Sep 17 00:00:00 2001 From: rax-it Date: Fri, 21 Jun 2024 10:14:21 -0700 Subject: [PATCH 21/26] chore: use lwc utils --- .gitignore | 6 +++++- src/commands/lightning/preview/app.ts | 6 +++++- src/configMeta.ts | 5 +---- src/lwc-dev-server/index.ts | 9 ++++----- src/shared/lwcDevServerUtils.ts | 2 +- 5 files changed, 16 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index a8f161a6..bd16aa0c 100644 --- a/.gitignore +++ b/.gitignore @@ -52,4 +52,8 @@ node_modules # lwr __lwr_cache__ app -bld \ No newline at end of file +bld + +# sf cli +.sf +.sfdx \ No newline at end of file diff --git a/src/commands/lightning/preview/app.ts b/src/commands/lightning/preview/app.ts index 1f88ff45..8de58306 100644 --- a/src/commands/lightning/preview/app.ts +++ b/src/commands/lightning/preview/app.ts @@ -21,6 +21,7 @@ import chalk from 'chalk'; import { OrgUtils } from '../../../shared/orgUtils.js'; import { startLWCServer } from '../../../lwc-dev-server/index.js'; import { PreviewUtils } from '../../../shared/previewUtils.js'; +import { LwcDevServerUtils } from '../../../shared/lwcDevServerUtils.js'; Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); const messages = Messages.loadMessages('@salesforce/plugin-lightning-dev', 'lightning.preview.app'); @@ -149,7 +150,10 @@ export default class LightningPreviewApp extends SfCommand { logger.debug('Determining Local Dev Server url'); // todo: figure out how to make the port dynamic instead of hard-coded value here - const ldpServerUrl = PreviewUtils.generateWebSocketUrlForLocalDevServer(platform, '8081'); + const ldpServerUrl = PreviewUtils.generateWebSocketUrlForLocalDevServer( + platform, + `${await LwcDevServerUtils.getLocalDevServerPort()}` + ); logger.debug(`Local Dev Server url is ${ldpServerUrl}`); let appId: string | undefined; diff --git a/src/configMeta.ts b/src/configMeta.ts index 8c78ab57..1f9561d6 100644 --- a/src/configMeta.ts +++ b/src/configMeta.ts @@ -36,10 +36,7 @@ export default [ console.log('Validating: ', value, typeof value); const parsedPort = parseInt(value as string, 10); - if (isNaN(parsedPort)) { - return false; - } - if (parsedPort < 1 || parsedPort > 65535) { + if (isNaN(parsedPort) || parsedPort < 1 || parsedPort > 65535) { return false; } return true; diff --git a/src/lwc-dev-server/index.ts b/src/lwc-dev-server/index.ts index 9d1d69f9..75385bc4 100644 --- a/src/lwc-dev-server/index.ts +++ b/src/lwc-dev-server/index.ts @@ -10,8 +10,7 @@ import path from 'node:path'; import process from 'node:process'; import { LWCServer, LogLevel, ServerConfig, Workspace, startLwcDevServer } from '@lwc/lwc-dev-server'; import { Logger } from '@salesforce/core'; - -const DEV_SERVER_PORT = 8081; +import { LwcDevServerUtils } from '../shared/lwcDevServerUtils.js'; /** * Map sf cli log level to lwc dev server log level @@ -39,7 +38,7 @@ function mapLogLevel(cliLogLevel: number): number { } } -function createLWCServerConfig(rootDir: string, logger: Logger): ServerConfig { +async function createLWCServerConfig(rootDir: string, logger: Logger): Promise { const sfdxConfig = path.resolve(rootDir, 'sfdx-project.json'); if (!existsSync(sfdxConfig) || !lstatSync(sfdxConfig).isFile()) { @@ -67,7 +66,7 @@ function createLWCServerConfig(rootDir: string, logger: Logger): ServerConfig { return { rootDir, - port: DEV_SERVER_PORT, + port: await LwcDevServerUtils.getLocalDevServerPort(), protocol: 'wss', host: 'localhost', paths: namespacePaths, @@ -78,7 +77,7 @@ function createLWCServerConfig(rootDir: string, logger: Logger): ServerConfig { } export async function startLWCServer(rootDir: string, logger: Logger): Promise { - const config = createLWCServerConfig(rootDir, logger); + const config = await createLWCServerConfig(rootDir, logger); logger.trace(`Starting LWC Dev Server with config: ${JSON.stringify(config)}`); let lwcDevServer: LWCServer | null = await startLwcDevServer(config); diff --git a/src/shared/lwcDevServerUtils.ts b/src/shared/lwcDevServerUtils.ts index b105119e..bdb0f543 100644 --- a/src/shared/lwcDevServerUtils.ts +++ b/src/shared/lwcDevServerUtils.ts @@ -13,7 +13,7 @@ export const LOCAL_DEV_SERVER_DEFAULT_PORT = 8081; export class LwcDevServerUtils { public static async getLocalDevServerPort(): Promise { // Should this be global or local? - const config = await Config.create({ isGlobal: true }); + const config = await Config.create({ isGlobal: false }); Config.addAllowedProperties(configMeta); const configPort = config.get(ConfigVars.LOCAL_DEV_SERVER_PORT) as number; From a830870ec1eaccdfba03ac5740434133cc6dbccb Mon Sep 17 00:00:00 2001 From: rax-it Date: Fri, 21 Jun 2024 12:05:58 -0700 Subject: [PATCH 22/26] chore: add support for workspace variable --- src/configMeta.ts | 31 +++++++++++++++++++++++++++++-- src/lwc-dev-server/index.ts | 10 +++++----- src/shared/lwcDevServerUtils.ts | 21 ++++++++++++++++++--- 3 files changed, 52 insertions(+), 10 deletions(-) diff --git a/src/configMeta.ts b/src/configMeta.ts index 1f9561d6..bc4109dc 100644 --- a/src/configMeta.ts +++ b/src/configMeta.ts @@ -6,6 +6,7 @@ */ import type { ConfigPropertyMeta, ConfigValue } from '@salesforce/core'; +import { Workspace } from '@lwc/lwc-dev-server'; export const enum ConfigVars { /** @@ -18,6 +19,11 @@ export const enum ConfigVars { * The port number of the local dev server. */ LOCAL_DEV_SERVER_PORT = 'local-dev-server-port', + + /** + * The Workspace name of the local dev server. + */ + LOCAL_DEV_SERVER_WORKSPACE = 'local-dev-server-workspace', } export default [ @@ -32,8 +38,10 @@ export default [ description: 'The port number of the local dev server', input: { validator: (value: ConfigValue): boolean => { - // eslint-disable-next-line no-console - console.log('Validating: ', value, typeof value); + if (!value) { + return false; + } + const parsedPort = parseInt(value as string, 10); if (isNaN(parsedPort) || parsedPort < 1 || parsedPort > 65535) { @@ -44,4 +52,23 @@ export default [ failedMessage: 'Must be a number between 1 and 65535', }, }, + { + key: ConfigVars.LOCAL_DEV_SERVER_WORKSPACE, + description: 'The workspace name of the local dev server', + input: { + validator: (value: ConfigValue): boolean => { + if (!value) { + return false; + } + + const workspace = value as Workspace; + + if (workspace === Workspace.SfCli || workspace === Workspace.Mrt) { + return true; + } + return false; + }, + failedMessage: 'Valid workspace value is "SalesforceCLI" OR "mrt"', + }, + }, ] as ConfigPropertyMeta[]; diff --git a/src/lwc-dev-server/index.ts b/src/lwc-dev-server/index.ts index 75385bc4..dc852d5a 100644 --- a/src/lwc-dev-server/index.ts +++ b/src/lwc-dev-server/index.ts @@ -8,7 +8,7 @@ import { existsSync, lstatSync, readFileSync } from 'node:fs'; import path from 'node:path'; import process from 'node:process'; -import { LWCServer, LogLevel, ServerConfig, Workspace, startLwcDevServer } from '@lwc/lwc-dev-server'; +import { LWCServer, LogLevel, ServerConfig, startLwcDevServer } from '@lwc/lwc-dev-server'; import { Logger } from '@salesforce/core'; import { LwcDevServerUtils } from '../shared/lwcDevServerUtils.js'; @@ -38,7 +38,7 @@ function mapLogLevel(cliLogLevel: number): number { } } -async function createLWCServerConfig(rootDir: string, logger: Logger): Promise { +function createLWCServerConfig(rootDir: string, logger: Logger): ServerConfig { const sfdxConfig = path.resolve(rootDir, 'sfdx-project.json'); if (!existsSync(sfdxConfig) || !lstatSync(sfdxConfig).isFile()) { @@ -66,18 +66,18 @@ async function createLWCServerConfig(rootDir: string, logger: Logger): Promise { - const config = await createLWCServerConfig(rootDir, logger); + const config = createLWCServerConfig(rootDir, logger); logger.trace(`Starting LWC Dev Server with config: ${JSON.stringify(config)}`); let lwcDevServer: LWCServer | null = await startLwcDevServer(config); diff --git a/src/shared/lwcDevServerUtils.ts b/src/shared/lwcDevServerUtils.ts index bdb0f543..1215665a 100644 --- a/src/shared/lwcDevServerUtils.ts +++ b/src/shared/lwcDevServerUtils.ts @@ -5,18 +5,33 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ +import { Workspace } from '@lwc/lwc-dev-server'; import { Config } from '@salesforce/core'; import configMeta, { ConfigVars } from '../configMeta.js'; export const LOCAL_DEV_SERVER_DEFAULT_PORT = 8081; +export const LOCAL_DEV_SERVER_DEFAULT_WORKSPACE = Workspace.SfCli; export class LwcDevServerUtils { - public static async getLocalDevServerPort(): Promise { + static #config: Config; + + public static async init(): Promise { // Should this be global or local? - const config = await Config.create({ isGlobal: false }); + this.#config = await Config.create({ isGlobal: false }); Config.addAllowedProperties(configMeta); - const configPort = config.get(ConfigVars.LOCAL_DEV_SERVER_PORT) as number; + } + + public static getLocalDevServerPort(): number { + const configPort = this.#config.get(ConfigVars.LOCAL_DEV_SERVER_PORT) as number; return configPort || LOCAL_DEV_SERVER_DEFAULT_PORT; } + + public static getLocalDevServerWorkspace(): Workspace { + const configWorkspace = this.#config.get(ConfigVars.LOCAL_DEV_SERVER_WORKSPACE) as Workspace; + + return configWorkspace || LOCAL_DEV_SERVER_DEFAULT_WORKSPACE; + } } + +await LwcDevServerUtils.init(); From ad671da8f29e95a641ff88ed8a2efafecefe1dea Mon Sep 17 00:00:00 2001 From: rax-it Date: Fri, 21 Jun 2024 12:08:05 -0700 Subject: [PATCH 23/26] chore: remove unnecessary await --- src/commands/lightning/preview/app.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/lightning/preview/app.ts b/src/commands/lightning/preview/app.ts index 8de58306..58654780 100644 --- a/src/commands/lightning/preview/app.ts +++ b/src/commands/lightning/preview/app.ts @@ -152,7 +152,7 @@ export default class LightningPreviewApp extends SfCommand { // todo: figure out how to make the port dynamic instead of hard-coded value here const ldpServerUrl = PreviewUtils.generateWebSocketUrlForLocalDevServer( platform, - `${await LwcDevServerUtils.getLocalDevServerPort()}` + `${LwcDevServerUtils.getLocalDevServerPort()}` ); logger.debug(`Local Dev Server url is ${ldpServerUrl}`); From 6f6a43be5ea211da348586e75a31bb3ce6dcf25f Mon Sep 17 00:00:00 2001 From: rax-it Date: Mon, 24 Jun 2024 10:59:40 -0700 Subject: [PATCH 24/26] chore: remove top level await --- src/commands/lightning/preview/app.ts | 2 +- src/lwc-dev-server/index.ts | 8 ++++---- src/shared/lwcDevServerUtils.ts | 19 +++++++++++-------- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/commands/lightning/preview/app.ts b/src/commands/lightning/preview/app.ts index 58654780..8de58306 100644 --- a/src/commands/lightning/preview/app.ts +++ b/src/commands/lightning/preview/app.ts @@ -152,7 +152,7 @@ export default class LightningPreviewApp extends SfCommand { // todo: figure out how to make the port dynamic instead of hard-coded value here const ldpServerUrl = PreviewUtils.generateWebSocketUrlForLocalDevServer( platform, - `${LwcDevServerUtils.getLocalDevServerPort()}` + `${await LwcDevServerUtils.getLocalDevServerPort()}` ); logger.debug(`Local Dev Server url is ${ldpServerUrl}`); diff --git a/src/lwc-dev-server/index.ts b/src/lwc-dev-server/index.ts index dc852d5a..9dbd344b 100644 --- a/src/lwc-dev-server/index.ts +++ b/src/lwc-dev-server/index.ts @@ -38,7 +38,7 @@ function mapLogLevel(cliLogLevel: number): number { } } -function createLWCServerConfig(rootDir: string, logger: Logger): ServerConfig { +async function createLWCServerConfig(rootDir: string, logger: Logger): Promise { const sfdxConfig = path.resolve(rootDir, 'sfdx-project.json'); if (!existsSync(sfdxConfig) || !lstatSync(sfdxConfig).isFile()) { @@ -66,18 +66,18 @@ function createLWCServerConfig(rootDir: string, logger: Logger): ServerConfig { return { rootDir, - port: LwcDevServerUtils.getLocalDevServerPort(), + port: await LwcDevServerUtils.getLocalDevServerPort(), protocol: 'wss', host: 'localhost', paths: namespacePaths, - workspace: LwcDevServerUtils.getLocalDevServerWorkspace(), + workspace: await LwcDevServerUtils.getLocalDevServerWorkspace(), targets: ['LEX'], // should this be something else? logLevel: mapLogLevel(logger.getLevel()), }; } export async function startLWCServer(rootDir: string, logger: Logger): Promise { - const config = createLWCServerConfig(rootDir, logger); + const config = await createLWCServerConfig(rootDir, logger); logger.trace(`Starting LWC Dev Server with config: ${JSON.stringify(config)}`); let lwcDevServer: LWCServer | null = await startLwcDevServer(config); diff --git a/src/shared/lwcDevServerUtils.ts b/src/shared/lwcDevServerUtils.ts index 1215665a..2cdf4dc6 100644 --- a/src/shared/lwcDevServerUtils.ts +++ b/src/shared/lwcDevServerUtils.ts @@ -15,23 +15,26 @@ export const LOCAL_DEV_SERVER_DEFAULT_WORKSPACE = Workspace.SfCli; export class LwcDevServerUtils { static #config: Config; - public static async init(): Promise { - // Should this be global or local? + public static async getConfig(): Promise { + if (this.#config) { + return this.#config; + } this.#config = await Config.create({ isGlobal: false }); Config.addAllowedProperties(configMeta); + return this.#config; } - public static getLocalDevServerPort(): number { - const configPort = this.#config.get(ConfigVars.LOCAL_DEV_SERVER_PORT) as number; + public static async getLocalDevServerPort(): Promise { + const config = await this.getConfig(); + const configPort = config.get(ConfigVars.LOCAL_DEV_SERVER_PORT) as number; return configPort || LOCAL_DEV_SERVER_DEFAULT_PORT; } - public static getLocalDevServerWorkspace(): Workspace { - const configWorkspace = this.#config.get(ConfigVars.LOCAL_DEV_SERVER_WORKSPACE) as Workspace; + public static async getLocalDevServerWorkspace(): Promise { + const config = await this.getConfig(); + const configWorkspace = config.get(ConfigVars.LOCAL_DEV_SERVER_WORKSPACE) as Workspace; return configWorkspace || LOCAL_DEV_SERVER_DEFAULT_WORKSPACE; } } - -await LwcDevServerUtils.init(); From 839c0d957646667e0bc61f354b3f425902936ce7 Mon Sep 17 00:00:00 2001 From: rax-it Date: Mon, 24 Jun 2024 16:03:20 -0700 Subject: [PATCH 25/26] chore: address feedback --- messages/shared.utils.md | 7 +++++++ src/configMeta.ts | 10 ++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 messages/shared.utils.md diff --git a/messages/shared.utils.md b/messages/shared.utils.md new file mode 100644 index 00000000..b73c9233 --- /dev/null +++ b/messages/shared.utils.md @@ -0,0 +1,7 @@ +# lwc-dev-server-utils.port-message + +Must be a number between 1 and 65535 + +# lwc-dev-server-utils.workspace-message + +Valid workspace value is "SalesforceCLI" OR "mrt" diff --git a/src/configMeta.ts b/src/configMeta.ts index bc4109dc..535cd93b 100644 --- a/src/configMeta.ts +++ b/src/configMeta.ts @@ -7,6 +7,12 @@ import type { ConfigPropertyMeta, ConfigValue } from '@salesforce/core'; import { Workspace } from '@lwc/lwc-dev-server'; +import { Messages } from '@salesforce/core'; + +Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); +const messages = Messages.loadMessages('@salesforce/plugin-lightning-dev', 'shared.utils'); +const LOCAL_DEV_SERVER_PORT_MESSAGE = messages.getMessage('lwc-dev-server-utils.port-message'); +const LOCAL_DEV_SERVER_WORKSPACE_MESSAGE = messages.getMessage('lwc-dev-server-utils.workspace-message'); export const enum ConfigVars { /** @@ -49,7 +55,7 @@ export default [ } return true; }, - failedMessage: 'Must be a number between 1 and 65535', + failedMessage: LOCAL_DEV_SERVER_PORT_MESSAGE, }, }, { @@ -68,7 +74,7 @@ export default [ } return false; }, - failedMessage: 'Valid workspace value is "SalesforceCLI" OR "mrt"', + failedMessage: LOCAL_DEV_SERVER_WORKSPACE_MESSAGE, }, }, ] as ConfigPropertyMeta[]; From d59fbc8df63c7c32bb797ed71de15d089f2a836d Mon Sep 17 00:00:00 2001 From: rax-it Date: Mon, 24 Jun 2024 16:08:53 -0700 Subject: [PATCH 26/26] chore: move messages --- messages/shared.utils.md | 12 ++++++++++++ src/configMeta.ts | 9 ++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/messages/shared.utils.md b/messages/shared.utils.md index b73c9233..19ae6c35 100644 --- a/messages/shared.utils.md +++ b/messages/shared.utils.md @@ -1,7 +1,19 @@ +# lwc-dev-server-utils.port-desc + +The port number of the local dev server + # lwc-dev-server-utils.port-message Must be a number between 1 and 65535 +# lwc-dev-server-utils.workspace-desc + +The workspace name of the local lwc dev server + # lwc-dev-server-utils.workspace-message Valid workspace value is "SalesforceCLI" OR "mrt" + +# identity-utils.token-desc + +The Base64-encoded identity token of the local web server diff --git a/src/configMeta.ts b/src/configMeta.ts index 535cd93b..b81d852f 100644 --- a/src/configMeta.ts +++ b/src/configMeta.ts @@ -11,7 +11,10 @@ import { Messages } from '@salesforce/core'; Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); const messages = Messages.loadMessages('@salesforce/plugin-lightning-dev', 'shared.utils'); +const IDENTITY_TOKEN_DESC = messages.getMessage('identity-utils.token-desc'); +const LOCAL_DEV_SERVER_PORT_DESC = messages.getMessage('lwc-dev-server-utils.port-desc'); const LOCAL_DEV_SERVER_PORT_MESSAGE = messages.getMessage('lwc-dev-server-utils.port-message'); +const LOCAL_DEV_SERVER_WORKSPACE_DESC = messages.getMessage('lwc-dev-server-utils.workspace-desc'); const LOCAL_DEV_SERVER_WORKSPACE_MESSAGE = messages.getMessage('lwc-dev-server-utils.workspace-message'); export const enum ConfigVars { @@ -35,13 +38,13 @@ export const enum ConfigVars { export default [ { key: ConfigVars.LOCAL_WEB_SERVER_IDENTITY_TOKEN, - description: 'The Base64-encoded identity token of the local web server', + description: IDENTITY_TOKEN_DESC, hidden: true, encrypted: true, }, { key: ConfigVars.LOCAL_DEV_SERVER_PORT, - description: 'The port number of the local dev server', + description: LOCAL_DEV_SERVER_PORT_DESC, input: { validator: (value: ConfigValue): boolean => { if (!value) { @@ -60,7 +63,7 @@ export default [ }, { key: ConfigVars.LOCAL_DEV_SERVER_WORKSPACE, - description: 'The workspace name of the local dev server', + description: LOCAL_DEV_SERVER_WORKSPACE_DESC, input: { validator: (value: ConfigValue): boolean => { if (!value) {