Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions .claude/skills/add-template-generator/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -129,9 +135,9 @@ import { runGenerator } from '../../utils/templateCommand.js';

public async run(): Promise<CreateOutput> {
const { flags } = await this.parse(CommandClass);

// Add any pre-processing or validation here

return runGenerator({
templateType: TemplateType.{YourMetadataType},
opts: flags,
Expand All @@ -143,6 +149,7 @@ public async run(): Promise<CreateOutput> {
## 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
Expand All @@ -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.
Expand Down Expand Up @@ -212,4 +220,4 @@ Before opening PR ensure:
- flags validated
- messages documented
- NUTs pass
- topics updated
- topics updated
11 changes: 7 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Command structure:
src/commands/template/generate/{metadataType}/

Files:

- index.ts → top-level generator
- {subTemplate}.ts → nested generator

Expand All @@ -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

---
Expand Down Expand Up @@ -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.
Use official Salesforce CLI docs when needed.
4 changes: 2 additions & 2 deletions command-snapshot.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
Expand Down
62 changes: 62 additions & 0 deletions messages/ui-bundle.generate.md
Original file line number Diff line number Diff line change
@@ -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 "<output-dir>/<name>".

**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.
61 changes: 0 additions & 61 deletions messages/webApplication.md

This file was deleted.

7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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', 'ui-bundle.generate');

export default class WebAppGenerate extends SfCommand<CreateOutput> {
export default class UiBundleGenerate extends SfCommand<CreateOutput> {
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',
Expand Down Expand Up @@ -50,23 +49,23 @@ export default class WebAppGenerate extends SfCommand<CreateOutput> {

/**
* 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<string> {
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', 'uiBundles');
} catch {
return '.';
}
}

public async run(): Promise<CreateOutput> {
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,
Expand Down
Loading
Loading