From 6abe92e8718a345f0dbd1e3b500ad157166939ca Mon Sep 17 00:00:00 2001 From: Deepu Mungamuri Date: Wed, 25 Mar 2026 15:01:19 +0530 Subject: [PATCH 1/3] refactor: rename template generate command and metadata folder to webui MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename command: sf template generate webapp → sf template generate webui - Rename source folder: src/commands/template/generate/webapp/ → webui/ - Rename messages file: messages/webApplication.md → messages/webui.md - Update default output directory: main/default/webapplications → main/default/webui - Update all path references, examples, and tests to use webui Made-with: Cursor --- .../skills/add-template-generator/SKILL.md | 16 +++++-- CLAUDE.md | 11 +++-- command-snapshot.json | 4 +- messages/{webApplication.md => webui.md} | 17 +++---- package.json | 4 +- .../generate/{webapp => webui}/index.ts | 15 +++---- .../generate/{webapp => webui}/index.nut.ts | 44 +++++++++---------- 7 files changed, 61 insertions(+), 50 deletions(-) rename messages/{webApplication.md => webui.md} (65%) rename src/commands/template/generate/{webapp => webui}/index.ts (86%) rename test/commands/template/generate/{webapp => webui}/index.nut.ts (72%) diff --git a/.claude/skills/add-template-generator/SKILL.md b/.claude/skills/add-template-generator/SKILL.md index 8e462544..ec43682d 100644 --- a/.claude/skills/add-template-generator/SKILL.md +++ b/.claude/skills/add-template-generator/SKILL.md @@ -2,6 +2,7 @@ name: add-template-generator description: Add a new template generator command to the CLI --- + Use this workflow whenever exposing a generator from salesforcedx-templates to CLI users. # Add Template Generator Command Workflow @@ -24,7 +25,8 @@ sf dev generate command -n template:generate:{metadataType}:{optionalSubTemplate ``` **Notes:** -- Replace `{metadataType}` with your metadata type (e.g., `flexipage`, `apex`) + +- Replace `{metadataType}` with your metadata type (e.g., `flexipage`, `multi-framework`, `apex`) - Only add `{optionalSubTemplate}` if you need nested generators (e.g., `digital-experience:site`) - This creates the command file, updates oclif metadata, and adds NUTs @@ -61,6 +63,7 @@ public static readonly state = 'beta'; // or 'preview' ``` **State options:** + - `beta`: Shows beta warning to users - `preview`: Shows preview warning to users - No state: Command is GA (requires backwards compatibility) @@ -100,11 +103,13 @@ sf dev generate flag ``` This will: + - Add the flag to your command's `flags` object - Generate TypeScript types - Add entries to the `messages.md` file **Common flags to consider:** + - `--name` / `-n`: Name of the generated item (usually required) - `--output-dir` / `-d`: Output directory (default: '.') - `--template` / `-t`: Template type selection (if multiple templates) @@ -113,6 +118,7 @@ This will: ## Step 6: Review Message Files Check `messages/{metadataType}.md` (merge from `template.generate.{metadataType}.md` if generator created a separate file) and ensure: + - Summary is clear and concise - Description provides helpful context - Flag descriptions are detailed and explain constraints @@ -129,9 +135,9 @@ import { runGenerator } from '../../utils/templateCommand.js'; public async run(): Promise { const { flags } = await this.parse(CommandClass); - + // Add any pre-processing or validation here - + return runGenerator({ templateType: TemplateType.{YourMetadataType}, opts: flags, @@ -143,6 +149,7 @@ public async run(): Promise { ## Step 8: Write/Update NUTs Review the auto-generated NUTs in `test/commands/template/generate/{metadataType}/`. Add tests to validate: + - Required flags work correctly - Optional flags are respected - Correct files are created in the right locations @@ -164,6 +171,7 @@ Test your command: ```bash sf template generate {metadataType} --name TestExample --output-dir ./test-output +# e.g. sf template generate multi-framework --name MyApp --output-dir ./test-output ``` Verify the generated files are correct. @@ -212,4 +220,4 @@ Before opening PR ensure: - flags validated - messages documented - NUTs pass -- topics updated \ No newline at end of file +- topics updated diff --git a/CLAUDE.md b/CLAUDE.md index 6da899cb..1379c8ef 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -30,6 +30,7 @@ Command structure: src/commands/template/generate/{metadataType}/ Files: + - index.ts → top-level generator - {subTemplate}.ts → nested generator @@ -38,7 +39,9 @@ Naming pattern: sf template generate {metadataType} {optionalSubTemplate} Examples: + - sf template generate flexipage +- sf template generate multi-framework - sf template generate digital-experience site --- @@ -92,13 +95,13 @@ Only GA commands require permanent backwards compatibility. All generators should call: runGenerator({ - templateType: TemplateType.X, - opts: flags, - ux +templateType: TemplateType.X, +opts: flags, +ux }) --- ## Reference Docs -Use official Salesforce CLI docs when needed. \ No newline at end of file +Use official Salesforce CLI docs when needed. diff --git a/command-snapshot.json b/command-snapshot.json index ca0bd5b6..3a5bd8e1 100644 --- a/command-snapshot.json +++ b/command-snapshot.json @@ -148,8 +148,8 @@ "plugin": "@salesforce/plugin-templates" }, { - "alias": ["webapp:generate"], - "command": "template:generate:webapp", + "alias": ["webui:generate"], + "command": "template:generate:webui", "flagAliases": [], "flagChars": ["d", "l", "n", "t"], "flags": ["api-version", "flags-dir", "json", "label", "name", "output-dir", "template"], diff --git a/messages/webApplication.md b/messages/webui.md similarity index 65% rename from messages/webApplication.md rename to messages/webui.md index 178b4580..5dd26544 100644 --- a/messages/webApplication.md +++ b/messages/webui.md @@ -4,7 +4,7 @@ Generate a web application. # description -Generates a web application in the specified directory or the current working directory. The web application files are created in a folder with the designated name. Web application files must be contained in a parent directory called "webapplications" in your package directory. Either run this command from an existing directory of this name, or use the --output-dir flag to create one or point to an existing one. +Generates a web application in the specified directory or the current working directory. The web application files are created in a folder with the designated name. Web application files must be contained in a parent directory called "webui" in your package directory. Either run this command from an existing directory of this name, or use the --output-dir flag to create one or point to an existing one. # examples @@ -16,9 +16,9 @@ Generates a web application in the specified directory or the current working di <%= config.bin %> <%= command.id %> --name MyReactApp --template reactbasic -- Generate the web application in the "force-app/main/default/webapplications" directory: +- Generate the web application in the "force-app/main/default/webui" directory: - <%= config.bin %> <%= command.id %> --name MyWebApp --output-dir force-app/main/default/webapplications + <%= config.bin %> <%= command.id %> --name MyWebApp --output-dir force-app/main/default/webui # flags.name.summary @@ -50,12 +50,13 @@ Directory for saving the created files. # flags.output-dir.description -The location can be an absolute path or relative to the current working directory. +The location can be an absolute path or relative to the current working directory. -**Important:** The generator automatically ensures the output directory ends with "webapplications". If your specified path doesn't end with "webapplications", it's automatically appended. The web application is created at "/". +**Important:** The generator automatically ensures the output directory ends with "webui". If your specified path doesn't end with "webui", it's automatically appended. The web application is created at "/". **Examples:** -- "--output-dir force-app/main/default" → Creates a web application at "force-app/main/default/webapplications/MyWebApp/" -- "--output-dir force-app/main/default/webapplications" → Creates a web application at "force-app/main/default/webapplications/MyWebApp/" (no change) -If not specified, the command reads your sfdx-project.json and defaults to "webapplications" directory within your default package directory. When running outside a Salesforce DX project, defaults to the current directory. +- "--output-dir force-app/main/default" → Creates a web application at "force-app/main/default/webui/MyWebApp/" +- "--output-dir force-app/main/default/webui" → Creates a web application at "force-app/main/default/webui/MyWebApp/" (no change) + +If not specified, the command reads your sfdx-project.json and defaults to "webui" directory within your default package directory. When running outside a Salesforce DX project, defaults to the current directory. diff --git a/package.json b/package.json index 8e348b93..92210be1 100644 --- a/package.json +++ b/package.json @@ -84,8 +84,8 @@ "visualforce": { "description": "Create a visualforce page or component." }, - "webapp": { - "description": "Create a web application." + "multi-framework": { + "description": "Create a multi-framework web application." } } } diff --git a/src/commands/template/generate/webapp/index.ts b/src/commands/template/generate/webui/index.ts similarity index 86% rename from src/commands/template/generate/webapp/index.ts rename to src/commands/template/generate/webui/index.ts index e91d2b57..076464ba 100644 --- a/src/commands/template/generate/webapp/index.ts +++ b/src/commands/template/generate/webui/index.ts @@ -12,15 +12,14 @@ 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', 'webApplication'); +const messages = Messages.loadMessages('@salesforce/plugin-templates', 'webui'); -export default class WebAppGenerate extends SfCommand { +export default class WebUIGenerate 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 hidden = true; // Hide from external developers until GA - public static readonly aliases = ['webapp:generate']; - public static readonly deprecateAliases = true; + public static readonly aliases = ['webui:generate']; public static readonly flags = { name: Flags.string({ char: 'n', @@ -50,23 +49,23 @@ export default class WebAppGenerate extends SfCommand { /** * Resolves the default output directory by reading the project's sfdx-project.json. - * Returns the path to webapplications under the default package directory, + * Returns the path to webui under 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', 'webapplications'); + return path.join(defaultPackage.path, 'main', 'default', 'webui'); } catch { return '.'; } } public async run(): Promise { - const { flags } = await this.parse(WebAppGenerate); + const { flags } = await this.parse(WebUIGenerate); - const outputDir = flags['output-dir'] ?? (await WebAppGenerate.getDefaultOutputDir()); + const outputDir = flags['output-dir'] ?? (await WebUIGenerate.getDefaultOutputDir()); const flagsAsOptions: WebApplicationOptions = { webappname: flags.name, diff --git a/test/commands/template/generate/webapp/index.nut.ts b/test/commands/template/generate/webui/index.nut.ts similarity index 72% rename from test/commands/template/generate/webapp/index.nut.ts rename to test/commands/template/generate/webui/index.nut.ts index efc2168c..f31cc80b 100644 --- a/test/commands/template/generate/webapp/index.nut.ts +++ b/test/commands/template/generate/webui/index.nut.ts @@ -23,9 +23,9 @@ describe('template generate web application:', () => { }); describe('Check webapp creation with default template', () => { - it('should create webapp using default template in webapplications directory', () => { - const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default', 'webapplications'); - execCmd(`template generate webapp --name MyWebApp --output-dir "${outputDir}"`, { ensureExitCode: 0 }); + it('should create webapp using default template in webui directory', () => { + const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default', 'webui'); + execCmd(`template generate webui --name MyWebApp --output-dir "${outputDir}"`, { ensureExitCode: 0 }); assert.file([ path.join(outputDir, 'MyWebApp', 'MyWebApp.webapplication-meta.xml'), path.join(outputDir, 'MyWebApp', 'src', 'index.html'), @@ -37,9 +37,9 @@ describe('template generate web application:', () => { ); }); - it('should default to project webapplications directory when --output-dir is omitted', () => { - const expectedOutputDir = path.join(session.project.dir, 'force-app', 'main', 'default', 'webapplications'); - execCmd('template generate webapp --name DefaultDirApp', { ensureExitCode: 0 }); + it('should default to project webui directory when --output-dir is omitted', () => { + const expectedOutputDir = path.join(session.project.dir, 'force-app', 'main', 'default', 'webui'); + execCmd('template generate webui --name DefaultDirApp', { ensureExitCode: 0 }); assert.file([ path.join(expectedOutputDir, 'DefaultDirApp', 'DefaultDirApp.webapplication-meta.xml'), path.join(expectedOutputDir, 'DefaultDirApp', 'src', 'index.html'), @@ -48,8 +48,8 @@ describe('template generate web application:', () => { }); it('should create webapp with custom label', () => { - const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default', 'webapplications'); - execCmd(`template generate webapp --name TestApp --label "Custom Label" --output-dir "${outputDir}"`, { + const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default', 'webui'); + execCmd(`template generate webui --name TestApp --label "Custom Label" --output-dir "${outputDir}"`, { ensureExitCode: 0, }); assert.file([ @@ -62,8 +62,8 @@ describe('template generate web application:', () => { describe('Check webapp creation with reactbasic template', () => { it('should create React webapp with all required files', () => { - const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default', 'webapplications'); - execCmd(`template generate webapp --name MyReactApp --template reactbasic --output-dir "${outputDir}"`, { + const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default', 'webui'); + execCmd(`template generate webui --name MyReactApp --template reactbasic --output-dir "${outputDir}"`, { ensureExitCode: 0, }); assert.file([ @@ -78,38 +78,38 @@ describe('template generate web application:', () => { describe('Check that all invalid name errors are thrown', () => { it('should throw a missing name error', () => { - const stderr = execCmd('template generate webapp').shellOutput.stderr; + const stderr = execCmd('template generate webui').shellOutput.stderr; expect(stderr).to.contain('Missing required flag'); }); it('should throw invalid non alphanumeric webapp name error', () => { - const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default', 'webapplications'); - const stderr = execCmd(`template generate webapp --name /a --output-dir "${outputDir}"`).shellOutput.stderr; + const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default', 'webui'); + const stderr = execCmd(`template generate webui --name /a --output-dir "${outputDir}"`).shellOutput.stderr; expect(stderr).to.contain(nls.localize('AlphaNumericNameError')); }); it('should throw invalid webapp name starting with numeric error', () => { - const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default', 'webapplications'); - const stderr = execCmd(`template generate webapp --name 3aa --output-dir "${outputDir}"`).shellOutput.stderr; + const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default', 'webui'); + const stderr = execCmd(`template generate webui --name 3aa --output-dir "${outputDir}"`).shellOutput.stderr; expect(stderr).to.contain(nls.localize('NameMustStartWithLetterError')); }); it('should throw invalid webapp name ending with underscore error', () => { - const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default', 'webapplications'); - const stderr = execCmd(`template generate webapp --name a_ --output-dir "${outputDir}"`).shellOutput.stderr; + const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default', 'webui'); + const stderr = execCmd(`template generate webui --name a_ --output-dir "${outputDir}"`).shellOutput.stderr; expect(stderr).to.contain(nls.localize('EndWithUnderscoreError')); }); it('should throw invalid webapp name with double underscore error', () => { - const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default', 'webapplications'); - const stderr = execCmd(`template generate webapp --name a__a --output-dir "${outputDir}"`).shellOutput.stderr; + const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default', 'webui'); + const stderr = execCmd(`template generate webui --name a__a --output-dir "${outputDir}"`).shellOutput.stderr; expect(stderr).to.contain(nls.localize('DoubleUnderscoreError')); }); - it('should auto-append webapplications folder when output dir does not end with webapplications', () => { + it('should auto-append webui folder when output dir does not end with webui', () => { const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default', 'test-dir'); - const expectedOutputDir = path.join(outputDir, 'webapplications'); - execCmd(`template generate webapp --name TestApp --output-dir "${outputDir}"`, { ensureExitCode: 0 }); + const expectedOutputDir = path.join(outputDir, 'webui'); + execCmd(`template generate webui --name TestApp --output-dir "${outputDir}"`, { ensureExitCode: 0 }); assert.file([ path.join(expectedOutputDir, 'TestApp', 'TestApp.webapplication-meta.xml'), path.join(expectedOutputDir, 'TestApp', 'src', 'index.html'), From 7070efa4953f5b8d94001576804ef448fa8e16cb Mon Sep 17 00:00:00 2001 From: Deepu Mungamuri Date: Fri, 27 Mar 2026 12:18:34 +0530 Subject: [PATCH 2/3] refactor: rename webui generate command to ui-bundle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Renames the `template generate webui` command and all associated artifacts to `template generate ui-bundle` to align with the updated metadata type name. Changes: - Rename src/commands/template/generate/webui/ → ui-bundle/ - Rename test/commands/template/generate/webui/ → ui-bundle/ - Replace messages/webui.md with messages/ui-bundle.generate.md - Update command-snapshot.json: command ID and alias updated (template:generate:webui → template:generate:ui-bundle, webui:generate → ui-bundle:generate) - Update package.json: taxonomy entry renamed from multi-framework/webui to ui-bundle, and a top-level ui-bundle topic added Made-with: Cursor --- command-snapshot.json | 4 +- messages/ui-bundle.generate.md | 62 ++++++++++++++++ messages/webui.md | 62 ---------------- package.json | 7 +- .../generate/{webui => ui-bundle}/index.ts | 14 ++-- .../{webui => ui-bundle}/index.nut.ts | 72 +++++++++---------- 6 files changed, 112 insertions(+), 109 deletions(-) create mode 100644 messages/ui-bundle.generate.md delete mode 100644 messages/webui.md rename src/commands/template/generate/{webui => ui-bundle}/index.ts (87%) rename test/commands/template/generate/{webui => ui-bundle}/index.nut.ts (54%) diff --git a/command-snapshot.json b/command-snapshot.json index 3a5bd8e1..d627cd56 100644 --- a/command-snapshot.json +++ b/command-snapshot.json @@ -148,8 +148,8 @@ "plugin": "@salesforce/plugin-templates" }, { - "alias": ["webui:generate"], - "command": "template:generate:webui", + "alias": ["ui-bundle:generate"], + "command": "template:generate:ui-bundle", "flagAliases": [], "flagChars": ["d", "l", "n", "t"], "flags": ["api-version", "flags-dir", "json", "label", "name", "output-dir", "template"], diff --git a/messages/ui-bundle.generate.md b/messages/ui-bundle.generate.md new file mode 100644 index 00000000..4714db01 --- /dev/null +++ b/messages/ui-bundle.generate.md @@ -0,0 +1,62 @@ +# summary + +Generate a UI bundle. + +# description + +Generates a UI bundle in the specified directory or the current working directory. The UI bundle files are created in a folder with the designated name. UI bundle files must be contained in a parent directory called "uiBundles" in your package directory. Either run this command from an existing directory of this name, or use the --output-dir flag to create one or point to an existing one. + +# examples + +- Generate a UI bundle called MyUiBundle in the current directory: + + <%= config.bin %> <%= command.id %> --name MyUiBundle + +- Generate a React-based UI bundle: + + <%= config.bin %> <%= command.id %> --name MyReactApp --template reactbasic + +- Generate the UI bundle in the "force-app/main/default/uiBundles" directory: + + <%= config.bin %> <%= command.id %> --name MyUiBundle --output-dir force-app/main/default/uiBundles + +# flags.name.summary + +Name of the generated UI bundle. + +# flags.name.description + +This name can contain only underscores and alphanumeric characters, and must be unique in your org. It must begin with a letter, not include spaces, not end with an underscore, and not contain two consecutive underscores. + +# flags.template.summary + +Template to use for file creation. + +# flags.template.description + +Supplied parameter values or default values are filled into a copy of the template. + +# flags.label.summary + +Master label for the UI bundle. + +# flags.label.description + +If not specified, the label is derived from the name. + +# flags.output-dir.summary + +Directory for saving the created files. + +# flags.output-dir.description + +The location can be an absolute path or relative to the current working directory. + +**Important:** The generator automatically ensures the output directory ends with "uiBundles". If your specified path doesn't end with "uiBundles", it's automatically appended. The UI bundle is created at "/". + +**Examples:** + +- "--output-dir force-app/main/default" → Creates a UI bundle at "force-app/main/default/uiBundles/MyUiBundle/" +- "--output-dir force-app/main/default/uiBundles" → Creates a UI bundle at "force-app/main/default/uiBundles/MyUiBundle/" (no change) + +If not specified, the command reads your sfdx-project.json and defaults to "uiBundles" directory within your default package directory. When running outside a Salesforce DX project, defaults to the current directory. diff --git a/messages/webui.md b/messages/webui.md deleted file mode 100644 index 5dd26544..00000000 --- a/messages/webui.md +++ /dev/null @@ -1,62 +0,0 @@ -# summary - -Generate a web application. - -# description - -Generates a web application in the specified directory or the current working directory. The web application files are created in a folder with the designated name. Web application files must be contained in a parent directory called "webui" in your package directory. Either run this command from an existing directory of this name, or use the --output-dir flag to create one or point to an existing one. - -# examples - -- Generate a web application called MyWebApp in the current directory: - - <%= config.bin %> <%= command.id %> --name MyWebApp - -- Generate a React-based web application: - - <%= config.bin %> <%= command.id %> --name MyReactApp --template reactbasic - -- Generate the web application in the "force-app/main/default/webui" directory: - - <%= config.bin %> <%= command.id %> --name MyWebApp --output-dir force-app/main/default/webui - -# flags.name.summary - -Name of the generated web application. - -# flags.name.description - -This name can contain only underscores and alphanumeric characters, and must be unique in your org. It must begin with a letter, not include spaces, not end with an underscore, and not contain two consecutive underscores. - -# flags.template.summary - -Template to use for file creation. - -# flags.template.description - -Supplied parameter values or default values are filled into a copy of the template. - -# flags.label.summary - -Master label for the web application. - -# flags.label.description - -If not specified, the label is derived from the name. - -# flags.output-dir.summary - -Directory for saving the created files. - -# flags.output-dir.description - -The location can be an absolute path or relative to the current working directory. - -**Important:** The generator automatically ensures the output directory ends with "webui". If your specified path doesn't end with "webui", it's automatically appended. The web application is created at "/". - -**Examples:** - -- "--output-dir force-app/main/default" → Creates a web application at "force-app/main/default/webui/MyWebApp/" -- "--output-dir force-app/main/default/webui" → Creates a web application at "force-app/main/default/webui/MyWebApp/" (no change) - -If not specified, the command reads your sfdx-project.json and defaults to "webui" directory within your default package directory. When running outside a Salesforce DX project, defaults to the current directory. diff --git a/package.json b/package.json index 92210be1..3ad71c4f 100644 --- a/package.json +++ b/package.json @@ -84,12 +84,15 @@ "visualforce": { "description": "Create a visualforce page or component." }, - "multi-framework": { - "description": "Create a multi-framework web application." + "ui-bundle": { + "description": "Generate a UI bundle." } } } } + }, + "ui-bundle": { + "description": "Work with UI bundles." } }, "flexibleTaxonomy": true, diff --git a/src/commands/template/generate/webui/index.ts b/src/commands/template/generate/ui-bundle/index.ts similarity index 87% rename from src/commands/template/generate/webui/index.ts rename to src/commands/template/generate/ui-bundle/index.ts index 076464ba..5f4acf0c 100644 --- a/src/commands/template/generate/webui/index.ts +++ b/src/commands/template/generate/ui-bundle/index.ts @@ -12,14 +12,14 @@ 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', 'webui'); +const messages = Messages.loadMessages('@salesforce/plugin-templates', 'ui-bundle.generate'); -export default class WebUIGenerate extends SfCommand { +export default class UiBundleGenerate 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 hidden = true; // Hide from external developers until GA - public static readonly aliases = ['webui:generate']; + public static readonly aliases = ['ui-bundle:generate']; public static readonly flags = { name: Flags.string({ char: 'n', @@ -49,23 +49,23 @@ export default class WebUIGenerate extends SfCommand { /** * Resolves the default output directory by reading the project's sfdx-project.json. - * Returns the path to webui under the default package directory, + * Returns the path to uiBundles under 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', 'webui'); + return path.join(defaultPackage.path, 'main', 'default', 'uiBundles'); } catch { return '.'; } } public async run(): Promise { - const { flags } = await this.parse(WebUIGenerate); + const { flags } = await this.parse(UiBundleGenerate); - const outputDir = flags['output-dir'] ?? (await WebUIGenerate.getDefaultOutputDir()); + const outputDir = flags['output-dir'] ?? (await UiBundleGenerate.getDefaultOutputDir()); const flagsAsOptions: WebApplicationOptions = { webappname: flags.name, diff --git a/test/commands/template/generate/webui/index.nut.ts b/test/commands/template/generate/ui-bundle/index.nut.ts similarity index 54% rename from test/commands/template/generate/webui/index.nut.ts rename to test/commands/template/generate/ui-bundle/index.nut.ts index f31cc80b..61c91d6e 100644 --- a/test/commands/template/generate/webui/index.nut.ts +++ b/test/commands/template/generate/ui-bundle/index.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('template generate web application:', () => { +describe('template generate ui-bundle:', () => { let session: TestSession; before(async () => { session = await TestSession.create({ @@ -22,24 +22,24 @@ describe('template generate web application:', () => { await session?.clean(); }); - describe('Check webapp creation with default template', () => { - it('should create webapp using default template in webui directory', () => { - const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default', 'webui'); - execCmd(`template generate webui --name MyWebApp --output-dir "${outputDir}"`, { ensureExitCode: 0 }); + describe('Check UI bundle creation with default template', () => { + it('should create UI bundle using default template in uiBundles directory', () => { + const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default', 'uiBundles'); + execCmd(`template generate ui-bundle --name MyUiBundle --output-dir "${outputDir}"`, { ensureExitCode: 0 }); assert.file([ - path.join(outputDir, 'MyWebApp', 'MyWebApp.webapplication-meta.xml'), - path.join(outputDir, 'MyWebApp', 'src', 'index.html'), - path.join(outputDir, 'MyWebApp', 'webapplication.json'), + path.join(outputDir, 'MyUiBundle', 'MyUiBundle.webapplication-meta.xml'), + path.join(outputDir, 'MyUiBundle', 'src', 'index.html'), + path.join(outputDir, 'MyUiBundle', 'webapplication.json'), ]); assert.fileContent( - path.join(outputDir, 'MyWebApp', 'MyWebApp.webapplication-meta.xml'), - 'My Web App' + path.join(outputDir, 'MyUiBundle', 'MyUiBundle.webapplication-meta.xml'), + 'My Ui Bundle' ); }); - it('should default to project webui directory when --output-dir is omitted', () => { - const expectedOutputDir = path.join(session.project.dir, 'force-app', 'main', 'default', 'webui'); - execCmd('template generate webui --name DefaultDirApp', { ensureExitCode: 0 }); + it('should default to project uiBundles directory when --output-dir is omitted', () => { + const expectedOutputDir = path.join(session.project.dir, 'force-app', 'main', 'default', 'uiBundles'); + execCmd('template generate ui-bundle --name DefaultDirApp', { ensureExitCode: 0 }); assert.file([ path.join(expectedOutputDir, 'DefaultDirApp', 'DefaultDirApp.webapplication-meta.xml'), path.join(expectedOutputDir, 'DefaultDirApp', 'src', 'index.html'), @@ -47,9 +47,9 @@ describe('template generate web application:', () => { ]); }); - it('should create webapp with custom label', () => { - const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default', 'webui'); - execCmd(`template generate webui --name TestApp --label "Custom Label" --output-dir "${outputDir}"`, { + it('should create UI bundle with custom label', () => { + const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default', 'uiBundles'); + execCmd(`template generate ui-bundle --name TestApp --label "Custom Label" --output-dir "${outputDir}"`, { ensureExitCode: 0, }); assert.file([ @@ -60,10 +60,10 @@ describe('template generate web application:', () => { }); }); - describe('Check webapp creation with reactbasic template', () => { - it('should create React webapp with all required files', () => { - const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default', 'webui'); - execCmd(`template generate webui --name MyReactApp --template reactbasic --output-dir "${outputDir}"`, { + describe('Check UI bundle creation with reactbasic template', () => { + it('should create React UI bundle with all required files', () => { + const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default', 'uiBundles'); + execCmd(`template generate ui-bundle --name MyReactApp --template reactbasic --output-dir "${outputDir}"`, { ensureExitCode: 0, }); assert.file([ @@ -78,38 +78,38 @@ describe('template generate web application:', () => { describe('Check that all invalid name errors are thrown', () => { it('should throw a missing name error', () => { - const stderr = execCmd('template generate webui').shellOutput.stderr; + const stderr = execCmd('template generate ui-bundle').shellOutput.stderr; expect(stderr).to.contain('Missing required flag'); }); - it('should throw invalid non alphanumeric webapp name error', () => { - const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default', 'webui'); - const stderr = execCmd(`template generate webui --name /a --output-dir "${outputDir}"`).shellOutput.stderr; + it('should throw invalid non alphanumeric name error', () => { + const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default', 'uiBundles'); + const stderr = execCmd(`template generate ui-bundle --name /a --output-dir "${outputDir}"`).shellOutput.stderr; expect(stderr).to.contain(nls.localize('AlphaNumericNameError')); }); - it('should throw invalid webapp name starting with numeric error', () => { - const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default', 'webui'); - const stderr = execCmd(`template generate webui --name 3aa --output-dir "${outputDir}"`).shellOutput.stderr; + it('should throw invalid name starting with numeric error', () => { + const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default', 'uiBundles'); + const stderr = execCmd(`template generate ui-bundle --name 3aa --output-dir "${outputDir}"`).shellOutput.stderr; expect(stderr).to.contain(nls.localize('NameMustStartWithLetterError')); }); - it('should throw invalid webapp name ending with underscore error', () => { - const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default', 'webui'); - const stderr = execCmd(`template generate webui --name a_ --output-dir "${outputDir}"`).shellOutput.stderr; + it('should throw invalid name ending with underscore error', () => { + const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default', 'uiBundles'); + const stderr = execCmd(`template generate ui-bundle --name a_ --output-dir "${outputDir}"`).shellOutput.stderr; expect(stderr).to.contain(nls.localize('EndWithUnderscoreError')); }); - it('should throw invalid webapp name with double underscore error', () => { - const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default', 'webui'); - const stderr = execCmd(`template generate webui --name a__a --output-dir "${outputDir}"`).shellOutput.stderr; + it('should throw invalid name with double underscore error', () => { + const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default', 'uiBundles'); + const stderr = execCmd(`template generate ui-bundle --name a__a --output-dir "${outputDir}"`).shellOutput.stderr; expect(stderr).to.contain(nls.localize('DoubleUnderscoreError')); }); - it('should auto-append webui folder when output dir does not end with webui', () => { + it('should auto-append uiBundles folder when output dir does not end with uiBundles', () => { const outputDir = path.join(session.project.dir, 'force-app', 'main', 'default', 'test-dir'); - const expectedOutputDir = path.join(outputDir, 'webui'); - execCmd(`template generate webui --name TestApp --output-dir "${outputDir}"`, { ensureExitCode: 0 }); + const expectedOutputDir = path.join(outputDir, 'uiBundles'); + execCmd(`template generate ui-bundle --name TestApp --output-dir "${outputDir}"`, { ensureExitCode: 0 }); assert.file([ path.join(expectedOutputDir, 'TestApp', 'TestApp.webapplication-meta.xml'), path.join(expectedOutputDir, 'TestApp', 'src', 'index.html'), From 24e17594f42dd38fa55de1a710e3ac874485334c Mon Sep 17 00:00:00 2001 From: Deepu Mungamuri Date: Fri, 27 Mar 2026 12:21:14 +0530 Subject: [PATCH 3/3] docs: update multi-framework example references to ui-bundle Made-with: Cursor --- .claude/skills/add-template-generator/SKILL.md | 4 ++-- CLAUDE.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.claude/skills/add-template-generator/SKILL.md b/.claude/skills/add-template-generator/SKILL.md index ec43682d..5623554b 100644 --- a/.claude/skills/add-template-generator/SKILL.md +++ b/.claude/skills/add-template-generator/SKILL.md @@ -26,7 +26,7 @@ sf dev generate command -n template:generate:{metadataType}:{optionalSubTemplate **Notes:** -- Replace `{metadataType}` with your metadata type (e.g., `flexipage`, `multi-framework`, `apex`) +- Replace `{metadataType}` with your metadata type (e.g., `flexipage`, `ui-bundle`, `apex`) - Only add `{optionalSubTemplate}` if you need nested generators (e.g., `digital-experience:site`) - This creates the command file, updates oclif metadata, and adds NUTs @@ -171,7 +171,7 @@ Test your command: ```bash sf template generate {metadataType} --name TestExample --output-dir ./test-output -# e.g. sf template generate multi-framework --name MyApp --output-dir ./test-output +# e.g. sf template generate ui-bundle --name MyApp --output-dir ./test-output ``` Verify the generated files are correct. diff --git a/CLAUDE.md b/CLAUDE.md index 1379c8ef..91ceedf5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -41,7 +41,7 @@ sf template generate {metadataType} {optionalSubTemplate} Examples: - sf template generate flexipage -- sf template generate multi-framework +- sf template generate ui-bundle - sf template generate digital-experience site ---