diff --git a/.claude/skills/add-template-generator/SKILL.md b/.claude/skills/add-template-generator/SKILL.md index 8e462544..0ce77cca 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,6 +25,7 @@ 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 @@ -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 @@ -212,4 +219,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..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,6 +39,7 @@ Naming pattern: sf template generate {metadataType} {optionalSubTemplate} Examples: + - sf template generate flexipage - sf template generate digital-experience site @@ -92,13 +94,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/project.md b/messages/project.md index 4417e539..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 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 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/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 ea5f5dad..a43c6dad 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.7.1" }, "devDependencies": { "@oclif/plugin-command-snapshot": "^5.3.13", @@ -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, @@ -247,5 +250,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/project/index.ts b/src/commands/template/generate/project/index.ts index 38d6420c..c44fb6aa 100644 --- a/src/commands/template/generate/project/index.ts +++ b/src/commands/template/generate/project/index.ts @@ -33,7 +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', 'reactb2e', 'reactb2x', 'agent'] as const, + options: ['standard', 'empty', 'analytics', 'reactinternalapp', 'reactexternalapp', 'agent'] as const, })(), 'output-dir': outputDirFlag, namespace: Flags.string({ diff --git a/src/commands/template/generate/webapp/index.ts b/src/commands/template/generate/ui-bundle/index.ts similarity index 77% rename from src/commands/template/generate/webapp/index.ts rename to src/commands/template/generate/ui-bundle/index.ts index e91d2b57..ca7b4e1a 100644 --- a/src/commands/template/generate/webapp/index.ts +++ b/src/commands/template/generate/ui-bundle/index.ts @@ -7,20 +7,21 @@ 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'; 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,26 +51,26 @@ 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, + const flagsAsOptions: UIBundleOptions = { + bundlename: flags.name, template: flags.template, masterlabel: flags.label, outputdir: outputDir, @@ -77,7 +78,7 @@ export default class WebAppGenerate extends SfCommand { }; return runGenerator({ - templateType: TemplateType.WebApplication, + templateType: TemplateType.UIBundle, opts: flagsAsOptions, ux: new Ux({ jsonEnabled: this.jsonEnabled() }), templates: getCustomTemplates(this.configAggregator), diff --git a/test/commands/template/generate/project/index.nut.ts b/test/commands/template/generate/project/index.nut.ts index 4c613d6a..cd0a0b28 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,18 +257,18 @@ describe('template generate project:', () => { 'force-app', 'main', 'default', - 'webapplications', + 'uiBundles', alphanumericName, - `${alphanumericName}.webapplication-meta.xml` + `${alphanumericName}.uibundle-meta.xml` ); assert.file([webappMetaPath]); 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,9 +279,9 @@ describe('template generate project:', () => { 'force-app', 'main', 'default', - 'webapplications', + '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 new file mode 100644 index 00000000..17275b7f --- /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.uibundle-meta.xml'), + path.join(outputDir, 'MyUiBundle', 'src', 'index.html'), + path.join(outputDir, 'MyUiBundle', 'ui-bundle.json'), + ]); + assert.fileContent( + path.join(outputDir, 'MyUiBundle', 'MyUiBundle.uibundle-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.uibundle-meta.xml'), + path.join(expectedOutputDir, 'DefaultDirApp', 'src', 'index.html'), + path.join(expectedOutputDir, 'DefaultDirApp', 'ui-bundle.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.uibundle-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.uibundle-meta.xml'), + path.join(outputDir, 'MyReactApp', 'index.html'), + 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"'); + }); + }); + + 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.uibundle-meta.xml'), + path.join(expectedOutputDir, 'TestApp', 'src', 'index.html'), + path.join(expectedOutputDir, 'TestApp', 'ui-bundle.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'), - ]); - }); - }); -}); diff --git a/yarn.lock b/yarn.lock index a695e619..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.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.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"