From f86dc7e192cfd299a9047ffb14421c5f4b3b6c18 Mon Sep 17 00:00:00 2001 From: Deepu Mungamuri Date: Fri, 27 Mar 2026 13:30:00 +0530 Subject: [PATCH 01/13] refactor: rename webapp template generate command to ui-bundle Rename `sf template generate webapp` to `sf template generate ui-bundle`: - Rename src/commands/template/generate/webapp/ to ui-bundle/ - Rename messages/webApplication.md to messages/ui-bundle.generate.md - Extract UI_BUNDLES_DIR constant for the uiBundles folder name - Update command-snapshot.json with new ui-bundle command entry - Update package.json oclif topics to reflect ui-bundle namespace - Update CLAUDE.md and add-template-generator skill with ui-bundle examples W-21575871 Made-with: Cursor --- .../skills/add-template-generator/SKILL.md | 16 ++- CLAUDE.md | 11 +- command-snapshot.json | 4 +- messages/ui-bundle.generate.md | 62 +++++++++ messages/webApplication.md | 61 --------- package.json | 19 +-- .../generate/{webapp => ui-bundle}/index.ts | 17 +-- .../template/generate/ui-bundle/index.nut.ts | 123 ++++++++++++++++++ .../template/generate/webapp/index.nut.ts | 120 ----------------- 9 files changed, 226 insertions(+), 207 deletions(-) create mode 100644 messages/ui-bundle.generate.md delete mode 100644 messages/webApplication.md rename src/commands/template/generate/{webapp => ui-bundle}/index.ts (84%) create mode 100644 test/commands/template/generate/ui-bundle/index.nut.ts delete mode 100644 test/commands/template/generate/webapp/index.nut.ts diff --git a/.claude/skills/add-template-generator/SKILL.md b/.claude/skills/add-template-generator/SKILL.md index 8e462544..5623554b 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`, `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 @@ -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 ui-bundle --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..91ceedf5 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 ui-bundle - 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..d627cd56 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": ["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/webApplication.md b/messages/webApplication.md deleted file mode 100644 index 178b4580..00000000 --- a/messages/webApplication.md +++ /dev/null @@ -1,61 +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 "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. - -# 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/webapplications" directory: - - <%= config.bin %> <%= command.id %> --name MyWebApp --output-dir force-app/main/default/webapplications - -# 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 "webapplications". If your specified path doesn't end with "webapplications", 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. diff --git a/package.json b/package.json index 8e348b93..2f1c4357 100644 --- a/package.json +++ b/package.json @@ -1,24 +1,24 @@ { "name": "@salesforce/plugin-templates", "description": "Commands to create metadata from a default or custom template", - "version": "56.10.7", + "version": "56.11.5", "author": "Salesforce", "bugs": "https://github.com/salesforcecli/plugin-templates/issues", "enableO11y": true, "o11yUploadEndpoint": "https://794testsite.my.site.com/byolwr/webruntime/log/metrics", "dependencies": { - "@salesforce/core": "^8.27.0", + "@salesforce/core": "^8.27.1", "@salesforce/sf-plugins-core": "^12", - "@salesforce/templates": "^66.4.1" + "@salesforce/templates": "^66.5.6" }, "devDependencies": { - "@oclif/plugin-command-snapshot": "^5.3.12", + "@oclif/plugin-command-snapshot": "^5.3.13", "@salesforce/cli-plugins-testkit": "^5.3.41", "@salesforce/dev-scripts": "^11.0.4", - "@salesforce/plugin-command-reference": "^3.1.81", + "@salesforce/plugin-command-reference": "^3.1.82", "@types/yeoman-assert": "^3.1.4", "eslint-plugin-sf-plugin": "^1.20.33", - "oclif": "^4.22.87", + "oclif": "^4.22.96", "ts-node": "^10.9.2", "typescript": "^5.9.3", "yeoman-assert": "^3.1.1" @@ -84,12 +84,15 @@ "visualforce": { "description": "Create a visualforce page or component." }, - "webapp": { - "description": "Create a 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/webapp/index.ts b/src/commands/template/generate/ui-bundle/index.ts similarity index 84% rename from src/commands/template/generate/webapp/index.ts rename to src/commands/template/generate/ui-bundle/index.ts index e91d2b57..7868f707 100644 --- a/src/commands/template/generate/webapp/index.ts +++ b/src/commands/template/generate/ui-bundle/index.ts @@ -12,15 +12,16 @@ 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', 'ui-bundle.generate'); -export default class WebAppGenerate extends SfCommand { +export const UI_BUNDLES_DIR = 'uiBundles'; + +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 = ['webapp:generate']; - public static readonly deprecateAliases = true; + public static readonly aliases = ['ui-bundle:generate']; public static readonly flags = { name: Flags.string({ char: 'n', @@ -50,23 +51,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 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', 'webapplications'); + return path.join(defaultPackage.path, 'main', 'default', UI_BUNDLES_DIR); } catch { return '.'; } } public async run(): Promise { - const { flags } = await this.parse(WebAppGenerate); + const { flags } = await this.parse(UiBundleGenerate); - const outputDir = flags['output-dir'] ?? (await WebAppGenerate.getDefaultOutputDir()); + const outputDir = flags['output-dir'] ?? (await UiBundleGenerate.getDefaultOutputDir()); const flagsAsOptions: WebApplicationOptions = { webappname: flags.name, diff --git a/test/commands/template/generate/ui-bundle/index.nut.ts b/test/commands/template/generate/ui-bundle/index.nut.ts new file mode 100644 index 00000000..d31e343b --- /dev/null +++ b/test/commands/template/generate/ui-bundle/index.nut.ts @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2025, 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'; +const UI_BUNDLES_DIR = 'uiBundles'; + +describe('template generate ui-bundle:', () => { + let session: TestSession; + let projectDir: string; + before(async () => { + session = await TestSession.create({ + project: {}, + devhubAuthStrategy: 'NONE', + }); + projectDir = session.project.dir; + }); + after(async () => { + await session?.clean(); + }); + + describe('Check UI bundle creation with default template', () => { + it('should create UI bundle using default template in uiBundles directory', () => { + const outputDir = path.join(projectDir, 'force-app', 'main', 'default', UI_BUNDLES_DIR); + execCmd(`template generate ui-bundle --name MyUiBundle --output-dir "${outputDir}"`, { ensureExitCode: 0 }); + assert.file([ + 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, 'MyUiBundle', 'MyUiBundle.webapplication-meta.xml'), + 'My Ui Bundle' + ); + }); + + it('should default to project uiBundles directory when --output-dir is omitted', () => { + const expectedOutputDir = path.join(projectDir, 'force-app', 'main', 'default', UI_BUNDLES_DIR); + 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'), + path.join(expectedOutputDir, 'DefaultDirApp', 'webapplication.json'), + ]); + }); + + it('should create UI bundle with custom label', () => { + const outputDir = path.join(projectDir, 'force-app', 'main', 'default', UI_BUNDLES_DIR); + execCmd(`template generate ui-bundle --name TestApp --label "Custom Label" --output-dir "${outputDir}"`, { + ensureExitCode: 0, + }); + assert.file([ + path.join(outputDir, 'TestApp', 'TestApp.webapplication-meta.xml'), + path.join(outputDir, 'TestApp', 'src', 'index.html'), + ]); + assert.fileContent(path.join(outputDir, 'TestApp', 'src', 'index.html'), 'Welcome to Web App'); + }); + }); + + describe('Check UI bundle creation with reactbasic template', () => { + it('should create React UI bundle with all required files', () => { + const outputDir = path.join(projectDir, 'force-app', 'main', 'default', UI_BUNDLES_DIR); + execCmd(`template generate ui-bundle --name MyReactApp --template reactbasic --output-dir "${outputDir}"`, { + ensureExitCode: 0, + }); + assert.file([ + path.join(outputDir, 'MyReactApp', 'MyReactApp.webapplication-meta.xml'), + path.join(outputDir, 'MyReactApp', 'index.html'), + path.join(outputDir, 'MyReactApp', 'webapplication.json'), + path.join(outputDir, 'MyReactApp', 'package.json'), + ]); + assert.fileContent(path.join(outputDir, 'MyReactApp', 'package.json'), '"name": "base-react-app"'); + }); + }); + + describe('Check that all invalid name errors are thrown', () => { + it('should throw a missing name error', () => { + const stderr = execCmd('template generate ui-bundle').shellOutput.stderr; + expect(stderr).to.contain('Missing required flag'); + }); + + it('should throw invalid non alphanumeric name error', () => { + const outputDir = path.join(projectDir, 'force-app', 'main', 'default', UI_BUNDLES_DIR); + 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 name starting with numeric error', () => { + const outputDir = path.join(projectDir, 'force-app', 'main', 'default', UI_BUNDLES_DIR); + 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 name ending with underscore error', () => { + const outputDir = path.join(projectDir, 'force-app', 'main', 'default', UI_BUNDLES_DIR); + 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 name with double underscore error', () => { + const outputDir = path.join(projectDir, 'force-app', 'main', 'default', UI_BUNDLES_DIR); + 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 uiBundles folder when output dir does not end with uiBundles', () => { + const outputDir = path.join(projectDir, 'force-app', 'main', 'default', 'test-dir'); + const expectedOutputDir = path.join(outputDir, UI_BUNDLES_DIR); + 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'), + path.join(expectedOutputDir, 'TestApp', 'webapplication.json'), + ]); + }); + }); +}); diff --git a/test/commands/template/generate/webapp/index.nut.ts b/test/commands/template/generate/webapp/index.nut.ts deleted file mode 100644 index efc2168c..00000000 --- a/test/commands/template/generate/webapp/index.nut.ts +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (c) 2025, 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('template generate web application:', () => { - let session: TestSession; - before(async () => { - session = await TestSession.create({ - project: {}, - devhubAuthStrategy: 'NONE', - }); - }); - after(async () => { - await session?.clean(); - }); - - 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 }); - assert.file([ - path.join(outputDir, 'MyWebApp', 'MyWebApp.webapplication-meta.xml'), - path.join(outputDir, 'MyWebApp', 'src', 'index.html'), - path.join(outputDir, 'MyWebApp', 'webapplication.json'), - ]); - assert.fileContent( - path.join(outputDir, 'MyWebApp', 'MyWebApp.webapplication-meta.xml'), - 'My Web App' - ); - }); - - 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 }); - assert.file([ - path.join(expectedOutputDir, 'DefaultDirApp', 'DefaultDirApp.webapplication-meta.xml'), - path.join(expectedOutputDir, 'DefaultDirApp', 'src', 'index.html'), - path.join(expectedOutputDir, 'DefaultDirApp', 'webapplication.json'), - ]); - }); - - 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}"`, { - ensureExitCode: 0, - }); - assert.file([ - path.join(outputDir, 'TestApp', 'TestApp.webapplication-meta.xml'), - path.join(outputDir, 'TestApp', 'src', 'index.html'), - ]); - assert.fileContent(path.join(outputDir, 'TestApp', 'src', 'index.html'), 'Welcome to Web App'); - }); - }); - - 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}"`, { - ensureExitCode: 0, - }); - assert.file([ - path.join(outputDir, 'MyReactApp', 'MyReactApp.webapplication-meta.xml'), - path.join(outputDir, 'MyReactApp', 'index.html'), - path.join(outputDir, 'MyReactApp', 'webapplication.json'), - path.join(outputDir, 'MyReactApp', 'package.json'), - ]); - assert.fileContent(path.join(outputDir, 'MyReactApp', 'package.json'), '"name": "base-react-app"'); - }); - }); - - describe('Check that all invalid name errors are thrown', () => { - it('should throw a missing name error', () => { - const stderr = execCmd('template generate webapp').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; - 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; - 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; - 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; - expect(stderr).to.contain(nls.localize('DoubleUnderscoreError')); - }); - - it('should auto-append webapplications folder when output dir does not end with webapplications', () => { - 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 }); - assert.file([ - path.join(expectedOutputDir, 'TestApp', 'TestApp.webapplication-meta.xml'), - path.join(expectedOutputDir, 'TestApp', 'src', 'index.html'), - path.join(expectedOutputDir, 'TestApp', 'webapplication.json'), - ]); - }); - }); -}); From 541a7d9eb0e4c0c1465b5148370e73c34df72b92 Mon Sep 17 00:00:00 2001 From: Deepu Mungamuri Date: Fri, 27 Mar 2026 19:07:43 +0530 Subject: [PATCH 02/13] feat: rename react project templates from reactb2e/reactb2x to reactinternalapp/reactexternalapp - Replace deprecated reactb2e and reactb2x template names with reactinternalapp and reactexternalapp in the project generate command options, improving clarity by reflecting the internal vs external audience distinction instead of opaque B2E/B2X codes - Add nativemobile as a new supported template option - Bump @salesforce/templates to 66.6.2 to pick up the uiBundles output directory rename (previously webapplications) and aligned internal template identifiers - Update NUT tests to use the new template names and uiBundles path - Update messages to document the renamed templates correctly Made-with: Cursor --- messages/project.md | 2 +- package.json | 2 +- .../template/generate/project/index.ts | 10 +++++++++- .../template/generate/project/index.nut.ts | 20 +++++++++---------- yarn.lock | 8 ++++---- 5 files changed, 25 insertions(+), 17 deletions(-) diff --git a/messages/project.md b/messages/project.md index 4417e539..a6d28c09 100644 --- a/messages/project.md +++ b/messages/project.md @@ -58,7 +58,7 @@ The standard template provides a complete force-app directory structure so you k The analytics template provides similar files and the force-app/main/default/waveTemplates directory. -The reactb2e and reactb2x templates provide React-based project scaffolding for B2E and B2X web application use cases. +The reactinternalapp and reactexternalapp templates provide React-based project scaffolding for internal and external web application use cases. The agent template provides project scaffolding for building Agentforce agents and includes a sample agent called Local Info Agent. diff --git a/package.json b/package.json index 2f1c4357..0ba72fc6 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "dependencies": { "@salesforce/core": "^8.27.1", "@salesforce/sf-plugins-core": "^12", - "@salesforce/templates": "^66.5.6" + "@salesforce/templates": "^66.6.2" }, "devDependencies": { "@oclif/plugin-command-snapshot": "^5.3.13", diff --git a/src/commands/template/generate/project/index.ts b/src/commands/template/generate/project/index.ts index 38d6420c..2ab31722 100644 --- a/src/commands/template/generate/project/index.ts +++ b/src/commands/template/generate/project/index.ts @@ -33,7 +33,15 @@ export default class Project extends SfCommand { summary: messages.getMessage('flags.template.summary'), description: messages.getMessage('flags.template.description'), default: 'standard', - options: ['standard', 'empty', 'analytics', 'reactb2e', 'reactb2x', 'agent'] as const, + options: [ + 'standard', + 'empty', + 'analytics', + 'reactinternalapp', + 'reactexternalapp', + 'agent', + 'nativemobile', + ] as const, })(), 'output-dir': outputDirFlag, namespace: Flags.string({ diff --git a/test/commands/template/generate/project/index.nut.ts b/test/commands/template/generate/project/index.nut.ts index 4c613d6a..1c4d68f0 100644 --- a/test/commands/template/generate/project/index.nut.ts +++ b/test/commands/template/generate/project/index.nut.ts @@ -243,10 +243,10 @@ describe('template generate project:', () => { assert.file([path.join(session.project.dir, 'analytics1', 'eslint.config.js')]); }); - it('should create project with reactb2e template', () => { - const projectName = 'react-b2e-test'; - const alphanumericName = 'reactb2etest'; - execCmd(`template generate project --projectname ${projectName} --template reactb2e`, { + it('should create project with reactexternalapp template', () => { + const projectName = 'react-externalapp-test'; + const alphanumericName = 'reactexternalapptest'; + execCmd(`template generate project --projectname ${projectName} --template reactexternalapp`, { ensureExitCode: 0, }); const projectDir = path.join(session.project.dir, projectName); @@ -257,7 +257,7 @@ describe('template generate project:', () => { 'force-app', 'main', 'default', - 'webapplications', + 'uiBundles', alphanumericName, `${alphanumericName}.webapplication-meta.xml` ); @@ -265,10 +265,10 @@ describe('template generate project:', () => { assert.fileContent(webappMetaPath, alphanumericName); }); - it('should create project with reactb2x template', () => { - const projectName = 'react-b2x-test'; - const alphanumericName = 'reactb2xtest'; - execCmd(`template generate project --projectname ${projectName} --template reactb2x`, { + it('should create project with reactinternalapp template', () => { + const projectName = 'react-internalapp-test'; + const alphanumericName = 'reactinternalapptest'; + execCmd(`template generate project --projectname ${projectName} --template reactinternalapp`, { ensureExitCode: 0, }); const projectDir = path.join(session.project.dir, projectName); @@ -279,7 +279,7 @@ describe('template generate project:', () => { 'force-app', 'main', 'default', - 'webapplications', + 'uiBundles', alphanumericName, `${alphanumericName}.webapplication-meta.xml` ); diff --git a/yarn.lock b/yarn.lock index a695e619..9b0be05d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1649,10 +1649,10 @@ cli-progress "^3.12.0" terminal-link "^3.0.0" -"@salesforce/templates@^66.5.6": - version "66.5.6" - resolved "https://registry.yarnpkg.com/@salesforce/templates/-/templates-66.5.6.tgz#1fcece13106882ded54fcd96a5d9bf49fd31a546" - integrity sha512-k18ROus8XUskIT6n2eXsHvoZK3NmrVePSPduCfjWEohMEJ/qy9ltdyMos/eebzc3ejooLBu40/22zKjVZFoFfA== +"@salesforce/templates@^66.6.2": + version "66.6.2" + resolved "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@salesforce/templates/-/templates-66.6.2.tgz#efc37965d41ba81791d08ecb733a327f4e9f8536" + integrity sha512-ACrKYF586OtPD9PfDivFL9p66Bb08wbVOlp/FnlphjLFmcVNGxNlmG0bJnZqjHQnckZOvtrVfwBaHxS4iFObtg== dependencies: "@salesforce/kit" "^3.2.4" ejs "^3.1.10" From 4502b4937b9a2512d8025ca29480b491412beabb Mon Sep 17 00:00:00 2001 From: gary-chang Date: Sat, 28 Mar 2026 17:37:37 -0700 Subject: [PATCH 03/13] fix: update command snapshot with webapp:generate alias and update project messages --- command-snapshot.json | 2 +- messages/project.md | 2 +- package.json | 14 +++++++++++++- src/commands/template/generate/ui-bundle/index.ts | 2 +- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/command-snapshot.json b/command-snapshot.json index d627cd56..a91bf327 100644 --- a/command-snapshot.json +++ b/command-snapshot.json @@ -148,7 +148,7 @@ "plugin": "@salesforce/plugin-templates" }, { - "alias": ["ui-bundle:generate"], + "alias": ["ui-bundle:generate", "webapp:generate"], "command": "template:generate:ui-bundle", "flagAliases": [], "flagChars": ["d", "l", "n", "t"], diff --git a/messages/project.md b/messages/project.md index a6d28c09..2373b891 100644 --- a/messages/project.md +++ b/messages/project.md @@ -58,7 +58,7 @@ The standard template provides a complete force-app directory structure so you k The analytics template provides similar files and the force-app/main/default/waveTemplates directory. -The reactinternalapp and reactexternalapp templates provide React-based project scaffolding for internal and external web application use cases. +The reactinternalapp and reactexternalapp templates provide React-based project scaffolding for internal and external UI bundle use cases. The agent template provides project scaffolding for building Agentforce agents and includes a sample agent called Local Info Agent. diff --git a/package.json b/package.json index 0ba72fc6..e37e0bf2 100644 --- a/package.json +++ b/package.json @@ -93,6 +93,14 @@ }, "ui-bundle": { "description": "Work with UI bundles." + }, + "webapp": { + "description": "Work with UI bundles.", + "subtopics": { + "generate": { + "description": "Generate a UI bundle." + } + } } }, "flexibleTaxonomy": true, @@ -250,5 +258,9 @@ } }, "exports": "./lib/index.js", - "type": "module" + "type": "module", + "volta": { + "node": "24.14.1", + "yarn": "1.22.22" + } } diff --git a/src/commands/template/generate/ui-bundle/index.ts b/src/commands/template/generate/ui-bundle/index.ts index 7868f707..26819a23 100644 --- a/src/commands/template/generate/ui-bundle/index.ts +++ b/src/commands/template/generate/ui-bundle/index.ts @@ -21,7 +21,7 @@ export default class UiBundleGenerate extends SfCommand { 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 = ['ui-bundle:generate']; + public static readonly aliases = ['ui-bundle:generate', 'webapp:generate']; public static readonly flags = { name: Flags.string({ char: 'n', From 8e3555402476e46cdef9073c2976955cb818f21c Mon Sep 17 00:00:00 2001 From: Deepu Mungamuri Date: Sun, 29 Mar 2026 14:05:53 +0530 Subject: [PATCH 04/13] fix: update NUT tests to expect uibundle-meta.xml and ui-bundle.json filenames Made-with: Cursor --- .../template/generate/project/index.nut.ts | 4 ++-- .../template/generate/ui-bundle/index.nut.ts | 20 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/test/commands/template/generate/project/index.nut.ts b/test/commands/template/generate/project/index.nut.ts index 1c4d68f0..cd0a0b28 100644 --- a/test/commands/template/generate/project/index.nut.ts +++ b/test/commands/template/generate/project/index.nut.ts @@ -259,7 +259,7 @@ describe('template generate project:', () => { 'default', 'uiBundles', alphanumericName, - `${alphanumericName}.webapplication-meta.xml` + `${alphanumericName}.uibundle-meta.xml` ); assert.file([webappMetaPath]); assert.fileContent(webappMetaPath, alphanumericName); @@ -281,7 +281,7 @@ describe('template generate project:', () => { 'default', 'uiBundles', alphanumericName, - `${alphanumericName}.webapplication-meta.xml` + `${alphanumericName}.uibundle-meta.xml` ); assert.file([webappMetaPath]); assert.fileContent(webappMetaPath, alphanumericName); diff --git a/test/commands/template/generate/ui-bundle/index.nut.ts b/test/commands/template/generate/ui-bundle/index.nut.ts index d31e343b..17275b7f 100644 --- a/test/commands/template/generate/ui-bundle/index.nut.ts +++ b/test/commands/template/generate/ui-bundle/index.nut.ts @@ -30,12 +30,12 @@ describe('template generate ui-bundle:', () => { const outputDir = path.join(projectDir, 'force-app', 'main', 'default', UI_BUNDLES_DIR); execCmd(`template generate ui-bundle --name MyUiBundle --output-dir "${outputDir}"`, { ensureExitCode: 0 }); assert.file([ - path.join(outputDir, 'MyUiBundle', 'MyUiBundle.webapplication-meta.xml'), + path.join(outputDir, 'MyUiBundle', 'MyUiBundle.uibundle-meta.xml'), path.join(outputDir, 'MyUiBundle', 'src', 'index.html'), - path.join(outputDir, 'MyUiBundle', 'webapplication.json'), + path.join(outputDir, 'MyUiBundle', 'ui-bundle.json'), ]); assert.fileContent( - path.join(outputDir, 'MyUiBundle', 'MyUiBundle.webapplication-meta.xml'), + path.join(outputDir, 'MyUiBundle', 'MyUiBundle.uibundle-meta.xml'), 'My Ui Bundle' ); }); @@ -44,9 +44,9 @@ describe('template generate ui-bundle:', () => { const expectedOutputDir = path.join(projectDir, 'force-app', 'main', 'default', UI_BUNDLES_DIR); execCmd('template generate ui-bundle --name DefaultDirApp', { ensureExitCode: 0 }); assert.file([ - path.join(expectedOutputDir, 'DefaultDirApp', 'DefaultDirApp.webapplication-meta.xml'), + path.join(expectedOutputDir, 'DefaultDirApp', 'DefaultDirApp.uibundle-meta.xml'), path.join(expectedOutputDir, 'DefaultDirApp', 'src', 'index.html'), - path.join(expectedOutputDir, 'DefaultDirApp', 'webapplication.json'), + path.join(expectedOutputDir, 'DefaultDirApp', 'ui-bundle.json'), ]); }); @@ -56,7 +56,7 @@ describe('template generate ui-bundle:', () => { ensureExitCode: 0, }); assert.file([ - path.join(outputDir, 'TestApp', 'TestApp.webapplication-meta.xml'), + path.join(outputDir, 'TestApp', 'TestApp.uibundle-meta.xml'), path.join(outputDir, 'TestApp', 'src', 'index.html'), ]); assert.fileContent(path.join(outputDir, 'TestApp', 'src', 'index.html'), 'Welcome to Web App'); @@ -70,9 +70,9 @@ describe('template generate ui-bundle:', () => { ensureExitCode: 0, }); assert.file([ - path.join(outputDir, 'MyReactApp', 'MyReactApp.webapplication-meta.xml'), + path.join(outputDir, 'MyReactApp', 'MyReactApp.uibundle-meta.xml'), path.join(outputDir, 'MyReactApp', 'index.html'), - path.join(outputDir, 'MyReactApp', 'webapplication.json'), + path.join(outputDir, 'MyReactApp', 'ui-bundle.json'), path.join(outputDir, 'MyReactApp', 'package.json'), ]); assert.fileContent(path.join(outputDir, 'MyReactApp', 'package.json'), '"name": "base-react-app"'); @@ -114,9 +114,9 @@ describe('template generate ui-bundle:', () => { const expectedOutputDir = path.join(outputDir, UI_BUNDLES_DIR); 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', 'TestApp.uibundle-meta.xml'), path.join(expectedOutputDir, 'TestApp', 'src', 'index.html'), - path.join(expectedOutputDir, 'TestApp', 'webapplication.json'), + path.join(expectedOutputDir, 'TestApp', 'ui-bundle.json'), ]); }); }); From 9e936913e85c4c4391c0eee5308abc52f3712496 Mon Sep 17 00:00:00 2001 From: Deepu Mungamuri <36835750+deepu-mungamuri94@users.noreply.github.com> Date: Sun, 29 Mar 2026 14:09:55 +0530 Subject: [PATCH 05/13] Update CLAUDE.md Co-authored-by: Brian Buchanan <5377888+bpbuch@users.noreply.github.com> --- CLAUDE.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index 91ceedf5..57db402d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -30,7 +30,6 @@ Command structure: src/commands/template/generate/{metadataType}/ Files: - - index.ts → top-level generator - {subTemplate}.ts → nested generator From b8355c22c4ce2cc527efdd6874ec77f3fc1ead05 Mon Sep 17 00:00:00 2001 From: Deepu Mungamuri <36835750+deepu-mungamuri94@users.noreply.github.com> Date: Sun, 29 Mar 2026 14:10:16 +0530 Subject: [PATCH 06/13] Update CLAUDE.md Co-authored-by: Brian Buchanan <5377888+bpbuch@users.noreply.github.com> --- CLAUDE.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index 57db402d..801bc55f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -38,7 +38,6 @@ Naming pattern: sf template generate {metadataType} {optionalSubTemplate} Examples: - - sf template generate flexipage - sf template generate ui-bundle - sf template generate digital-experience site From a9681fb1749b459c2180933167b799c29ef8a6d3 Mon Sep 17 00:00:00 2001 From: Deepu Mungamuri <36835750+deepu-mungamuri94@users.noreply.github.com> Date: Sun, 29 Mar 2026 14:10:30 +0530 Subject: [PATCH 07/13] Update CLAUDE.md Co-authored-by: Brian Buchanan <5377888+bpbuch@users.noreply.github.com> --- CLAUDE.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 801bc55f..82450d61 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -93,9 +93,9 @@ Only GA commands require permanent backwards compatibility. All generators should call: runGenerator({ -templateType: TemplateType.X, -opts: flags, -ux + templateType: TemplateType.X, + opts: flags, + ux }) --- From ee7b71f3a220d6782105a38bd05bc7a9bb8fe0ad Mon Sep 17 00:00:00 2001 From: Deepu Mungamuri Date: Sun, 29 Mar 2026 14:13:36 +0530 Subject: [PATCH 08/13] revert: restore SKILL.md to match origin/main, no rename changes needed Made-with: Cursor --- .claude/skills/add-template-generator/SKILL.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.claude/skills/add-template-generator/SKILL.md b/.claude/skills/add-template-generator/SKILL.md index 5623554b..0ce77cca 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`, `ui-bundle`, `apex`) +- Replace `{metadataType}` with your metadata type (e.g., `flexipage`, `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,6 @@ Test your command: ```bash sf template generate {metadataType} --name TestExample --output-dir ./test-output -# e.g. sf template generate ui-bundle --name MyApp --output-dir ./test-output ``` Verify the generated files are correct. From 689950c0a8bdf18e60e797f79886e5c131c81fdb Mon Sep 17 00:00:00 2001 From: Deepu Mungamuri Date: Sun, 29 Mar 2026 14:23:00 +0530 Subject: [PATCH 09/13] refactor: use UIBundleOptions/TemplateType.UIBundle/bundlename, remove webapp alias Made-with: Cursor --- command-snapshot.json | 2 +- package.json | 8 -------- src/commands/template/generate/ui-bundle/index.ts | 10 +++++----- 3 files changed, 6 insertions(+), 14 deletions(-) diff --git a/command-snapshot.json b/command-snapshot.json index a91bf327..d627cd56 100644 --- a/command-snapshot.json +++ b/command-snapshot.json @@ -148,7 +148,7 @@ "plugin": "@salesforce/plugin-templates" }, { - "alias": ["ui-bundle:generate", "webapp:generate"], + "alias": ["ui-bundle:generate"], "command": "template:generate:ui-bundle", "flagAliases": [], "flagChars": ["d", "l", "n", "t"], diff --git a/package.json b/package.json index 1bd2cd5d..7bec6ac9 100644 --- a/package.json +++ b/package.json @@ -93,14 +93,6 @@ }, "ui-bundle": { "description": "Work with UI bundles." - }, - "webapp": { - "description": "Work with UI bundles.", - "subtopics": { - "generate": { - "description": "Generate a UI bundle." - } - } } }, "flexibleTaxonomy": true, diff --git a/src/commands/template/generate/ui-bundle/index.ts b/src/commands/template/generate/ui-bundle/index.ts index 26819a23..ca7b4e1a 100644 --- a/src/commands/template/generate/ui-bundle/index.ts +++ b/src/commands/template/generate/ui-bundle/index.ts @@ -7,7 +7,7 @@ import path from 'node:path'; import { Flags, SfCommand, Ux } from '@salesforce/sf-plugins-core'; -import { CreateOutput, WebApplicationOptions, TemplateType } from '@salesforce/templates'; +import { CreateOutput, UIBundleOptions, TemplateType } from '@salesforce/templates'; import { Messages, SfProject } from '@salesforce/core'; import { getCustomTemplates, runGenerator } from '../../../../utils/templateCommand.js'; @@ -21,7 +21,7 @@ export default class UiBundleGenerate extends SfCommand { 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 = ['ui-bundle:generate', 'webapp:generate']; + public static readonly aliases = ['ui-bundle:generate']; public static readonly flags = { name: Flags.string({ char: 'n', @@ -69,8 +69,8 @@ export default class UiBundleGenerate extends SfCommand { const outputDir = flags['output-dir'] ?? (await UiBundleGenerate.getDefaultOutputDir()); - const flagsAsOptions: WebApplicationOptions = { - webappname: flags.name, + const flagsAsOptions: UIBundleOptions = { + bundlename: flags.name, template: flags.template, masterlabel: flags.label, outputdir: outputDir, @@ -78,7 +78,7 @@ export default class UiBundleGenerate extends SfCommand { }; return runGenerator({ - templateType: TemplateType.WebApplication, + templateType: TemplateType.UIBundle, opts: flagsAsOptions, ux: new Ux({ jsonEnabled: this.jsonEnabled() }), templates: getCustomTemplates(this.configAggregator), From f5bc493a69b9b2f65bd497be2d88dc5cb8f38e39 Mon Sep 17 00:00:00 2001 From: Deepu Mungamuri Date: Sun, 29 Mar 2026 14:30:58 +0530 Subject: [PATCH 10/13] refactor: rename webappMetaPath to uiBundleMetaPath in NUT tests Made-with: Cursor --- test/commands/template/generate/project/index.nut.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/commands/template/generate/project/index.nut.ts b/test/commands/template/generate/project/index.nut.ts index cd0a0b28..e1e338cb 100644 --- a/test/commands/template/generate/project/index.nut.ts +++ b/test/commands/template/generate/project/index.nut.ts @@ -252,7 +252,7 @@ describe('template generate project:', () => { const projectDir = path.join(session.project.dir, projectName); assert.file([path.join(projectDir, 'sfdx-project.json')]); assert.fileContent(path.join(projectDir, 'sfdx-project.json'), 'sourceApiVersion'); - const webappMetaPath = path.join( + const uiBundleMetaPath = path.join( projectDir, 'force-app', 'main', @@ -261,8 +261,8 @@ describe('template generate project:', () => { alphanumericName, `${alphanumericName}.uibundle-meta.xml` ); - assert.file([webappMetaPath]); - assert.fileContent(webappMetaPath, alphanumericName); + assert.file([uiBundleMetaPath]); + assert.fileContent(uiBundleMetaPath, alphanumericName); }); it('should create project with reactinternalapp template', () => { @@ -274,7 +274,7 @@ describe('template generate project:', () => { const projectDir = path.join(session.project.dir, projectName); assert.file([path.join(projectDir, 'sfdx-project.json')]); assert.fileContent(path.join(projectDir, 'sfdx-project.json'), 'sourceApiVersion'); - const webappMetaPath = path.join( + const uiBundleMetaPath = path.join( projectDir, 'force-app', 'main', @@ -283,8 +283,8 @@ describe('template generate project:', () => { alphanumericName, `${alphanumericName}.uibundle-meta.xml` ); - assert.file([webappMetaPath]); - assert.fileContent(webappMetaPath, alphanumericName); + assert.file([uiBundleMetaPath]); + assert.fileContent(uiBundleMetaPath, alphanumericName); }); it('should create project with agent template', () => { From 265178e6e177d93ad172f6e1ba9d368508cff329 Mon Sep 17 00:00:00 2001 From: Deepu Mungamuri Date: Mon, 30 Mar 2026 10:48:01 +0530 Subject: [PATCH 11/13] chore: remove volta config and nativemobile template option Removes volta configuration from package.json to align with main branch. Removes nativemobile from project template options as it's no longer supported. Co-Authored-By: Claude Sonnet 4.5 --- package.json | 6 +----- src/commands/template/generate/project/index.ts | 10 +--------- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index 7bec6ac9..ba32f85f 100644 --- a/package.json +++ b/package.json @@ -250,9 +250,5 @@ } }, "exports": "./lib/index.js", - "type": "module", - "volta": { - "node": "24.14.1", - "yarn": "1.22.22" - } + "type": "module" } diff --git a/src/commands/template/generate/project/index.ts b/src/commands/template/generate/project/index.ts index 2ab31722..c44fb6aa 100644 --- a/src/commands/template/generate/project/index.ts +++ b/src/commands/template/generate/project/index.ts @@ -33,15 +33,7 @@ export default class Project extends SfCommand { summary: messages.getMessage('flags.template.summary'), description: messages.getMessage('flags.template.description'), default: 'standard', - options: [ - 'standard', - 'empty', - 'analytics', - 'reactinternalapp', - 'reactexternalapp', - 'agent', - 'nativemobile', - ] as const, + options: ['standard', 'empty', 'analytics', 'reactinternalapp', 'reactexternalapp', 'agent'] as const, })(), 'output-dir': outputDirFlag, namespace: Flags.string({ From adb6f5219c56f1d3c0d37aafe8f80bf511121784 Mon Sep 17 00:00:00 2001 From: Deepu Mungamuri Date: Mon, 30 Mar 2026 11:19:48 +0530 Subject: [PATCH 12/13] chore: bump @salesforce/templates to ^66.7.1 Updates @salesforce/templates dependency to incorporate latest fixes and improvements. Also removes ui-bundle example from CLAUDE.md as it's now covered by the main template generation docs. Co-Authored-By: Claude Sonnet 4.5 --- CLAUDE.md | 9 +++++---- package.json | 2 +- yarn.lock | 8 ++++---- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 82450d61..ce538e66 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,8 +39,8 @@ Naming pattern: sf template generate {metadataType} {optionalSubTemplate} Examples: + - sf template generate flexipage -- sf template generate ui-bundle - sf template generate digital-experience site --- @@ -93,9 +94,9 @@ Only GA commands require permanent backwards compatibility. All generators should call: runGenerator({ - templateType: TemplateType.X, - opts: flags, - ux +templateType: TemplateType.X, +opts: flags, +ux }) --- diff --git a/package.json b/package.json index ba32f85f..38a99cd9 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "dependencies": { "@salesforce/core": "^8.27.1", "@salesforce/sf-plugins-core": "^12", - "@salesforce/templates": "^66.6.2" + "@salesforce/templates": "^66.7.1" }, "devDependencies": { "@oclif/plugin-command-snapshot": "^5.3.13", diff --git a/yarn.lock b/yarn.lock index edbce5bb..1f618c1a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1649,10 +1649,10 @@ cli-progress "^3.12.0" terminal-link "^3.0.0" -"@salesforce/templates@^66.6.2": - version "66.6.2" - resolved "https://registry.yarnpkg.com/@salesforce/templates/-/templates-66.6.2.tgz#efc37965d41ba81791d08ecb733a327f4e9f8536" - integrity sha512-ACrKYF586OtPD9PfDivFL9p66Bb08wbVOlp/FnlphjLFmcVNGxNlmG0bJnZqjHQnckZOvtrVfwBaHxS4iFObtg== +"@salesforce/templates@^66.7.1": + version "66.7.1" + resolved "https://registry.yarnpkg.com/@salesforce/templates/-/templates-66.7.1.tgz#042a8dd786e544aa5772ca361fe010d23b5bf12f" + integrity sha512-MCHKy2Fjll528Yoxg7WiSHx3yWKanxFFtpW3IYQQhBpjyh4lkbwT5/iOMfihkqdD9HjzTQxDvAW+FRjCBq2HlQ== dependencies: "@salesforce/kit" "^3.2.4" ejs "^3.1.10" From 4724392efebc3493e2e63e22475c19c38ca6d791 Mon Sep 17 00:00:00 2001 From: Deepu Mungamuri Date: Mon, 30 Mar 2026 11:32:12 +0530 Subject: [PATCH 13/13] revert: restore CLAUDE.md and SKILL.md to match upstream main Reverts formatting changes to prevent them from appearing in PR against upstream main branch. Co-Authored-By: Claude Sonnet 4.5 --- .claude/skills/add-template-generator/SKILL.md | 13 +++---------- CLAUDE.md | 10 ++++------ 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/.claude/skills/add-template-generator/SKILL.md b/.claude/skills/add-template-generator/SKILL.md index 0ce77cca..8e462544 100644 --- a/.claude/skills/add-template-generator/SKILL.md +++ b/.claude/skills/add-template-generator/SKILL.md @@ -2,7 +2,6 @@ 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 @@ -25,7 +24,6 @@ sf dev generate command -n template:generate:{metadataType}:{optionalSubTemplate ``` **Notes:** - - Replace `{metadataType}` with your metadata type (e.g., `flexipage`, `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 @@ -63,7 +61,6 @@ 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) @@ -103,13 +100,11 @@ 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) @@ -118,7 +113,6 @@ 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 @@ -135,9 +129,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, @@ -149,7 +143,6 @@ 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 @@ -219,4 +212,4 @@ Before opening PR ensure: - flags validated - messages documented - NUTs pass -- topics updated +- topics updated \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index ce538e66..6da899cb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -30,7 +30,6 @@ Command structure: src/commands/template/generate/{metadataType}/ Files: - - index.ts → top-level generator - {subTemplate}.ts → nested generator @@ -39,7 +38,6 @@ Naming pattern: sf template generate {metadataType} {optionalSubTemplate} Examples: - - sf template generate flexipage - sf template generate digital-experience site @@ -94,13 +92,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. +Use official Salesforce CLI docs when needed. \ No newline at end of file