From 6ea9a16a197e3ef7618befd80f72a07513c2a699 Mon Sep 17 00:00:00 2001 From: Scott Mo Date: Wed, 21 Jan 2026 21:48:40 -0600 Subject: [PATCH 01/19] feat: dxp site generator --- command-snapshot.json | 8 + messages/dxpsiteBuildYourOwnLwr.md | 41 +++++ package.json | 8 + .../dxpsite/generate/build-your-own-lwr.ts | 75 ++++++++ .../generate/build-your-own-lwr/create.nut.ts | 173 ++++++++++++++++++ 5 files changed, 305 insertions(+) create mode 100644 messages/dxpsiteBuildYourOwnLwr.md create mode 100644 src/commands/dxpsite/generate/build-your-own-lwr.ts create mode 100644 test/commands/force/dxpsite/generate/build-your-own-lwr/create.nut.ts diff --git a/command-snapshot.json b/command-snapshot.json index eee76b27..232e99f5 100644 --- a/command-snapshot.json +++ b/command-snapshot.json @@ -23,6 +23,14 @@ "flags": ["api-version", "event", "flags-dir", "json", "loglevel", "name", "output-dir", "sobject", "template"], "plugin": "@salesforce/plugin-templates" }, + { + "alias": [], + "command": "dxpsite:generate:build-your-own-lwr", + "flagAliases": [], + "flagChars": ["d", "n", "p"], + "flags": ["flags-dir", "json", "name", "output-dir", "url-path-prefix"], + "plugin": "@salesforce/plugin-templates" + }, { "alias": ["force:lightning:app:create"], "command": "lightning:generate:app", diff --git a/messages/dxpsiteBuildYourOwnLwr.md b/messages/dxpsiteBuildYourOwnLwr.md new file mode 100644 index 00000000..c765bee2 --- /dev/null +++ b/messages/dxpsiteBuildYourOwnLwr.md @@ -0,0 +1,41 @@ +# summary + +Generate a Lightning Web Runtime (LWR) Build Your Own Experience Site. + +# description + +Creates an LWR Build Your Own Experience Site with the specified name and URL path prefix. The site includes all necessary metadata files including DigitalExperienceConfig, DigitalExperienceBundle, Network, and CustomSite. + +# examples + +- Generate an LWR BYO site named "mysite" with URL path prefix "mysite": + + <%= config.bin %> <%= command.id %> --name mysite --url-path-prefix mysite + +- Generate an LWR BYO site with a custom output directory: + + <%= config.bin %> <%= command.id %> --name mysite --url-path-prefix mysite --output-dir force-app/main/default + +# flags.name.summary + +Name of the site to generate. + +# flags.name.description + +The name of the site. + +# flags.url-path-prefix.summary + +URL path prefix for the site. + +# flags.url-path-prefix.description + +Optional. The URL path prefix for the site. This is used in the site's URL. Must contain only alphanumeric characters. + +# flags.output-dir.summary + +Directory to generate the site files in. + +# flags.output-dir.description + +The location can be an absolute path or relative to the current working directory. If not specified, the command reads your sfdx-project.json and uses the default package directory. When running outside a Salesforce DX project, defaults to the current directory. diff --git a/package.json b/package.json index 2eadee62..c906c91e 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,14 @@ } } }, + "dxpsite": { + "description": "Work with Digital Experience Sites.", + "subtopics": { + "generate": { + "description": "Create a Digital Experience Site." + } + } + }, "lightning": { "description": "Work with Lightning Web and Aura components.", "external": true, diff --git a/src/commands/dxpsite/generate/build-your-own-lwr.ts b/src/commands/dxpsite/generate/build-your-own-lwr.ts new file mode 100644 index 00000000..839efd6b --- /dev/null +++ b/src/commands/dxpsite/generate/build-your-own-lwr.ts @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2026, 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 { Flags, SfCommand, Ux } from '@salesforce/sf-plugins-core'; +import { CreateOutput, DxpSiteOptions, TemplateType } from '@salesforce/templates'; +import { Messages, SfProject } from '@salesforce/core'; +import { getCustomTemplates, runGenerator } from '../../../utils/templateCommand.js'; + +Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); +const messages = Messages.loadMessages('@salesforce/plugin-templates', 'dxpsiteBuildYourOwnLwr'); + +export default class BuildYourOwnLwrGenerate extends SfCommand { + public static readonly summary = messages.getMessage('summary'); + public static readonly description = messages.getMessage('description'); + public static readonly examples = messages.getMessages('examples'); + public static readonly flags = { + name: Flags.string({ + char: 'n', + summary: messages.getMessage('flags.name.summary'), + description: messages.getMessage('flags.name.description'), + required: true, + }), + 'url-path-prefix': Flags.string({ + char: 'p', + summary: messages.getMessage('flags.url-path-prefix.summary'), + description: messages.getMessage('flags.url-path-prefix.description'), + default: '', + }), + 'output-dir': Flags.directory({ + char: 'd', + summary: messages.getMessage('flags.output-dir.summary'), + description: messages.getMessage('flags.output-dir.description'), + }), + }; + + /** + * Resolves the default output directory by reading the project's sfdx-project.json. + * Returns the path to the default package directory, + * or falls back to the current directory if not in a project context. + */ + private static async getDefaultOutputDir(): Promise { + try { + const project = await SfProject.resolve(); + const defaultPackage = project.getDefaultPackage(); + return path.join(defaultPackage.path, 'main', 'default'); + } catch { + return '.'; + } + } + + public async run(): Promise { + const { flags } = await this.parse(BuildYourOwnLwrGenerate); + + const outputDir = flags['output-dir'] ?? (await BuildYourOwnLwrGenerate.getDefaultOutputDir()); + + const flagsAsOptions: DxpSiteOptions = { + sitename: flags.name, + urlpathprefix: flags['url-path-prefix'], + template: 'build_your_own_lwr', + outputdir: outputDir, + }; + + return runGenerator({ + templateType: TemplateType.DxpSite, + opts: flagsAsOptions, + ux: new Ux({ jsonEnabled: this.jsonEnabled() }), + templates: getCustomTemplates(this.configAggregator), + }); + } +} diff --git a/test/commands/force/dxpsite/generate/build-your-own-lwr/create.nut.ts b/test/commands/force/dxpsite/generate/build-your-own-lwr/create.nut.ts new file mode 100644 index 00000000..cfd92392 --- /dev/null +++ b/test/commands/force/dxpsite/generate/build-your-own-lwr/create.nut.ts @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2026, 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 { expect } from 'chai'; +import { TestSession, execCmd } from '@salesforce/cli-plugins-testkit'; +import { nls } from '@salesforce/templates/lib/i18n/index.js'; +import assert from 'yeoman-assert'; + +describe('DXP Site build-your-own-lwr creation tests:', () => { + let session: TestSession; + before(async () => { + session = await TestSession.create({ + project: {}, + devhubAuthStrategy: 'NONE', + }); + }); + after(async () => { + await session?.clean(); + }); + + describe('Check dxpsite creation with build-your-own-lwr template', () => { + it('should create dxpsite with all required files', () => { + const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default'); + execCmd( + `dxpsite generate build-your-own-lwr --name "My Test Site" --url-path-prefix mytestsite --output-dir "${outputDir}"`, + { + ensureExitCode: 0, + } + ); + + const bundlePath = path.join(outputDir, 'digitalExperiences', 'site', 'My_Test_Site1'); + + // Check top-level metadata files + assert.file([ + path.join(outputDir, 'networks', 'My Test Site.network-meta.xml'), + path.join(outputDir, 'sites', 'My_Test_Site.site-meta.xml'), + path.join(outputDir, 'digitalExperienceConfigs', 'My_Test_Site1.digitalExperienceConfig-meta.xml'), + path.join(bundlePath, 'My_Test_Site1.digitalExperience-meta.xml'), + ]); + + // Check DEB components + assert.file([ + path.join(bundlePath, 'sfdc_cms__appPage', 'mainAppPage', 'content.json'), + path.join(bundlePath, 'sfdc_cms__appPage', 'mainAppPage', '_meta.json'), + path.join(bundlePath, 'sfdc_cms__brandingSet', 'Build_Your_Own_LWR', 'content.json'), + path.join(bundlePath, 'sfdc_cms__brandingSet', 'Build_Your_Own_LWR', '_meta.json'), + path.join(bundlePath, 'sfdc_cms__languageSettings', 'languages', 'content.json'), + path.join(bundlePath, 'sfdc_cms__languageSettings', 'languages', '_meta.json'), + path.join(bundlePath, 'sfdc_cms__mobilePublisherConfig', 'mobilePublisherConfig', 'content.json'), + path.join(bundlePath, 'sfdc_cms__mobilePublisherConfig', 'mobilePublisherConfig', '_meta.json'), + path.join(bundlePath, 'sfdc_cms__theme', 'Build_Your_Own_LWR', 'content.json'), + path.join(bundlePath, 'sfdc_cms__theme', 'Build_Your_Own_LWR', '_meta.json'), + path.join(bundlePath, 'sfdc_cms__site', 'My_Test_Site1', 'content.json'), + path.join(bundlePath, 'sfdc_cms__site', 'My_Test_Site1', '_meta.json'), + ]); + + // Check routes + const routes = [ + 'Check_Password', + 'Error', + 'Forgot_Password', + 'Home', + 'Login', + 'News_Detail__c', + 'Register', + 'Service_Not_Available', + 'Too_Many_Requests', + ]; + for (const route of routes) { + assert.file([ + path.join(bundlePath, 'sfdc_cms__route', route, 'content.json'), + path.join(bundlePath, 'sfdc_cms__route', route, '_meta.json'), + ]); + } + + // Check theme layouts + const layouts = ['scopedHeaderAndFooter', 'snaThemeLayout']; + for (const layout of layouts) { + assert.file([ + path.join(bundlePath, 'sfdc_cms__themeLayout', layout, 'content.json'), + path.join(bundlePath, 'sfdc_cms__themeLayout', layout, '_meta.json'), + ]); + } + + // Check views + const views = [ + 'checkPasswordResetEmail', + 'error', + 'forgotPassword', + 'home', + 'login', + 'newsDetail', + 'register', + 'serviceNotAvailable', + 'tooManyRequests', + ]; + for (const view of views) { + assert.file([ + path.join(bundlePath, 'sfdc_cms__view', view, 'content.json'), + path.join(bundlePath, 'sfdc_cms__view', view, '_meta.json'), + ]); + } + }); + + it('should handle site names starting with special characters', () => { + const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default'); + execCmd( + `dxpsite generate build-your-own-lwr --name "123 @ Test's Site" --url-path-prefix site123 --output-dir "${outputDir}"`, + { + ensureExitCode: 0, + } + ); + + // Site dev name should be prefixed with X when starting with a number + const bundlePath = path.join(outputDir, 'digitalExperiences', 'site', 'X123_Test_s_Site1'); + assert.file([ + path.join(outputDir, 'networks', '123 %40 Test%27s Site.network-meta.xml'), + path.join(outputDir, 'sites', 'X123_Test_s_Site.site-meta.xml'), + path.join(outputDir, 'digitalExperienceConfigs', 'X123_Test_s_Site1.digitalExperienceConfig-meta.xml'), + path.join(bundlePath, 'X123_Test_s_Site1.digitalExperience-meta.xml'), + ]); + }); + }); + + describe('Check that all invalid input errors are thrown', () => { + it('should throw a missing name error', () => { + const stderr = execCmd('dxpsite generate build-your-own-lwr --url-path-prefix test').shellOutput.stderr; + expect(stderr).to.contain('Missing required flag'); + }); + + it('should create site without url-path-prefix (uses empty default)', () => { + const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default'); + execCmd(`dxpsite generate build-your-own-lwr --name "No Prefix Site" --output-dir "${outputDir}"`, { + ensureExitCode: 0, + }); + + const bundlePath = path.join(outputDir, 'digitalExperiences', 'site', 'No_Prefix_Site1'); + assert.file([ + path.join(outputDir, 'networks', 'No Prefix Site.network-meta.xml'), + path.join(outputDir, 'sites', 'No_Prefix_Site.site-meta.xml'), + path.join(bundlePath, 'No_Prefix_Site1.digitalExperience-meta.xml'), + ]); + }); + + it('should throw invalid url-path-prefix error for non-alphanumeric characters', () => { + const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default'); + const stderr = execCmd( + `dxpsite generate build-your-own-lwr --name TestSite --url-path-prefix "my-prefix" --output-dir "${outputDir}"` + ).shellOutput.stderr; + expect(stderr).to.contain(nls.localize('AlphaNumericValidationError', 'url-path-prefix')); + }); + + it('should throw invalid url-path-prefix error for underscores', () => { + const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default'); + const stderr = execCmd( + `dxpsite generate build-your-own-lwr --name TestSite --url-path-prefix "my_prefix" --output-dir "${outputDir}"` + ).shellOutput.stderr; + expect(stderr).to.contain(nls.localize('AlphaNumericValidationError', 'url-path-prefix')); + }); + + it('should throw invalid url-path-prefix error for spaces', () => { + const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default'); + const stderr = execCmd( + `dxpsite generate build-your-own-lwr --name TestSite --url-path-prefix "my prefix" --output-dir "${outputDir}"` + ).shellOutput.stderr; + expect(stderr).to.contain(nls.localize('AlphaNumericValidationError', 'url-path-prefix')); + }); + }); +}); From 6abb9aa58aee736e4aa626fd9e38abee65bb3870 Mon Sep 17 00:00:00 2001 From: Scott Mo Date: Mon, 26 Jan 2026 10:46:22 -0600 Subject: [PATCH 02/19] feat: add adminEmail param and update docs/tests --- command-snapshot.json | 4 +- messages/dxpsiteBuildYourOwnLwr.md | 10 +- .../dxpsite/generate/build-your-own-lwr.ts | 8 +- .../generate/build-your-own-lwr/create.nut.ts | 184 ++++++++---------- 4 files changed, 96 insertions(+), 110 deletions(-) diff --git a/command-snapshot.json b/command-snapshot.json index 232e99f5..640a1e66 100644 --- a/command-snapshot.json +++ b/command-snapshot.json @@ -27,8 +27,8 @@ "alias": [], "command": "dxpsite:generate:build-your-own-lwr", "flagAliases": [], - "flagChars": ["d", "n", "p"], - "flags": ["flags-dir", "json", "name", "output-dir", "url-path-prefix"], + "flagChars": ["d", "n", "p", "e"], + "flags": ["flags-dir", "json", "name", "output-dir", "url-path-prefix", "admin-email"], "plugin": "@salesforce/plugin-templates" }, { diff --git a/messages/dxpsiteBuildYourOwnLwr.md b/messages/dxpsiteBuildYourOwnLwr.md index c765bee2..48190df7 100644 --- a/messages/dxpsiteBuildYourOwnLwr.md +++ b/messages/dxpsiteBuildYourOwnLwr.md @@ -20,17 +20,13 @@ Creates an LWR Build Your Own Experience Site with the specified name and URL pa Name of the site to generate. -# flags.name.description - -The name of the site. - # flags.url-path-prefix.summary -URL path prefix for the site. +URL path prefix for the site; must contain only alphanumeric characters. -# flags.url-path-prefix.description +# flags.admin-email.summary -Optional. The URL path prefix for the site. This is used in the site's URL. Must contain only alphanumeric characters. +Email address for the site administrator. # flags.output-dir.summary diff --git a/src/commands/dxpsite/generate/build-your-own-lwr.ts b/src/commands/dxpsite/generate/build-your-own-lwr.ts index 839efd6b..bcb296c4 100644 --- a/src/commands/dxpsite/generate/build-your-own-lwr.ts +++ b/src/commands/dxpsite/generate/build-your-own-lwr.ts @@ -22,15 +22,18 @@ export default class BuildYourOwnLwrGenerate extends SfCommand { name: Flags.string({ char: 'n', summary: messages.getMessage('flags.name.summary'), - description: messages.getMessage('flags.name.description'), required: true, }), 'url-path-prefix': Flags.string({ char: 'p', summary: messages.getMessage('flags.url-path-prefix.summary'), - description: messages.getMessage('flags.url-path-prefix.description'), default: '', }), + 'admin-email': Flags.string({ + char: 'e', + summary: messages.getMessage('flags.admin-email.summary'), + default: 'admin@salesforce.com', + }), 'output-dir': Flags.directory({ char: 'd', summary: messages.getMessage('flags.output-dir.summary'), @@ -61,6 +64,7 @@ export default class BuildYourOwnLwrGenerate extends SfCommand { const flagsAsOptions: DxpSiteOptions = { sitename: flags.name, urlpathprefix: flags['url-path-prefix'], + adminEmail: flags['admin-email'], template: 'build_your_own_lwr', outputdir: outputDir, }; diff --git a/test/commands/force/dxpsite/generate/build-your-own-lwr/create.nut.ts b/test/commands/force/dxpsite/generate/build-your-own-lwr/create.nut.ts index cfd92392..7227ac59 100644 --- a/test/commands/force/dxpsite/generate/build-your-own-lwr/create.nut.ts +++ b/test/commands/force/dxpsite/generate/build-your-own-lwr/create.nut.ts @@ -10,7 +10,7 @@ import { TestSession, execCmd } from '@salesforce/cli-plugins-testkit'; import { nls } from '@salesforce/templates/lib/i18n/index.js'; import assert from 'yeoman-assert'; -describe('DXP Site build-your-own-lwr creation tests:', () => { +describe('DXP Site build-your-own-lwr', () => { let session: TestSession; before(async () => { session = await TestSession.create({ @@ -22,90 +22,90 @@ describe('DXP Site build-your-own-lwr creation tests:', () => { await session?.clean(); }); - describe('Check dxpsite creation with build-your-own-lwr template', () => { - it('should create dxpsite with all required files', () => { - const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default'); - execCmd( - `dxpsite generate build-your-own-lwr --name "My Test Site" --url-path-prefix mytestsite --output-dir "${outputDir}"`, - { - ensureExitCode: 0, - } - ); - - const bundlePath = path.join(outputDir, 'digitalExperiences', 'site', 'My_Test_Site1'); - - // Check top-level metadata files + it('should create dxpsite with all required files', () => { + const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default'); + execCmd( + `dxpsite generate build-your-own-lwr --name "My Test Site" --url-path-prefix mytestsite --output-dir "${outputDir}"`, + { + ensureExitCode: 0, + } + ); + + const bundlePath = path.join(outputDir, 'digitalExperiences', 'site', 'My_Test_Site1'); + + // Check top-level metadata files + assert.file([ + path.join(outputDir, 'networks', 'My Test Site.network-meta.xml'), + path.join(outputDir, 'sites', 'My_Test_Site.site-meta.xml'), + path.join(outputDir, 'digitalExperienceConfigs', 'My_Test_Site1.digitalExperienceConfig-meta.xml'), + path.join(bundlePath, 'My_Test_Site1.digitalExperience-meta.xml'), + ]); + + // Check DEB components + assert.file([ + path.join(bundlePath, 'sfdc_cms__appPage', 'mainAppPage', 'content.json'), + path.join(bundlePath, 'sfdc_cms__appPage', 'mainAppPage', '_meta.json'), + path.join(bundlePath, 'sfdc_cms__brandingSet', 'Build_Your_Own_LWR', 'content.json'), + path.join(bundlePath, 'sfdc_cms__brandingSet', 'Build_Your_Own_LWR', '_meta.json'), + path.join(bundlePath, 'sfdc_cms__languageSettings', 'languages', 'content.json'), + path.join(bundlePath, 'sfdc_cms__languageSettings', 'languages', '_meta.json'), + path.join(bundlePath, 'sfdc_cms__mobilePublisherConfig', 'mobilePublisherConfig', 'content.json'), + path.join(bundlePath, 'sfdc_cms__mobilePublisherConfig', 'mobilePublisherConfig', '_meta.json'), + path.join(bundlePath, 'sfdc_cms__theme', 'Build_Your_Own_LWR', 'content.json'), + path.join(bundlePath, 'sfdc_cms__theme', 'Build_Your_Own_LWR', '_meta.json'), + path.join(bundlePath, 'sfdc_cms__site', 'My_Test_Site1', 'content.json'), + path.join(bundlePath, 'sfdc_cms__site', 'My_Test_Site1', '_meta.json'), + ]); + + // Check routes + const routes = [ + 'Check_Password', + 'Error', + 'Forgot_Password', + 'Home', + 'Login', + 'News_Detail__c', + 'Register', + 'Service_Not_Available', + 'Too_Many_Requests', + ]; + for (const route of routes) { assert.file([ - path.join(outputDir, 'networks', 'My Test Site.network-meta.xml'), - path.join(outputDir, 'sites', 'My_Test_Site.site-meta.xml'), - path.join(outputDir, 'digitalExperienceConfigs', 'My_Test_Site1.digitalExperienceConfig-meta.xml'), - path.join(bundlePath, 'My_Test_Site1.digitalExperience-meta.xml'), + path.join(bundlePath, 'sfdc_cms__route', route, 'content.json'), + path.join(bundlePath, 'sfdc_cms__route', route, '_meta.json'), ]); + } - // Check DEB components + // Check theme layouts + const layouts = ['scopedHeaderAndFooter', 'snaThemeLayout']; + for (const layout of layouts) { assert.file([ - path.join(bundlePath, 'sfdc_cms__appPage', 'mainAppPage', 'content.json'), - path.join(bundlePath, 'sfdc_cms__appPage', 'mainAppPage', '_meta.json'), - path.join(bundlePath, 'sfdc_cms__brandingSet', 'Build_Your_Own_LWR', 'content.json'), - path.join(bundlePath, 'sfdc_cms__brandingSet', 'Build_Your_Own_LWR', '_meta.json'), - path.join(bundlePath, 'sfdc_cms__languageSettings', 'languages', 'content.json'), - path.join(bundlePath, 'sfdc_cms__languageSettings', 'languages', '_meta.json'), - path.join(bundlePath, 'sfdc_cms__mobilePublisherConfig', 'mobilePublisherConfig', 'content.json'), - path.join(bundlePath, 'sfdc_cms__mobilePublisherConfig', 'mobilePublisherConfig', '_meta.json'), - path.join(bundlePath, 'sfdc_cms__theme', 'Build_Your_Own_LWR', 'content.json'), - path.join(bundlePath, 'sfdc_cms__theme', 'Build_Your_Own_LWR', '_meta.json'), - path.join(bundlePath, 'sfdc_cms__site', 'My_Test_Site1', 'content.json'), - path.join(bundlePath, 'sfdc_cms__site', 'My_Test_Site1', '_meta.json'), + path.join(bundlePath, 'sfdc_cms__themeLayout', layout, 'content.json'), + path.join(bundlePath, 'sfdc_cms__themeLayout', layout, '_meta.json'), ]); + } + + // Check views + const views = [ + 'checkPasswordResetEmail', + 'error', + 'forgotPassword', + 'home', + 'login', + 'newsDetail', + 'register', + 'serviceNotAvailable', + 'tooManyRequests', + ]; + for (const view of views) { + assert.file([ + path.join(bundlePath, 'sfdc_cms__view', view, 'content.json'), + path.join(bundlePath, 'sfdc_cms__view', view, '_meta.json'), + ]); + } + }); - // Check routes - const routes = [ - 'Check_Password', - 'Error', - 'Forgot_Password', - 'Home', - 'Login', - 'News_Detail__c', - 'Register', - 'Service_Not_Available', - 'Too_Many_Requests', - ]; - for (const route of routes) { - assert.file([ - path.join(bundlePath, 'sfdc_cms__route', route, 'content.json'), - path.join(bundlePath, 'sfdc_cms__route', route, '_meta.json'), - ]); - } - - // Check theme layouts - const layouts = ['scopedHeaderAndFooter', 'snaThemeLayout']; - for (const layout of layouts) { - assert.file([ - path.join(bundlePath, 'sfdc_cms__themeLayout', layout, 'content.json'), - path.join(bundlePath, 'sfdc_cms__themeLayout', layout, '_meta.json'), - ]); - } - - // Check views - const views = [ - 'checkPasswordResetEmail', - 'error', - 'forgotPassword', - 'home', - 'login', - 'newsDetail', - 'register', - 'serviceNotAvailable', - 'tooManyRequests', - ]; - for (const view of views) { - assert.file([ - path.join(bundlePath, 'sfdc_cms__view', view, 'content.json'), - path.join(bundlePath, 'sfdc_cms__view', view, '_meta.json'), - ]); - } - }); - + describe('parameter name', () => { it('should handle site names starting with special characters', () => { const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default'); execCmd( @@ -124,29 +124,15 @@ describe('DXP Site build-your-own-lwr creation tests:', () => { path.join(bundlePath, 'X123_Test_s_Site1.digitalExperience-meta.xml'), ]); }); - }); - describe('Check that all invalid input errors are thrown', () => { - it('should throw a missing name error', () => { + it('should throw error if missing', () => { const stderr = execCmd('dxpsite generate build-your-own-lwr --url-path-prefix test').shellOutput.stderr; expect(stderr).to.contain('Missing required flag'); }); + }); - it('should create site without url-path-prefix (uses empty default)', () => { - const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default'); - execCmd(`dxpsite generate build-your-own-lwr --name "No Prefix Site" --output-dir "${outputDir}"`, { - ensureExitCode: 0, - }); - - const bundlePath = path.join(outputDir, 'digitalExperiences', 'site', 'No_Prefix_Site1'); - assert.file([ - path.join(outputDir, 'networks', 'No Prefix Site.network-meta.xml'), - path.join(outputDir, 'sites', 'No_Prefix_Site.site-meta.xml'), - path.join(bundlePath, 'No_Prefix_Site1.digitalExperience-meta.xml'), - ]); - }); - - it('should throw invalid url-path-prefix error for non-alphanumeric characters', () => { + describe('parameter url-path-prefix', () => { + it('should throw error for non-alphanumeric characters', () => { const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default'); const stderr = execCmd( `dxpsite generate build-your-own-lwr --name TestSite --url-path-prefix "my-prefix" --output-dir "${outputDir}"` @@ -154,7 +140,7 @@ describe('DXP Site build-your-own-lwr creation tests:', () => { expect(stderr).to.contain(nls.localize('AlphaNumericValidationError', 'url-path-prefix')); }); - it('should throw invalid url-path-prefix error for underscores', () => { + it('should throw error for underscores', () => { const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default'); const stderr = execCmd( `dxpsite generate build-your-own-lwr --name TestSite --url-path-prefix "my_prefix" --output-dir "${outputDir}"` @@ -162,7 +148,7 @@ describe('DXP Site build-your-own-lwr creation tests:', () => { expect(stderr).to.contain(nls.localize('AlphaNumericValidationError', 'url-path-prefix')); }); - it('should throw invalid url-path-prefix error for spaces', () => { + it('should throw error for spaces', () => { const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default'); const stderr = execCmd( `dxpsite generate build-your-own-lwr --name TestSite --url-path-prefix "my prefix" --output-dir "${outputDir}"` From cd6e8e75c3dddc3e360f6c8dfa0776baf2554372 Mon Sep 17 00:00:00 2001 From: Scott Mo Date: Mon, 26 Jan 2026 11:41:41 -0600 Subject: [PATCH 03/19] fix: casing --- src/commands/dxpsite/generate/build-your-own-lwr.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/dxpsite/generate/build-your-own-lwr.ts b/src/commands/dxpsite/generate/build-your-own-lwr.ts index bcb296c4..05ed4983 100644 --- a/src/commands/dxpsite/generate/build-your-own-lwr.ts +++ b/src/commands/dxpsite/generate/build-your-own-lwr.ts @@ -64,7 +64,7 @@ export default class BuildYourOwnLwrGenerate extends SfCommand { const flagsAsOptions: DxpSiteOptions = { sitename: flags.name, urlpathprefix: flags['url-path-prefix'], - adminEmail: flags['admin-email'], + adminemail: flags['admin-email'], template: 'build_your_own_lwr', outputdir: outputDir, }; From 9fb0036d353441feaec01c043f6db0272e2af505 Mon Sep 17 00:00:00 2001 From: Scott Mo Date: Mon, 26 Jan 2026 15:32:51 -0600 Subject: [PATCH 04/19] feat: use username as admin email if available --- command-snapshot.json | 4 ++-- src/commands/dxpsite/generate/build-your-own-lwr.ts | 11 +++++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/command-snapshot.json b/command-snapshot.json index 640a1e66..b8fce079 100644 --- a/command-snapshot.json +++ b/command-snapshot.json @@ -27,8 +27,8 @@ "alias": [], "command": "dxpsite:generate:build-your-own-lwr", "flagAliases": [], - "flagChars": ["d", "n", "p", "e"], - "flags": ["flags-dir", "json", "name", "output-dir", "url-path-prefix", "admin-email"], + "flagChars": ["d", "n", "p", "e", "o"], + "flags": ["flags-dir", "json", "name", "output-dir", "url-path-prefix", "admin-email", "target-org"], "plugin": "@salesforce/plugin-templates" }, { diff --git a/src/commands/dxpsite/generate/build-your-own-lwr.ts b/src/commands/dxpsite/generate/build-your-own-lwr.ts index 05ed4983..b2ced9f0 100644 --- a/src/commands/dxpsite/generate/build-your-own-lwr.ts +++ b/src/commands/dxpsite/generate/build-your-own-lwr.ts @@ -19,6 +19,7 @@ export default class BuildYourOwnLwrGenerate extends SfCommand { public static readonly description = messages.getMessage('description'); public static readonly examples = messages.getMessages('examples'); public static readonly flags = { + 'target-org': Flags.optionalOrg(), name: Flags.string({ char: 'n', summary: messages.getMessage('flags.name.summary'), @@ -32,7 +33,6 @@ export default class BuildYourOwnLwrGenerate extends SfCommand { 'admin-email': Flags.string({ char: 'e', summary: messages.getMessage('flags.admin-email.summary'), - default: 'admin@salesforce.com', }), 'output-dir': Flags.directory({ char: 'd', @@ -59,12 +59,19 @@ export default class BuildYourOwnLwrGenerate extends SfCommand { public async run(): Promise { const { flags } = await this.parse(BuildYourOwnLwrGenerate); + let adminEmail = flags['admin-email']; + if (!adminEmail) { + const org = flags['target-org']; + // if this ever fails to return username, '' will become "null.invalid" in admin workspace + adminEmail = org?.getConnection()?.getUsername() ?? ''; + } + const outputDir = flags['output-dir'] ?? (await BuildYourOwnLwrGenerate.getDefaultOutputDir()); const flagsAsOptions: DxpSiteOptions = { sitename: flags.name, urlpathprefix: flags['url-path-prefix'], - adminemail: flags['admin-email'], + adminemail: adminEmail, template: 'build_your_own_lwr', outputdir: outputDir, }; From 25181840fbbd7938d1922214c087fceea75d1831 Mon Sep 17 00:00:00 2001 From: Scott Mo Date: Thu, 29 Jan 2026 12:50:43 -0600 Subject: [PATCH 05/19] fix: address comments - update descriptions and comments - set tool state to preview --- messages/dxpsiteBuildYourOwnLwr.md | 6 +++--- src/commands/dxpsite/generate/build-your-own-lwr.ts | 8 ++++++-- .../generate/build-your-own-lwr/create.nut.ts | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) rename test/commands/{force/dxpsite => dxp}/generate/build-your-own-lwr/create.nut.ts (98%) diff --git a/messages/dxpsiteBuildYourOwnLwr.md b/messages/dxpsiteBuildYourOwnLwr.md index 48190df7..68f19cd7 100644 --- a/messages/dxpsiteBuildYourOwnLwr.md +++ b/messages/dxpsiteBuildYourOwnLwr.md @@ -1,10 +1,10 @@ # summary -Generate a Lightning Web Runtime (LWR) Build Your Own Experience Site. +Generate an Experience Cloud site powered by Lightning Web Runtime (LWR) using the Build Your Own template. # description -Creates an LWR Build Your Own Experience Site with the specified name and URL path prefix. The site includes all necessary metadata files including DigitalExperienceConfig, DigitalExperienceBundle, Network, and CustomSite. +Creates a Build Your Own LWR site with the specified name and URL path prefix. The site includes all necessary metadata files including DigitalExperienceConfig, DigitalExperienceBundle, Network, and CustomSite. See for more details about the template. # examples @@ -26,7 +26,7 @@ URL path prefix for the site; must contain only alphanumeric characters. # flags.admin-email.summary -Email address for the site administrator. +Email address for the site administrator. This will default to the username of the currently authenticated user. # flags.output-dir.summary diff --git a/src/commands/dxpsite/generate/build-your-own-lwr.ts b/src/commands/dxpsite/generate/build-your-own-lwr.ts index b2ced9f0..bdfd1345 100644 --- a/src/commands/dxpsite/generate/build-your-own-lwr.ts +++ b/src/commands/dxpsite/generate/build-your-own-lwr.ts @@ -15,6 +15,7 @@ Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); const messages = Messages.loadMessages('@salesforce/plugin-templates', 'dxpsiteBuildYourOwnLwr'); export default class BuildYourOwnLwrGenerate extends SfCommand { + public static readonly state = 'preview'; public static readonly summary = messages.getMessage('summary'); public static readonly description = messages.getMessage('description'); public static readonly examples = messages.getMessages('examples'); @@ -28,6 +29,8 @@ export default class BuildYourOwnLwrGenerate extends SfCommand { 'url-path-prefix': Flags.string({ char: 'p', summary: messages.getMessage('flags.url-path-prefix.summary'), + // each site must have a unique url path prefix, if not provided assume it's empty + // to mimic UI's behavior default: '', }), 'admin-email': Flags.string({ @@ -62,8 +65,9 @@ export default class BuildYourOwnLwrGenerate extends SfCommand { let adminEmail = flags['admin-email']; if (!adminEmail) { const org = flags['target-org']; - // if this ever fails to return username, '' will become "null.invalid" in admin workspace - adminEmail = org?.getConnection()?.getUsername() ?? ''; + // If this ever fails to return a username, the default value will be appeneded ".invalid" + // in admin workspace with a note asking the admin to set a valid email and verify it. + adminEmail = org?.getConnection()?.getUsername() ?? 'senderEmail@example.com'; } const outputDir = flags['output-dir'] ?? (await BuildYourOwnLwrGenerate.getDefaultOutputDir()); diff --git a/test/commands/force/dxpsite/generate/build-your-own-lwr/create.nut.ts b/test/commands/dxp/generate/build-your-own-lwr/create.nut.ts similarity index 98% rename from test/commands/force/dxpsite/generate/build-your-own-lwr/create.nut.ts rename to test/commands/dxp/generate/build-your-own-lwr/create.nut.ts index 7227ac59..0fe59bf8 100644 --- a/test/commands/force/dxpsite/generate/build-your-own-lwr/create.nut.ts +++ b/test/commands/dxp/generate/build-your-own-lwr/create.nut.ts @@ -10,7 +10,7 @@ import { TestSession, execCmd } from '@salesforce/cli-plugins-testkit'; import { nls } from '@salesforce/templates/lib/i18n/index.js'; import assert from 'yeoman-assert'; -describe('DXP Site build-your-own-lwr', () => { +describe('DXP Site Generation for Build Your Own LWR Template', () => { let session: TestSession; before(async () => { session = await TestSession.create({ From 9eef6a9d220d6b455f9ae292f0b0f613bfdf4b55 Mon Sep 17 00:00:00 2001 From: Scott Mo Date: Thu, 29 Jan 2026 15:37:24 -0600 Subject: [PATCH 06/19] refactor: change command structure and namespace --- command-snapshot.json | 6 +-- messages/dxpSite.md | 47 +++++++++++++++++++ messages/dxpsiteBuildYourOwnLwr.md | 37 --------------- package.json | 2 +- .../generate/site.ts} | 16 +++++-- .../generate/build-your-own-lwr/create.nut.ts | 15 +++--- 6 files changed, 70 insertions(+), 53 deletions(-) create mode 100644 messages/dxpSite.md delete mode 100644 messages/dxpsiteBuildYourOwnLwr.md rename src/commands/{dxpsite/generate/build-your-own-lwr.ts => dxp/generate/site.ts} (87%) diff --git a/command-snapshot.json b/command-snapshot.json index b8fce079..8df20870 100644 --- a/command-snapshot.json +++ b/command-snapshot.json @@ -25,10 +25,10 @@ }, { "alias": [], - "command": "dxpsite:generate:build-your-own-lwr", + "command": "dxp:generate:site", "flagAliases": [], - "flagChars": ["d", "n", "p", "e", "o"], - "flags": ["flags-dir", "json", "name", "output-dir", "url-path-prefix", "admin-email", "target-org"], + "flagChars": ["d", "n", "p", "e", "o", "t"], + "flags": ["flags-dir", "json", "name", "output-dir", "url-path-prefix", "admin-email", "target-org", "template"], "plugin": "@salesforce/plugin-templates" }, { diff --git a/messages/dxpSite.md b/messages/dxpSite.md new file mode 100644 index 00000000..ae7fc91e --- /dev/null +++ b/messages/dxpSite.md @@ -0,0 +1,47 @@ +# summary + +Generate an Experience Cloud site. + +# description + +Creates an Experience Cloud site with the specified template, name and URL path prefix. The site includes all necessary metadata files including DigitalExperienceConfig, DigitalExperienceBundle, Network, and CustomSite. + +# examples + +- Generate a BYO LWR site named "mysite" with URL path prefix "mysite": + + <%= config.bin %> <%= command.id %> --template build_your_own_lwr --name mysite --url-path-prefix mysite + +- Generate a BYO LWR site with a custom output directory: + + <%= config.bin %> <%= command.id %> --template build_your_own_lwr --name mysite --url-path-prefix mysite --output-dir force-app/main/default + +# flags.name.summary + +Name of the site to generate. + +# flags.template.summary + +Template to use for site generation. + +# flags.template.description + +Supported templates: + +- build_your_own_lwr - + +# flags.url-path-prefix.summary + +URL path prefix for the site; must contain only alphanumeric characters. + +# flags.admin-email.summary + +Email address for the site administrator. This will default to the username of the currently authenticated user. + +# flags.output-dir.summary + +Directory to generate the site files in. + +# flags.output-dir.description + +The location can be an absolute path or relative to the current working directory. If not specified, the command reads your sfdx-project.json and uses the default package directory. When running outside a Salesforce DX project, defaults to the current directory. diff --git a/messages/dxpsiteBuildYourOwnLwr.md b/messages/dxpsiteBuildYourOwnLwr.md deleted file mode 100644 index 68f19cd7..00000000 --- a/messages/dxpsiteBuildYourOwnLwr.md +++ /dev/null @@ -1,37 +0,0 @@ -# summary - -Generate an Experience Cloud site powered by Lightning Web Runtime (LWR) using the Build Your Own template. - -# description - -Creates a Build Your Own LWR site with the specified name and URL path prefix. The site includes all necessary metadata files including DigitalExperienceConfig, DigitalExperienceBundle, Network, and CustomSite. See for more details about the template. - -# examples - -- Generate an LWR BYO site named "mysite" with URL path prefix "mysite": - - <%= config.bin %> <%= command.id %> --name mysite --url-path-prefix mysite - -- Generate an LWR BYO site with a custom output directory: - - <%= config.bin %> <%= command.id %> --name mysite --url-path-prefix mysite --output-dir force-app/main/default - -# flags.name.summary - -Name of the site to generate. - -# flags.url-path-prefix.summary - -URL path prefix for the site; must contain only alphanumeric characters. - -# flags.admin-email.summary - -Email address for the site administrator. This will default to the username of the currently authenticated user. - -# flags.output-dir.summary - -Directory to generate the site files in. - -# flags.output-dir.description - -The location can be an absolute path or relative to the current working directory. If not specified, the command reads your sfdx-project.json and uses the default package directory. When running outside a Salesforce DX project, defaults to the current directory. diff --git a/package.json b/package.json index c906c91e..f6cb09c3 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ } } }, - "dxpsite": { + "dxp": { "description": "Work with Digital Experience Sites.", "subtopics": { "generate": { diff --git a/src/commands/dxpsite/generate/build-your-own-lwr.ts b/src/commands/dxp/generate/site.ts similarity index 87% rename from src/commands/dxpsite/generate/build-your-own-lwr.ts rename to src/commands/dxp/generate/site.ts index bdfd1345..0a6e116f 100644 --- a/src/commands/dxpsite/generate/build-your-own-lwr.ts +++ b/src/commands/dxp/generate/site.ts @@ -12,9 +12,9 @@ import { Messages, SfProject } from '@salesforce/core'; import { getCustomTemplates, runGenerator } from '../../../utils/templateCommand.js'; Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); -const messages = Messages.loadMessages('@salesforce/plugin-templates', 'dxpsiteBuildYourOwnLwr'); +const messages = Messages.loadMessages('@salesforce/plugin-templates', 'dxpSite'); -export default class BuildYourOwnLwrGenerate extends SfCommand { +export default class GenerateSite extends SfCommand { public static readonly state = 'preview'; public static readonly summary = messages.getMessage('summary'); public static readonly description = messages.getMessage('description'); @@ -26,6 +26,12 @@ export default class BuildYourOwnLwrGenerate extends SfCommand { summary: messages.getMessage('flags.name.summary'), required: true, }), + template: Flags.string({ + char: 't', + summary: messages.getMessage('flags.template.summary'), + options: ['build_your_own_lwr'] as const, + required: true, + }), 'url-path-prefix': Flags.string({ char: 'p', summary: messages.getMessage('flags.url-path-prefix.summary'), @@ -60,7 +66,7 @@ export default class BuildYourOwnLwrGenerate extends SfCommand { } public async run(): Promise { - const { flags } = await this.parse(BuildYourOwnLwrGenerate); + const { flags } = await this.parse(GenerateSite); let adminEmail = flags['admin-email']; if (!adminEmail) { @@ -70,13 +76,13 @@ export default class BuildYourOwnLwrGenerate extends SfCommand { adminEmail = org?.getConnection()?.getUsername() ?? 'senderEmail@example.com'; } - const outputDir = flags['output-dir'] ?? (await BuildYourOwnLwrGenerate.getDefaultOutputDir()); + const outputDir = flags['output-dir'] ?? (await GenerateSite.getDefaultOutputDir()); const flagsAsOptions: DxpSiteOptions = { sitename: flags.name, urlpathprefix: flags['url-path-prefix'], adminemail: adminEmail, - template: 'build_your_own_lwr', + template: flags.template, outputdir: outputDir, }; diff --git a/test/commands/dxp/generate/build-your-own-lwr/create.nut.ts b/test/commands/dxp/generate/build-your-own-lwr/create.nut.ts index 0fe59bf8..5f21b63c 100644 --- a/test/commands/dxp/generate/build-your-own-lwr/create.nut.ts +++ b/test/commands/dxp/generate/build-your-own-lwr/create.nut.ts @@ -22,10 +22,10 @@ describe('DXP Site Generation for Build Your Own LWR Template', () => { await session?.clean(); }); - it('should create dxpsite with all required files', () => { + it('should create dxp site with all required files', () => { const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default'); execCmd( - `dxpsite generate build-your-own-lwr --name "My Test Site" --url-path-prefix mytestsite --output-dir "${outputDir}"`, + `dxp generate site --template build_your_own_lwr --name "My Test Site" --url-path-prefix mytestsite --output-dir "${outputDir}"`, { ensureExitCode: 0, } @@ -109,7 +109,7 @@ describe('DXP Site Generation for Build Your Own LWR Template', () => { it('should handle site names starting with special characters', () => { const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default'); execCmd( - `dxpsite generate build-your-own-lwr --name "123 @ Test's Site" --url-path-prefix site123 --output-dir "${outputDir}"`, + `dxp generate site --template build_your_own_lwr --name "123 @ Test's Site" --url-path-prefix site123 --output-dir "${outputDir}"`, { ensureExitCode: 0, } @@ -126,7 +126,8 @@ describe('DXP Site Generation for Build Your Own LWR Template', () => { }); it('should throw error if missing', () => { - const stderr = execCmd('dxpsite generate build-your-own-lwr --url-path-prefix test').shellOutput.stderr; + const stderr = execCmd('dxp generate site --template build_your_own_lwr --url-path-prefix test').shellOutput + .stderr; expect(stderr).to.contain('Missing required flag'); }); }); @@ -135,7 +136,7 @@ describe('DXP Site Generation for Build Your Own LWR Template', () => { it('should throw error for non-alphanumeric characters', () => { const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default'); const stderr = execCmd( - `dxpsite generate build-your-own-lwr --name TestSite --url-path-prefix "my-prefix" --output-dir "${outputDir}"` + `dxp generate site --template build_your_own_lwr --name TestSite --url-path-prefix "my-prefix" --output-dir "${outputDir}"` ).shellOutput.stderr; expect(stderr).to.contain(nls.localize('AlphaNumericValidationError', 'url-path-prefix')); }); @@ -143,7 +144,7 @@ describe('DXP Site Generation for Build Your Own LWR Template', () => { it('should throw error for underscores', () => { const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default'); const stderr = execCmd( - `dxpsite generate build-your-own-lwr --name TestSite --url-path-prefix "my_prefix" --output-dir "${outputDir}"` + `dxp generate site --template build_your_own_lwr --name TestSite --url-path-prefix "my_prefix" --output-dir "${outputDir}"` ).shellOutput.stderr; expect(stderr).to.contain(nls.localize('AlphaNumericValidationError', 'url-path-prefix')); }); @@ -151,7 +152,7 @@ describe('DXP Site Generation for Build Your Own LWR Template', () => { it('should throw error for spaces', () => { const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default'); const stderr = execCmd( - `dxpsite generate build-your-own-lwr --name TestSite --url-path-prefix "my prefix" --output-dir "${outputDir}"` + `dxp generate site --template build_your_own_lwr --name TestSite --url-path-prefix "my prefix" --output-dir "${outputDir}"` ).shellOutput.stderr; expect(stderr).to.contain(nls.localize('AlphaNumericValidationError', 'url-path-prefix')); }); From 73e195ccb837c1af91bdff3cd8d9dac588f29c61 Mon Sep 17 00:00:00 2001 From: Scott Mo Date: Thu, 5 Feb 2026 11:44:01 -0600 Subject: [PATCH 07/19] refactor: mv command and update tests --- LICENSE.txt | 2 +- command-snapshot.json | 16 +- package.json | 21 ++- .../generate/dxp}/site.ts | 2 +- .../generate/build-your-own-lwr/create.nut.ts | 160 ------------------ .../template/generate/dxp/site/create.nut.ts | 151 +++++++++++++++++ 6 files changed, 174 insertions(+), 178 deletions(-) rename src/commands/{dxp/generate => template/generate/dxp}/site.ts (97%) delete mode 100644 test/commands/dxp/generate/build-your-own-lwr/create.nut.ts create mode 100644 test/commands/template/generate/dxp/site/create.nut.ts diff --git a/LICENSE.txt b/LICENSE.txt index ca35d0df..1aeebc57 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ Apache License Version 2.0 -Copyright (c) 2025 Salesforce, Inc. +Copyright (c) 2026 Salesforce, Inc. All rights reserved. Apache License diff --git a/command-snapshot.json b/command-snapshot.json index 8df20870..581a4b53 100644 --- a/command-snapshot.json +++ b/command-snapshot.json @@ -23,14 +23,6 @@ "flags": ["api-version", "event", "flags-dir", "json", "loglevel", "name", "output-dir", "sobject", "template"], "plugin": "@salesforce/plugin-templates" }, - { - "alias": [], - "command": "dxp:generate:site", - "flagAliases": [], - "flagChars": ["d", "n", "p", "e", "o", "t"], - "flags": ["flags-dir", "json", "name", "output-dir", "url-path-prefix", "admin-email", "target-org", "template"], - "plugin": "@salesforce/plugin-templates" - }, { "alias": ["force:lightning:app:create"], "command": "lightning:generate:app", @@ -99,6 +91,14 @@ "flags": ["api-version", "flags-dir", "json", "loglevel", "name", "output-dir", "type"], "plugin": "@salesforce/plugin-templates" }, + { + "alias": [], + "command": "template:generate:dxp:site", + "flagAliases": [], + "flagChars": ["d", "n", "p", "e", "o", "t"], + "flags": ["flags-dir", "json", "name", "output-dir", "url-path-prefix", "admin-email", "target-org", "template"], + "plugin": "@salesforce/plugin-templates" + }, { "alias": ["force:visualforce:component:create"], "command": "visualforce:generate:component", diff --git a/package.json b/package.json index f6cb09c3..ac90c701 100644 --- a/package.json +++ b/package.json @@ -69,14 +69,6 @@ } } }, - "dxp": { - "description": "Work with Digital Experience Sites.", - "subtopics": { - "generate": { - "description": "Create a Digital Experience Site." - } - } - }, "lightning": { "description": "Work with Lightning Web and Aura components.", "external": true, @@ -99,6 +91,19 @@ }, "static-resource": { "description": "Work with static resources." + }, + "template": { + "description": "Collection of Salesforce templates.", + "subtopics": { + "generate": { + "description": "Use a Salesforce template to generate local files.", + "subtopics": { + "dxp": { + "description": "Create a Digital Experience site." + } + } + } + } } }, "flexibleTaxonomy": true, diff --git a/src/commands/dxp/generate/site.ts b/src/commands/template/generate/dxp/site.ts similarity index 97% rename from src/commands/dxp/generate/site.ts rename to src/commands/template/generate/dxp/site.ts index 0a6e116f..a2e86849 100644 --- a/src/commands/dxp/generate/site.ts +++ b/src/commands/template/generate/dxp/site.ts @@ -9,7 +9,7 @@ import path from 'node:path'; import { Flags, SfCommand, Ux } from '@salesforce/sf-plugins-core'; import { CreateOutput, DxpSiteOptions, TemplateType } from '@salesforce/templates'; import { Messages, SfProject } from '@salesforce/core'; -import { getCustomTemplates, runGenerator } from '../../../utils/templateCommand.js'; +import { getCustomTemplates, runGenerator } from '../../../../utils/templateCommand.js'; Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); const messages = Messages.loadMessages('@salesforce/plugin-templates', 'dxpSite'); diff --git a/test/commands/dxp/generate/build-your-own-lwr/create.nut.ts b/test/commands/dxp/generate/build-your-own-lwr/create.nut.ts deleted file mode 100644 index 5f21b63c..00000000 --- a/test/commands/dxp/generate/build-your-own-lwr/create.nut.ts +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright (c) 2026, 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 { expect } from 'chai'; -import { TestSession, execCmd } from '@salesforce/cli-plugins-testkit'; -import { nls } from '@salesforce/templates/lib/i18n/index.js'; -import assert from 'yeoman-assert'; - -describe('DXP Site Generation for Build Your Own LWR Template', () => { - let session: TestSession; - before(async () => { - session = await TestSession.create({ - project: {}, - devhubAuthStrategy: 'NONE', - }); - }); - after(async () => { - await session?.clean(); - }); - - it('should create dxp site with all required files', () => { - const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default'); - execCmd( - `dxp generate site --template build_your_own_lwr --name "My Test Site" --url-path-prefix mytestsite --output-dir "${outputDir}"`, - { - ensureExitCode: 0, - } - ); - - const bundlePath = path.join(outputDir, 'digitalExperiences', 'site', 'My_Test_Site1'); - - // Check top-level metadata files - assert.file([ - path.join(outputDir, 'networks', 'My Test Site.network-meta.xml'), - path.join(outputDir, 'sites', 'My_Test_Site.site-meta.xml'), - path.join(outputDir, 'digitalExperienceConfigs', 'My_Test_Site1.digitalExperienceConfig-meta.xml'), - path.join(bundlePath, 'My_Test_Site1.digitalExperience-meta.xml'), - ]); - - // Check DEB components - assert.file([ - path.join(bundlePath, 'sfdc_cms__appPage', 'mainAppPage', 'content.json'), - path.join(bundlePath, 'sfdc_cms__appPage', 'mainAppPage', '_meta.json'), - path.join(bundlePath, 'sfdc_cms__brandingSet', 'Build_Your_Own_LWR', 'content.json'), - path.join(bundlePath, 'sfdc_cms__brandingSet', 'Build_Your_Own_LWR', '_meta.json'), - path.join(bundlePath, 'sfdc_cms__languageSettings', 'languages', 'content.json'), - path.join(bundlePath, 'sfdc_cms__languageSettings', 'languages', '_meta.json'), - path.join(bundlePath, 'sfdc_cms__mobilePublisherConfig', 'mobilePublisherConfig', 'content.json'), - path.join(bundlePath, 'sfdc_cms__mobilePublisherConfig', 'mobilePublisherConfig', '_meta.json'), - path.join(bundlePath, 'sfdc_cms__theme', 'Build_Your_Own_LWR', 'content.json'), - path.join(bundlePath, 'sfdc_cms__theme', 'Build_Your_Own_LWR', '_meta.json'), - path.join(bundlePath, 'sfdc_cms__site', 'My_Test_Site1', 'content.json'), - path.join(bundlePath, 'sfdc_cms__site', 'My_Test_Site1', '_meta.json'), - ]); - - // Check routes - const routes = [ - 'Check_Password', - 'Error', - 'Forgot_Password', - 'Home', - 'Login', - 'News_Detail__c', - 'Register', - 'Service_Not_Available', - 'Too_Many_Requests', - ]; - for (const route of routes) { - assert.file([ - path.join(bundlePath, 'sfdc_cms__route', route, 'content.json'), - path.join(bundlePath, 'sfdc_cms__route', route, '_meta.json'), - ]); - } - - // Check theme layouts - const layouts = ['scopedHeaderAndFooter', 'snaThemeLayout']; - for (const layout of layouts) { - assert.file([ - path.join(bundlePath, 'sfdc_cms__themeLayout', layout, 'content.json'), - path.join(bundlePath, 'sfdc_cms__themeLayout', layout, '_meta.json'), - ]); - } - - // Check views - const views = [ - 'checkPasswordResetEmail', - 'error', - 'forgotPassword', - 'home', - 'login', - 'newsDetail', - 'register', - 'serviceNotAvailable', - 'tooManyRequests', - ]; - for (const view of views) { - assert.file([ - path.join(bundlePath, 'sfdc_cms__view', view, 'content.json'), - path.join(bundlePath, 'sfdc_cms__view', view, '_meta.json'), - ]); - } - }); - - describe('parameter name', () => { - it('should handle site names starting with special characters', () => { - const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default'); - execCmd( - `dxp generate site --template build_your_own_lwr --name "123 @ Test's Site" --url-path-prefix site123 --output-dir "${outputDir}"`, - { - ensureExitCode: 0, - } - ); - - // Site dev name should be prefixed with X when starting with a number - const bundlePath = path.join(outputDir, 'digitalExperiences', 'site', 'X123_Test_s_Site1'); - assert.file([ - path.join(outputDir, 'networks', '123 %40 Test%27s Site.network-meta.xml'), - path.join(outputDir, 'sites', 'X123_Test_s_Site.site-meta.xml'), - path.join(outputDir, 'digitalExperienceConfigs', 'X123_Test_s_Site1.digitalExperienceConfig-meta.xml'), - path.join(bundlePath, 'X123_Test_s_Site1.digitalExperience-meta.xml'), - ]); - }); - - it('should throw error if missing', () => { - const stderr = execCmd('dxp generate site --template build_your_own_lwr --url-path-prefix test').shellOutput - .stderr; - expect(stderr).to.contain('Missing required flag'); - }); - }); - - describe('parameter url-path-prefix', () => { - it('should throw error for non-alphanumeric characters', () => { - const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default'); - const stderr = execCmd( - `dxp generate site --template build_your_own_lwr --name TestSite --url-path-prefix "my-prefix" --output-dir "${outputDir}"` - ).shellOutput.stderr; - expect(stderr).to.contain(nls.localize('AlphaNumericValidationError', 'url-path-prefix')); - }); - - it('should throw error for underscores', () => { - const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default'); - const stderr = execCmd( - `dxp generate site --template build_your_own_lwr --name TestSite --url-path-prefix "my_prefix" --output-dir "${outputDir}"` - ).shellOutput.stderr; - expect(stderr).to.contain(nls.localize('AlphaNumericValidationError', 'url-path-prefix')); - }); - - it('should throw error for spaces', () => { - const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default'); - const stderr = execCmd( - `dxp generate site --template build_your_own_lwr --name TestSite --url-path-prefix "my prefix" --output-dir "${outputDir}"` - ).shellOutput.stderr; - expect(stderr).to.contain(nls.localize('AlphaNumericValidationError', 'url-path-prefix')); - }); - }); -}); diff --git a/test/commands/template/generate/dxp/site/create.nut.ts b/test/commands/template/generate/dxp/site/create.nut.ts new file mode 100644 index 00000000..fc7f6b90 --- /dev/null +++ b/test/commands/template/generate/dxp/site/create.nut.ts @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2026, 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 fs from 'node:fs'; +import { expect } from 'chai'; +import { TestSession, execCmd } from '@salesforce/cli-plugins-testkit'; +import assert from 'yeoman-assert'; + +const COMMAND = 'template generate dxp site'; + +describe(COMMAND, () => { + let session: TestSession; + before(async () => { + session = await TestSession.create({ + project: {}, + devhubAuthStrategy: 'NONE', + }); + }); + after(async () => { + await session?.clean(); + }); + + describe('--template build_your_own_lwr', () => { + it('should create with all required files', () => { + const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default'); + execCmd( + `${COMMAND} --template build_your_own_lwr --name "123 @ Test Site" --url-path-prefix 123testsite --output-dir "${outputDir}"`, + { + ensureExitCode: 0, + } + ); + + const bundlePath = path.join(outputDir, 'digitalExperiences', 'site', 'X123_Test_Site1'); + + // Check top-level metadata files + assert.file([ + path.join(outputDir, 'networks', '123 %40 Test Site.network-meta.xml'), + path.join(outputDir, 'sites', 'X123_Test_Site.site-meta.xml'), + path.join(outputDir, 'digitalExperienceConfigs', 'X123_Test_Site1.digitalExperienceConfig-meta.xml'), + path.join(bundlePath, 'X123_Test_Site1.digitalExperience-meta.xml'), + ]); + + // Check DEB components + assert.file([ + path.join(bundlePath, 'sfdc_cms__appPage', 'mainAppPage', 'content.json'), + path.join(bundlePath, 'sfdc_cms__appPage', 'mainAppPage', '_meta.json'), + path.join(bundlePath, 'sfdc_cms__brandingSet', 'Build_Your_Own_LWR', 'content.json'), + path.join(bundlePath, 'sfdc_cms__brandingSet', 'Build_Your_Own_LWR', '_meta.json'), + path.join(bundlePath, 'sfdc_cms__languageSettings', 'languages', 'content.json'), + path.join(bundlePath, 'sfdc_cms__languageSettings', 'languages', '_meta.json'), + path.join(bundlePath, 'sfdc_cms__mobilePublisherConfig', 'mobilePublisherConfig', 'content.json'), + path.join(bundlePath, 'sfdc_cms__mobilePublisherConfig', 'mobilePublisherConfig', '_meta.json'), + path.join(bundlePath, 'sfdc_cms__theme', 'Build_Your_Own_LWR', 'content.json'), + path.join(bundlePath, 'sfdc_cms__theme', 'Build_Your_Own_LWR', '_meta.json'), + path.join(bundlePath, 'sfdc_cms__site', 'X123_Test_Site1', 'content.json'), + path.join(bundlePath, 'sfdc_cms__site', 'X123_Test_Site1', '_meta.json'), + ]); + + // Check routes + const routes = [ + 'Check_Password', + 'Error', + 'Forgot_Password', + 'Home', + 'Login', + 'News_Detail__c', + 'Register', + 'Service_Not_Available', + 'Too_Many_Requests', + ]; + for (const route of routes) { + assert.file([ + path.join(bundlePath, 'sfdc_cms__route', route, 'content.json'), + path.join(bundlePath, 'sfdc_cms__route', route, '_meta.json'), + ]); + } + + // Check theme layouts + const layouts = ['scopedHeaderAndFooter', 'snaThemeLayout']; + for (const layout of layouts) { + assert.file([ + path.join(bundlePath, 'sfdc_cms__themeLayout', layout, 'content.json'), + path.join(bundlePath, 'sfdc_cms__themeLayout', layout, '_meta.json'), + ]); + } + + // Check views + const views = [ + 'checkPasswordResetEmail', + 'error', + 'forgotPassword', + 'home', + 'login', + 'newsDetail', + 'register', + 'serviceNotAvailable', + 'tooManyRequests', + ]; + for (const view of views) { + assert.file([ + path.join(bundlePath, 'sfdc_cms__view', view, 'content.json'), + path.join(bundlePath, 'sfdc_cms__view', view, '_meta.json'), + ]); + } + }); + }); + + describe('parameter validation', () => { + it('should throw error if missing site name', () => { + const stderr = execCmd(`${COMMAND} --template build_your_own_lwr --url-path-prefix test`).shellOutput.stderr; + expect(stderr).to.contain('Missing required flag'); + }); + + it('should throw error if missing template', () => { + const stderr = execCmd(`${COMMAND} --name test --url-path-prefix test`).shellOutput.stderr; + expect(stderr).to.contain('Missing required flag'); + }); + + it('should default to empty string if url-path-prefix is not provided', () => { + const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default'); + execCmd(`${COMMAND} --template build_your_own_lwr --name "DefaultPrefixSite" --output-dir "${outputDir}"`, { + ensureExitCode: 0, + }); + + const networkPath = path.join(outputDir, 'networks', 'DefaultPrefixSite.network-meta.xml'); + const networkContent = fs.readFileSync(networkPath, 'utf8'); + expect(networkContent).to.include('vforcesite'); + + const configPath = path.join( + outputDir, + 'digitalExperienceConfigs', + 'DefaultPrefixSite1.digitalExperienceConfig-meta.xml' + ); + const configContent = fs.readFileSync(configPath, 'utf8'); + expect(configContent).to.include(''); + }); + + it('should default to force/main/default if output-dir is not provided', () => { + execCmd(`${COMMAND} --template build_your_own_lwr --name "DefaultDirSite" --url-path-prefix defaultdir`, { + ensureExitCode: 0, + }); + const defaultOutputDir = path.join(session.project.dir, 'force-app', 'main', 'default'); + assert.file(path.join(defaultOutputDir, 'networks', 'DefaultDirSite.network-meta.xml')); + assert.file(path.join(defaultOutputDir, 'sites', 'DefaultDirSite.site-meta.xml')); + }); + }); +}); From 5f438ea2176a227d94d62e0f0f8ee140a3de3c32 Mon Sep 17 00:00:00 2001 From: Scott Mo Date: Thu, 5 Feb 2026 11:46:15 -0600 Subject: [PATCH 08/19] fix: undo license change --- LICENSE.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE.txt b/LICENSE.txt index 1aeebc57..ca35d0df 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ Apache License Version 2.0 -Copyright (c) 2026 Salesforce, Inc. +Copyright (c) 2025 Salesforce, Inc. All rights reserved. Apache License From 367bb61bde51d3355dcd7a30dcddbffc227f9d4e Mon Sep 17 00:00:00 2001 From: Scott Mo Date: Thu, 5 Feb 2026 15:02:34 -0600 Subject: [PATCH 09/19] refactor: rename command and template --- command-snapshot.json | 2 +- messages/{dxpSite.md => digitalExperienceSite.md} | 6 +++--- package.json | 2 +- .../generate/{dxp => digital-experience}/site.ts | 10 +++++----- .../{dxp => digital-experience}/site/create.nut.ts | 12 ++++++------ 5 files changed, 16 insertions(+), 16 deletions(-) rename messages/{dxpSite.md => digitalExperienceSite.md} (77%) rename src/commands/template/generate/{dxp => digital-experience}/site.ts (91%) rename test/commands/template/generate/{dxp => digital-experience}/site/create.nut.ts (90%) diff --git a/command-snapshot.json b/command-snapshot.json index 3b67527d..f2237961 100644 --- a/command-snapshot.json +++ b/command-snapshot.json @@ -93,7 +93,7 @@ }, { "alias": [], - "command": "template:generate:dxp:site", + "command": "template:generate:digital-experience:site", "flagAliases": [], "flagChars": ["d", "n", "p", "e", "o", "t"], "flags": ["flags-dir", "json", "name", "output-dir", "url-path-prefix", "admin-email", "target-org", "template"], diff --git a/messages/dxpSite.md b/messages/digitalExperienceSite.md similarity index 77% rename from messages/dxpSite.md rename to messages/digitalExperienceSite.md index ae7fc91e..471a4ebf 100644 --- a/messages/dxpSite.md +++ b/messages/digitalExperienceSite.md @@ -10,11 +10,11 @@ Creates an Experience Cloud site with the specified template, name and URL path - Generate a BYO LWR site named "mysite" with URL path prefix "mysite": - <%= config.bin %> <%= command.id %> --template build_your_own_lwr --name mysite --url-path-prefix mysite + <%= config.bin %> <%= command.id %> --template BuildYourOwnLWR --name mysite --url-path-prefix mysite - Generate a BYO LWR site with a custom output directory: - <%= config.bin %> <%= command.id %> --template build_your_own_lwr --name mysite --url-path-prefix mysite --output-dir force-app/main/default + <%= config.bin %> <%= command.id %> --template BuildYourOwnLWR --name mysite --url-path-prefix mysite --output-dir force-app/main/default # flags.name.summary @@ -28,7 +28,7 @@ Template to use for site generation. Supported templates: -- build_your_own_lwr - +- BuildYourOwnLWR - # flags.url-path-prefix.summary diff --git a/package.json b/package.json index 824b236e..942f647f 100644 --- a/package.json +++ b/package.json @@ -93,7 +93,7 @@ "apex": { "description": "Create an apex class or trigger." }, - "dxp": { + "digital-experience": { "description": "Create a Digital Experience site." } } diff --git a/src/commands/template/generate/dxp/site.ts b/src/commands/template/generate/digital-experience/site.ts similarity index 91% rename from src/commands/template/generate/dxp/site.ts rename to src/commands/template/generate/digital-experience/site.ts index a2e86849..a617ee28 100644 --- a/src/commands/template/generate/dxp/site.ts +++ b/src/commands/template/generate/digital-experience/site.ts @@ -7,12 +7,12 @@ import path from 'node:path'; import { Flags, SfCommand, Ux } from '@salesforce/sf-plugins-core'; -import { CreateOutput, DxpSiteOptions, TemplateType } from '@salesforce/templates'; +import { CreateOutput, DigitalExperienceSiteOptions, TemplateType } from '@salesforce/templates'; import { Messages, SfProject } from '@salesforce/core'; import { getCustomTemplates, runGenerator } from '../../../../utils/templateCommand.js'; Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); -const messages = Messages.loadMessages('@salesforce/plugin-templates', 'dxpSite'); +const messages = Messages.loadMessages('@salesforce/plugin-templates', 'digitalExperienceSite'); export default class GenerateSite extends SfCommand { public static readonly state = 'preview'; @@ -29,7 +29,7 @@ export default class GenerateSite extends SfCommand { template: Flags.string({ char: 't', summary: messages.getMessage('flags.template.summary'), - options: ['build_your_own_lwr'] as const, + options: ['BuildYourOwnLWR'] as const, required: true, }), 'url-path-prefix': Flags.string({ @@ -78,7 +78,7 @@ export default class GenerateSite extends SfCommand { const outputDir = flags['output-dir'] ?? (await GenerateSite.getDefaultOutputDir()); - const flagsAsOptions: DxpSiteOptions = { + const flagsAsOptions: DigitalExperienceSiteOptions = { sitename: flags.name, urlpathprefix: flags['url-path-prefix'], adminemail: adminEmail, @@ -87,7 +87,7 @@ export default class GenerateSite extends SfCommand { }; return runGenerator({ - templateType: TemplateType.DxpSite, + templateType: TemplateType.DigitalExperienceSite, opts: flagsAsOptions, ux: new Ux({ jsonEnabled: this.jsonEnabled() }), templates: getCustomTemplates(this.configAggregator), diff --git a/test/commands/template/generate/dxp/site/create.nut.ts b/test/commands/template/generate/digital-experience/site/create.nut.ts similarity index 90% rename from test/commands/template/generate/dxp/site/create.nut.ts rename to test/commands/template/generate/digital-experience/site/create.nut.ts index fc7f6b90..490f11a2 100644 --- a/test/commands/template/generate/dxp/site/create.nut.ts +++ b/test/commands/template/generate/digital-experience/site/create.nut.ts @@ -10,7 +10,7 @@ import { expect } from 'chai'; import { TestSession, execCmd } from '@salesforce/cli-plugins-testkit'; import assert from 'yeoman-assert'; -const COMMAND = 'template generate dxp site'; +const COMMAND = 'template generate digital-experience site'; describe(COMMAND, () => { let session: TestSession; @@ -24,11 +24,11 @@ describe(COMMAND, () => { await session?.clean(); }); - describe('--template build_your_own_lwr', () => { + describe('--template BuildYourOwnLWR', () => { it('should create with all required files', () => { const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default'); execCmd( - `${COMMAND} --template build_your_own_lwr --name "123 @ Test Site" --url-path-prefix 123testsite --output-dir "${outputDir}"`, + `${COMMAND} --template BuildYourOwnLWR --name "123 @ Test Site" --url-path-prefix 123testsite --output-dir "${outputDir}"`, { ensureExitCode: 0, } @@ -111,7 +111,7 @@ describe(COMMAND, () => { describe('parameter validation', () => { it('should throw error if missing site name', () => { - const stderr = execCmd(`${COMMAND} --template build_your_own_lwr --url-path-prefix test`).shellOutput.stderr; + const stderr = execCmd(`${COMMAND} --template BuildYourOwnLWR --url-path-prefix test`).shellOutput.stderr; expect(stderr).to.contain('Missing required flag'); }); @@ -122,7 +122,7 @@ describe(COMMAND, () => { it('should default to empty string if url-path-prefix is not provided', () => { const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default'); - execCmd(`${COMMAND} --template build_your_own_lwr --name "DefaultPrefixSite" --output-dir "${outputDir}"`, { + execCmd(`${COMMAND} --template BuildYourOwnLWR --name "DefaultPrefixSite" --output-dir "${outputDir}"`, { ensureExitCode: 0, }); @@ -140,7 +140,7 @@ describe(COMMAND, () => { }); it('should default to force/main/default if output-dir is not provided', () => { - execCmd(`${COMMAND} --template build_your_own_lwr --name "DefaultDirSite" --url-path-prefix defaultdir`, { + execCmd(`${COMMAND} --template BuildYourOwnLWR --name "DefaultDirSite" --url-path-prefix defaultdir`, { ensureExitCode: 0, }); const defaultOutputDir = path.join(session.project.dir, 'force-app', 'main', 'default'); From 0505698f6610e248d9fde2ca92a6f39684406446 Mon Sep 17 00:00:00 2001 From: Scott Mo Date: Thu, 5 Feb 2026 15:47:07 -0600 Subject: [PATCH 10/19] chore: bump template version --- package.json | 2 +- yarn.lock | 24 ++++++++++++++++++------ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 942f647f..f916bda2 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "dependencies": { "@salesforce/core": "^8.25.0", "@salesforce/sf-plugins-core": "^12", - "@salesforce/templates": "^65.4.3" + "@salesforce/templates": "^65.5.1" }, "devDependencies": { "@oclif/plugin-command-snapshot": "^5.3.8", diff --git a/yarn.lock b/yarn.lock index 31fe1e5a..c03d968e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2009,18 +2009,20 @@ cli-progress "^3.12.0" terminal-link "^3.0.0" -"@salesforce/templates@^65.4.3": - version "65.4.3" - resolved "https://registry.yarnpkg.com/@salesforce/templates/-/templates-65.4.3.tgz#15b0e62d118fea9abe8e010c854f80c68fae6e3b" - integrity sha512-v2iYhDOS/nlZgynXphN/Qongta/EufCRHLsAtbTQnL40PkAnnC8vfZJ4cTAgRtn0C15VBNl+OD4nkXdaa2067Q== +"@salesforce/templates@^65.5.1": + version "65.5.1" + resolved "https://registry.yarnpkg.com/@salesforce/templates/-/templates-65.5.1.tgz#43873b48fcec25e992382d30f1a77c4337b8f8d8" + integrity sha512-4LYZmVCLxwHuAhhI1o9V/noxcUp0uvkuLqHN7SUGJQ8SZdKCZEykm1gC1UgVyhaGf/XfPg2IHTAK7gU8oEYVnQ== dependencies: "@salesforce/kit" "^3.2.4" + "@sfdc-webapps/base-reference-app" "^1.0.6" + "@sfdc-webapps/base-web-app" "^1.0.6" ejs "^3.1.10" got "^11.8.6" hpagent "^1.2.0" mime-types "^3.0.2" proxy-from-env "^1.1.0" - tar "^7.5.4" + tar "^7.5.6" tslib "^2.8.1" "@salesforce/ts-types@^2.0.11", "@salesforce/ts-types@^2.0.12": @@ -2028,6 +2030,16 @@ resolved "https://registry.npmjs.org/@salesforce/ts-types/-/ts-types-2.0.12.tgz" integrity sha512-BIJyduJC18Kc8z+arUm5AZ9VkPRyw1KKAm+Tk+9LT99eOzhNilyfKzhZ4t+tG2lIGgnJpmytZfVDZ0e2kFul8g== +"@sfdc-webapps/base-reference-app@^1.0.6": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@sfdc-webapps/base-reference-app/-/base-reference-app-1.0.6.tgz#af752456e53e628ada44f360e7975b215db376a0" + integrity sha512-9tlydXK/InnUFNYN4VYw9w+N91HTDVvT57VwCDfwlILN+306+oXirf002fx94Wktk+X/CaG5fl32SVtEkKy/JQ== + +"@sfdc-webapps/base-web-app@^1.0.6": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@sfdc-webapps/base-web-app/-/base-web-app-1.0.6.tgz#5881f1e9e97ee8e2cac7a467d1523a5d2b67744a" + integrity sha512-arRsMRuNxAn9bgrxCdNdAwdmvZdGxal4fNXpHxYLla+zPK/vd/CLJC8j9pj5KcU28JBKV10pueFe8aW6DiSYHg== + "@shikijs/core@1.11.1": version "1.11.1" resolved "https://registry.npmjs.org/@shikijs/core/-/core-1.11.1.tgz" @@ -7782,7 +7794,7 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -tar@^7.5.4: +tar@^7.5.6: version "7.5.7" resolved "https://registry.yarnpkg.com/tar/-/tar-7.5.7.tgz#adf99774008ba1c89819f15dbd6019c630539405" integrity sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ== From 8a987cd8d76571386f8ef13c5953f53ce0b5ede3 Mon Sep 17 00:00:00 2001 From: Scott Mo Date: Thu, 5 Feb 2026 16:34:02 -0600 Subject: [PATCH 11/19] Update messages/digitalExperienceSite.md Co-authored-by: Juliet Shackell <63259011+jshackell-sfdc@users.noreply.github.com> --- messages/digitalExperienceSite.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/digitalExperienceSite.md b/messages/digitalExperienceSite.md index 471a4ebf..ce932c60 100644 --- a/messages/digitalExperienceSite.md +++ b/messages/digitalExperienceSite.md @@ -4,7 +4,7 @@ Generate an Experience Cloud site. # description -Creates an Experience Cloud site with the specified template, name and URL path prefix. The site includes all necessary metadata files including DigitalExperienceConfig, DigitalExperienceBundle, Network, and CustomSite. +Creates an Experience Cloud site with the specified template, name, and URL path prefix. The site includes all necessary metadata files, including DigitalExperienceConfig, DigitalExperienceBundle, Network, and CustomSite. # examples From 4532b3edfd446a67a779f0fcbcd88c14201702cc Mon Sep 17 00:00:00 2001 From: Scott Mo Date: Thu, 5 Feb 2026 16:34:52 -0600 Subject: [PATCH 12/19] Update messages/digitalExperienceSite.md Co-authored-by: Juliet Shackell <63259011+jshackell-sfdc@users.noreply.github.com> --- messages/digitalExperienceSite.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/digitalExperienceSite.md b/messages/digitalExperienceSite.md index ce932c60..5dd472cf 100644 --- a/messages/digitalExperienceSite.md +++ b/messages/digitalExperienceSite.md @@ -12,7 +12,7 @@ Creates an Experience Cloud site with the specified template, name, and URL path <%= config.bin %> <%= command.id %> --template BuildYourOwnLWR --name mysite --url-path-prefix mysite -- Generate a BYO LWR site with a custom output directory: +- Generate an Experience Cloud site like the last example, but generate the files into the specified output directory: <%= config.bin %> <%= command.id %> --template BuildYourOwnLWR --name mysite --url-path-prefix mysite --output-dir force-app/main/default From a3eee90ca75a163bdc304e7454e1215d86d7d64c Mon Sep 17 00:00:00 2001 From: Scott Mo Date: Thu, 5 Feb 2026 16:35:20 -0600 Subject: [PATCH 13/19] Update messages/digitalExperienceSite.md Co-authored-by: Juliet Shackell <63259011+jshackell-sfdc@users.noreply.github.com> --- messages/digitalExperienceSite.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/digitalExperienceSite.md b/messages/digitalExperienceSite.md index 5dd472cf..143de293 100644 --- a/messages/digitalExperienceSite.md +++ b/messages/digitalExperienceSite.md @@ -44,4 +44,4 @@ Directory to generate the site files in. # flags.output-dir.description -The location can be an absolute path or relative to the current working directory. If not specified, the command reads your sfdx-project.json and uses the default package directory. When running outside a Salesforce DX project, defaults to the current directory. +The location can be an absolute path or relative to the current working directory. If not specified, the command reads your sfdx-project.json file and uses the default package directory. When running outside a Salesforce DX project, defaults to the current directory. From cbec36872163051372b16e4056a556b049a629ca Mon Sep 17 00:00:00 2001 From: Scott Mo Date: Thu, 5 Feb 2026 16:37:35 -0600 Subject: [PATCH 14/19] Apply suggestion from @jshackell-sfdc Co-authored-by: Juliet Shackell <63259011+jshackell-sfdc@users.noreply.github.com> --- messages/digitalExperienceSite.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/digitalExperienceSite.md b/messages/digitalExperienceSite.md index 143de293..1982b0c6 100644 --- a/messages/digitalExperienceSite.md +++ b/messages/digitalExperienceSite.md @@ -18,7 +18,7 @@ Creates an Experience Cloud site with the specified template, name, and URL path # flags.name.summary -Name of the site to generate. +Name of the Experience Cloud site to generate. # flags.template.summary From 5b3899a0cd0dd103417e21154ef13c7c41f0bc4a Mon Sep 17 00:00:00 2001 From: Scott Mo Date: Thu, 5 Feb 2026 16:38:09 -0600 Subject: [PATCH 15/19] Apply suggestion from @jshackell-sfdc Co-authored-by: Juliet Shackell <63259011+jshackell-sfdc@users.noreply.github.com> --- messages/digitalExperienceSite.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/digitalExperienceSite.md b/messages/digitalExperienceSite.md index 1982b0c6..7b6e8512 100644 --- a/messages/digitalExperienceSite.md +++ b/messages/digitalExperienceSite.md @@ -36,7 +36,7 @@ URL path prefix for the site; must contain only alphanumeric characters. # flags.admin-email.summary -Email address for the site administrator. This will default to the username of the currently authenticated user. +Email address for the site administrator. Defaults to the username of the currently authenticated user. # flags.output-dir.summary From 4e53b126cce7c83c8d3e1eeb131b864815830e68 Mon Sep 17 00:00:00 2001 From: Scott Mo Date: Thu, 5 Feb 2026 16:38:20 -0600 Subject: [PATCH 16/19] Apply suggestion from @jshackell-sfdc Co-authored-by: Juliet Shackell <63259011+jshackell-sfdc@users.noreply.github.com> --- messages/digitalExperienceSite.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/digitalExperienceSite.md b/messages/digitalExperienceSite.md index 7b6e8512..a6d08aaf 100644 --- a/messages/digitalExperienceSite.md +++ b/messages/digitalExperienceSite.md @@ -22,7 +22,7 @@ Name of the Experience Cloud site to generate. # flags.template.summary -Template to use for site generation. +Template to use when generating the site. # flags.template.description From f04bcd44004755bae16e6cc71e37e0fdbaf49f58 Mon Sep 17 00:00:00 2001 From: Scott Mo Date: Thu, 5 Feb 2026 16:41:13 -0600 Subject: [PATCH 17/19] Apply suggestion from @jshackell-sfdc Co-authored-by: Juliet Shackell <63259011+jshackell-sfdc@users.noreply.github.com> --- messages/digitalExperienceSite.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/digitalExperienceSite.md b/messages/digitalExperienceSite.md index a6d08aaf..3718c0fc 100644 --- a/messages/digitalExperienceSite.md +++ b/messages/digitalExperienceSite.md @@ -28,7 +28,7 @@ Template to use when generating the site. Supported templates: -- BuildYourOwnLWR - +- BuildYourOwnLWR - Creates blazing-fast digital experiences, such as websites, microsites, and portals, using the Lightning Web Components programming model. Powered by Lightning Web Runtime (LWR), this customizable template delivers unparalleled site performance. For additional details, see this Salesforce Help topic: https://help.salesforce.com/s/articleView?id=experience.rss_build_your_own_lwr.htm. # flags.url-path-prefix.summary From 6c9b3eaf8a4ba35e38b481745c1a912f720bc6a5 Mon Sep 17 00:00:00 2001 From: Scott Mo Date: Thu, 5 Feb 2026 16:41:23 -0600 Subject: [PATCH 18/19] Apply suggestion from @jshackell-sfdc Co-authored-by: Juliet Shackell <63259011+jshackell-sfdc@users.noreply.github.com> --- messages/digitalExperienceSite.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/digitalExperienceSite.md b/messages/digitalExperienceSite.md index 3718c0fc..7163a9b1 100644 --- a/messages/digitalExperienceSite.md +++ b/messages/digitalExperienceSite.md @@ -8,7 +8,7 @@ Creates an Experience Cloud site with the specified template, name, and URL path # examples -- Generate a BYO LWR site named "mysite" with URL path prefix "mysite": +- Generate an Experience Cloud site using the BuildYourOwnLWR template. The site is called "mysite" and has the URL path prefix "mysite": <%= config.bin %> <%= command.id %> --template BuildYourOwnLWR --name mysite --url-path-prefix mysite From 9f4bd7dd02415f19d5255bf57601686f6fe7dc48 Mon Sep 17 00:00:00 2001 From: Scott Mo Date: Mon, 9 Feb 2026 11:48:21 -0600 Subject: [PATCH 19/19] chore: bump template version --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index dcb89f9d..9d24bda3 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "dependencies": { "@salesforce/core": "^8.25.0", "@salesforce/sf-plugins-core": "^12", - "@salesforce/templates": "^65.5.2" + "@salesforce/templates": "^65.5.3" }, "devDependencies": { "@oclif/plugin-command-snapshot": "^5.3.8", diff --git a/yarn.lock b/yarn.lock index f5d3b2cd..222fc4cb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2009,10 +2009,10 @@ cli-progress "^3.12.0" terminal-link "^3.0.0" -"@salesforce/templates@^65.5.2": - version "65.5.2" - resolved "https://registry.yarnpkg.com/@salesforce/templates/-/templates-65.5.2.tgz#33dc497908c94cfb1b8ebd93f790e24d4775794b" - integrity sha512-SBlyOPmzc3/+/LmeN7q62/cRTBeLI3g3O6I4JQPzvXuyzdUHs71NYLopQN35lV75BceF3WYKnV3+p/eTIp8Okg== +"@salesforce/templates@^65.5.3": + version "65.5.3" + resolved "https://registry.yarnpkg.com/@salesforce/templates/-/templates-65.5.3.tgz#8d6fcb4bfabfbe0b7b79e129f7ed26f6fb7357aa" + integrity sha512-ocnHa3dyHNfhDjljHNuxDUfQ0SHA+blswilLELNswvdyaKmNFDpmF3kKz75gsibuQ6MT32FA0TlEbsVlWWZZpQ== dependencies: "@salesforce/kit" "^3.2.4" "@salesforce/webapp-template-base-react-app-experimental" "^1.3.5"