From 24b1a1021532f5473d530ae81ee0ed5c2a03381d Mon Sep 17 00:00:00 2001 From: Nicolas Kruk Date: Wed, 16 Jul 2025 13:15:44 -0400 Subject: [PATCH 1/6] chore: update task label and arguments in tasks.json for CLI plugin build --- .vscode/tasks.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 31e4d998..578c4627 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -3,7 +3,7 @@ "problemMatcher": "$tsc", "tasks": [ { - "label": "Compile tests", + "label": "Build CLI Plugin", "group": { "kind": "build", "isDefault": true @@ -14,7 +14,7 @@ "focus": false, "panel": "dedicated" }, - "args": ["test:compile"], + "args": ["build"], "isBackground": false } ] From 33fff0826d562d3bcc2de59375e76b235b6e1ee1 Mon Sep 17 00:00:00 2001 From: Nicolas Kruk Date: Wed, 16 Jul 2025 13:41:50 -0400 Subject: [PATCH 2/6] feat: add comprehensive cursor rules documentation for Salesforce CLI plugin --- .cursorrules | 218 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 218 insertions(+) create mode 100644 .cursorrules diff --git a/.cursorrules b/.cursorrules new file mode 100644 index 00000000..0b601a7a --- /dev/null +++ b/.cursorrules @@ -0,0 +1,218 @@ +# Salesforce CLI Plugin Lightning Dev - Cursor Rules + +## Project Overview + +This is a Salesforce CLI plugin for Lightning development tools (LEX, Mobile, and Experience Sites). It's built with TypeScript, uses yarn for package management, and leverages wireit for intelligent build caching and task orchestration. + +## Key Technologies + +- **TypeScript**: Primary language for the plugin +- **Wireit**: Build task orchestration with intelligent caching +- **Yarn**: Package manager (v1.22.22 as specified in volta config) +- **Node.js**: Runtime (v20.11.0 as specified in volta config) +- **Salesforce CLI (sf)**: The platform this plugin extends +- **Lightning Web Components (LWC)**: Core technology for component development +- **Jest/Mocha**: Testing frameworks + +## Understanding Wireit Caching + +### What is Wireit? + +Wireit is a build orchestration tool that provides: + +- **Incremental builds**: Only rebuilds what has changed +- **Intelligent caching**: Stores build outputs in `.wireit/` directory +- **Task dependencies**: Automatically runs dependent tasks in correct order +- **Parallel execution**: Runs independent tasks concurrently + +### Wireit Cache Location + +- Cache is stored in `.wireit/` directory at the project root +- Contains fingerprints and outputs from previous builds +- Can become corrupted or inconsistent, leading to build errors + +### Common Wireit Tasks in This Project + +- `build`: Main build task (depends on compile + lint) +- `compile`: TypeScript compilation with incremental builds +- `lint`: ESLint with caching +- `test`: Comprehensive testing including unit tests, command reference, deprecation policy +- `format`: Prettier formatting + +## Troubleshooting Build Issues + +### When to Run `clean-all` + +Run `yarn clean-all` when experiencing: + +- **Mysterious TypeScript errors** that don't match the actual code +- **Outdated build outputs** not reflecting recent changes +- **Wireit dependency errors** or inconsistent cache states +- **"Cannot find module" errors** for modules that clearly exist +- **Incremental builds failing** but full rebuilds work +- **Test failures** that don't reproduce when running tests individually +- **Linting errors** on unchanged files + +### The `clean-all` Process + +```bash +# This command cleans ALL build artifacts and caches +yarn clean-all +``` + +What `clean-all` does: + +- Runs `sf-clean all` which removes: + - `lib/` directory (compiled output) + - `.wireit/` directory (all cached tasks and fingerprints) + - `*.tsbuildinfo` files (TypeScript incremental compilation cache) + - `.eslintcache` file (ESLint cache) + - Other temporary build artifacts + +### Post-Cleanup Setup + +**CRITICAL**: After running `clean-all`, you must re-setup the environment: + +```bash +# Always run these commands after clean-all +yarn install && yarn build +``` + +Why both commands are needed: + +1. `yarn install`: Ensures all dependencies are properly installed and linked +2. `yarn build`: Rebuilds all artifacts and re-populates wireit cache + +### Alternative Cleaning Options + +- `yarn clean`: Light cleanup (preserves some caches) +- Individual task clearing: Delete specific folders in `.wireit/` if you know which task is problematic + +## Development Workflow Best Practices + +### Starting Development + +```bash +# Fresh start (if needed) +yarn clean-all +yarn install && yarn build + +# Normal development +yarn build # Builds incrementally using wireit cache +``` + +### Running Tests + +```bash +yarn test # Full test suite with all checks +yarn test:only # Just unit tests +yarn test:nuts # Integration tests (slower) +``` + +### Local Development + +```bash +# Use local dev command instead of global sf +./bin/dev lightning dev component +./bin/dev lightning dev site +./bin/dev lightning dev app +``` + +### Linking for Testing + +```bash +# Link plugin to global sf CLI for testing +sf plugins link . +sf plugins # Verify plugin is linked +``` + +## Project-Specific Context + +### Lightning Development Commands + +This plugin provides three main commands: + +- `sf lightning dev component`: Preview LWC components locally +- `sf lightning dev site`: Preview Experience Cloud sites locally +- `sf lightning dev app`: Preview Lightning apps locally + +### LWR (Lightning Web Runtime) Integration + +- Uses LWR for server-side rendering and local development +- Has special yarn scripts for linking/unlinking LWR dependencies +- `__lwr_cache__/` directory stores LWR-specific cache + +### API Version Management + +- Supports multiple Salesforce API versions (62.0-65.0) +- Has version mappings in `apiVersionMetadata` in package.json +- Different tags target different org versions + +## File Structure Understanding + +### Key Directories + +- `src/`: Source TypeScript files +- `lib/`: Compiled JavaScript output (generated) +- `test/`: All test files (.test.ts, .nut.ts) +- `messages/`: CLI command help text and internationalization +- `.wireit/`: Build cache (can be safely deleted) + +### Important Files + +- `package.json`: Contains all wireit task definitions +- `tsconfig.json`: TypeScript configuration +- `lwc.config.json`: Lightning Web Components configuration +- `.eslintcache`: ESLint cache file + +## Coding Guidelines + +### TypeScript Standards + +- Use strict TypeScript compilation settings +- Follow Salesforce coding standards +- Prefer explicit types over `any` +- Use proper JSDoc comments for CLI commands + +### Testing Requirements + +- Unit tests for all new functionality +- Integration tests (nuts) for CLI commands +- Minimum 95% code coverage requirement +- Mock external dependencies appropriately + +### CLI Command Development + +- Follow oclif patterns for command structure +- Use proper message files for user-facing text +- Include comprehensive help text and examples +- Handle errors gracefully with user-friendly messages + +## Common Pitfalls to Avoid + +1. **Don't commit build artifacts**: `lib/` directory should never be committed +2. **Don't ignore wireit cache issues**: If builds seem wrong, clean and rebuild +3. **Don't skip the yarn install step**: After `clean-all`, always reinstall dependencies +4. **Don't modify generated files**: Focus changes on `src/` directory +5. **Don't use global sf CLI for development**: Use `./bin/dev` for local testing + +## Emergency Troubleshooting + +If everything seems broken: + +```bash +# Nuclear option - start completely fresh +rm -rf node_modules .wireit lib *.tsbuildinfo .eslintcache +yarn install +yarn build +``` + +If wireit tasks seem stuck or corrupted: + +```bash +# Just clear wireit cache +rm -rf .wireit +yarn build +``` + +Remember: **When in doubt, `yarn clean-all && yarn install && yarn build`** - this solves 90% of build-related issues in this project. From 5111e3b4b2ba5e127ba11f7f68e9fa3cce4336c0 Mon Sep 17 00:00:00 2001 From: Nicolas Kruk Date: Wed, 16 Jul 2025 13:43:23 -0400 Subject: [PATCH 3/6] feat: enhance component preview functionality by generating and logging the full preview URL --- src/commands/lightning/dev/component.ts | 20 ++++++++++++++++-- src/shared/previewUtils.ts | 28 +++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/commands/lightning/dev/component.ts b/src/commands/lightning/dev/component.ts index 97a1fab4..8161e102 100644 --- a/src/commands/lightning/dev/component.ts +++ b/src/commands/lightning/dev/component.ts @@ -129,7 +129,23 @@ export default class LightningDevComponent extends SfCommand { targetOrgArg ); - // Open the browser and navigate to the right page - await this.config.runCommand('org:open', launchArguments); + // Construct and log the full URL that will be opened + const connection = targetOrg.getConnection(); + + const decodedFullUrl = PreviewUtils.generateComponentPreviewUrl( + connection.instanceUrl, + ldpServerUrl, + ldpServerId, + componentName, + false + ); + + // Open the browser and navigate to the right page (unless OPEN_BROWSER is set to true) + if (process.env.OPEN_BROWSER !== 'false') { + await this.config.runCommand('org:open', launchArguments); + } else { + // Otherwise, log the URL to the console + this.log(`PreviewURL: ${decodedFullUrl}`); + } } } diff --git a/src/shared/previewUtils.ts b/src/shared/previewUtils.ts index 64ff30a1..ff28e262 100644 --- a/src/shared/previewUtils.ts +++ b/src/shared/previewUtils.ts @@ -246,6 +246,34 @@ export class PreviewUtils { return launchArguments; } + /** + * Generates the full URL for a component preview. + * + * @param instanceUrl The URL of the Salesforce instance + * @param ldpServerUrl The URL for the local dev server + * @param ldpServerId Record ID for the identity token + * @param componentName The name of the component to preview + * @param encodePath Whether to encode the path + * @returns The full URL for the component preview + */ + public static generateComponentPreviewUrl( + instanceUrl: string, + ldpServerUrl: string, + ldpServerId: string, + componentName?: string, + encodePath = false + ): string { + let url = `${instanceUrl}/lwr/application/e/devpreview/ai/${ + encodePath ? encodeURIComponent('localdev%2Fpreview') : 'localdev%2Fpreview' + }?ldpServerUrl=${ldpServerUrl}&ldpServerId=${ldpServerId}`; + if (componentName) { + // TODO: support other namespaces + url += `&specifier=c/${componentName}`; + } + + return url; + } + /** * Generates the proper set of arguments to be used for launching a mobile app with custom launch arguments. * From b2a05fd300a7357c6262e033afab686f15483dd1 Mon Sep 17 00:00:00 2001 From: Nicolas Kruk Date: Thu, 17 Jul 2025 15:12:21 -0400 Subject: [PATCH 4/6] feat: enhance LightningDevComponent to return json --- src/commands/lightning/dev/component.ts | 102 ++++++++++++++---------- 1 file changed, 61 insertions(+), 41 deletions(-) diff --git a/src/commands/lightning/dev/component.ts b/src/commands/lightning/dev/component.ts index 8161e102..04315289 100644 --- a/src/commands/lightning/dev/component.ts +++ b/src/commands/lightning/dev/component.ts @@ -18,7 +18,15 @@ Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); const messages = Messages.loadMessages('@salesforce/plugin-lightning-dev', 'lightning.dev.component'); const sharedMessages = Messages.loadMessages('@salesforce/plugin-lightning-dev', 'shared.utils'); -export default class LightningDevComponent extends SfCommand { +export type ComponentPreviewResult = { + instanceUrl: string; + ldpServerUrl: string; + ldpServerId: string; + componentName: string; + previewUrl: string; +}; + +export default class LightningDevComponent extends SfCommand { public static readonly summary = messages.getMessage('summary'); public static readonly description = messages.getMessage('description'); public static readonly examples = messages.getMessages('examples'); @@ -29,6 +37,7 @@ export default class LightningDevComponent extends SfCommand { char: 'n', requiredOrDefaulted: false, }), + 'api-version': Flags.orgApiVersion(), 'client-select': Flags.boolean({ summary: messages.getMessage('flags.client-select.summary'), char: 'c', @@ -37,7 +46,7 @@ export default class LightningDevComponent extends SfCommand { 'target-org': Flags.requiredOrg(), }; - public async run(): Promise { + public async run(): Promise { const { flags } = await this.parse(LightningDevComponent); const logger = await Logger.child(this.ctor.name); const project = await SfProject.resolve(); @@ -54,6 +63,7 @@ export default class LightningDevComponent extends SfCommand { let componentName = flags['name']; const clientSelect = flags['client-select']; const targetOrg = flags['target-org']; + const apiVersion = flags['api-version']; const { ldpServerId, ldpServerToken } = await PreviewUtils.initializePreviewConnection(targetOrg); @@ -65,41 +75,41 @@ export default class LightningDevComponent extends SfCommand { const ldpServerUrl = PreviewUtils.generateWebSocketUrlForLocalDevServer(Platform.desktop, serverPorts, logger); logger.debug(`Local Dev Server url is ${ldpServerUrl}`); - const namespacePaths = await ComponentUtils.getNamespacePaths(project); - const componentPaths = await ComponentUtils.getAllComponentPaths(namespacePaths); - if (!componentPaths) { - throw new Error(messages.getMessage('error.directory')); - } + if (!clientSelect) { + const namespacePaths = await ComponentUtils.getNamespacePaths(project); + const componentPaths = await ComponentUtils.getAllComponentPaths(namespacePaths); + if (!componentPaths) { + throw new Error(messages.getMessage('error.directory')); + } - const components = ( - await Promise.all( - componentPaths.map(async (componentPath) => { - let xml; - - try { - xml = await ComponentUtils.getComponentMetadata(componentPath); - } catch (err) { - this.warn(messages.getMessage('error.component-metadata', [componentPath])); - } - - // components must have meta xml to be previewed - if (!xml) { - return undefined; - } - - const name = path.basename(componentPath); - const label = ComponentUtils.componentNameToTitleCase(name); - - return { - name, - label: xml.LightningComponentBundle.masterLabel ?? label, - description: xml.LightningComponentBundle.description ?? '', - }; - }) - ) - ).filter((component) => !!component); + const components = ( + await Promise.all( + componentPaths.map(async (componentPath) => { + let xml; + + try { + xml = await ComponentUtils.getComponentMetadata(componentPath); + } catch (err) { + this.warn(messages.getMessage('error.component-metadata', [componentPath])); + } + + // components must have meta xml to be previewed + if (!xml) { + return undefined; + } + + const name = path.basename(componentPath); + const label = ComponentUtils.componentNameToTitleCase(name); + + return { + name, + label: xml.LightningComponentBundle.masterLabel ?? label, + description: xml.LightningComponentBundle.description ?? '', + }; + }) + ) + ).filter((component) => !!component); - if (!clientSelect) { if (componentName) { // validate that the component exists before launching the server const match = components.find( @@ -119,7 +129,9 @@ export default class LightningDevComponent extends SfCommand { } } - await startLWCServer(logger, sfdxProjectRootPath, ldpServerToken, Platform.desktop, serverPorts); + if (process.env.LAUNCH_SERVER !== 'false') { + await startLWCServer(logger, sfdxProjectRootPath, ldpServerToken, Platform.desktop, serverPorts); + } const targetOrgArg = PreviewUtils.getTargetOrgFromArguments(this.argv); const launchArguments = PreviewUtils.generateComponentPreviewLaunchArguments( @@ -130,9 +142,9 @@ export default class LightningDevComponent extends SfCommand { ); // Construct and log the full URL that will be opened - const connection = targetOrg.getConnection(); + const connection = targetOrg.getConnection(apiVersion); - const decodedFullUrl = PreviewUtils.generateComponentPreviewUrl( + const previewUrl = PreviewUtils.generateComponentPreviewUrl( connection.instanceUrl, ldpServerUrl, ldpServerId, @@ -140,12 +152,20 @@ export default class LightningDevComponent extends SfCommand { false ); + // Prepare the result for JSON output + const result: ComponentPreviewResult = { + instanceUrl: connection.instanceUrl, + ldpServerUrl, + ldpServerId, + componentName: componentName ?? '', + previewUrl, + }; + // Open the browser and navigate to the right page (unless OPEN_BROWSER is set to true) if (process.env.OPEN_BROWSER !== 'false') { await this.config.runCommand('org:open', launchArguments); - } else { - // Otherwise, log the URL to the console - this.log(`PreviewURL: ${decodedFullUrl}`); } + + return result; } } From 03e044b63f93fca7a56a8fa1c81eee15c52d5612 Mon Sep 17 00:00:00 2001 From: Nicolas Kruk Date: Thu, 17 Jul 2025 17:40:37 -0400 Subject: [PATCH 5/6] feat: update command-snapshot.json to include new flags for Lightning Dev commands --- command-snapshot.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/command-snapshot.json b/command-snapshot.json index aa16dcf4..26cc62d9 100644 --- a/command-snapshot.json +++ b/command-snapshot.json @@ -12,7 +12,7 @@ "command": "lightning:dev:component", "flagAliases": [], "flagChars": ["c", "n", "o"], - "flags": ["client-select", "flags-dir", "json", "name", "target-org"], + "flags": ["api-version", "client-select", "flags-dir", "json", "name", "target-org"], "plugin": "@salesforce/plugin-lightning-dev" }, { @@ -20,7 +20,7 @@ "command": "lightning:dev:site", "flagAliases": [], "flagChars": ["l", "n", "o"], - "flags": ["flags-dir", "get-latest", "guest", "name", "target-org", "ssr"], + "flags": ["flags-dir", "get-latest", "guest", "name", "ssr", "target-org"], "plugin": "@salesforce/plugin-lightning-dev" } ] From 6a5cd6a2ac2722f77016f5e508395437607ae723 Mon Sep 17 00:00:00 2001 From: svc-cli-bot Date: Fri, 18 Jul 2025 20:02:15 +0000 Subject: [PATCH 6/6] chore(release): 4.4.1-alpha.0 [skip ci] --- README.md | 17 +++++++++-------- package.json | 2 +- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 25ca5a2a..e7214f6e 100644 --- a/README.md +++ b/README.md @@ -201,7 +201,7 @@ EXAMPLES $ sf lightning dev app --target-org myOrg --device-type ios --device-id "iPhone 15 Pro Max" ``` -_See code: [src/commands/lightning/dev/app.ts](https://github.com/salesforcecli/plugin-lightning-dev/blob/4.4.0/src/commands/lightning/dev/app.ts)_ +_See code: [src/commands/lightning/dev/app.ts](https://github.com/salesforcecli/plugin-lightning-dev/blob/4.4.1-alpha.0/src/commands/lightning/dev/app.ts)_ ## `sf lightning dev component` @@ -209,13 +209,14 @@ _See code: [src/commands/lightning/dev/app.ts](https://github.com/salesforcecli/ ``` USAGE - $ sf lightning dev component -o [--json] [--flags-dir ] [-n ] [-c] + $ sf lightning dev component -o [--json] [--flags-dir ] [-n ] [--api-version ] [-c] FLAGS - -c, --client-select Launch component preview without selecting a component - -n, --name= Name of a component to preview. - -o, --target-org= (required) Username or alias of the target org. Not required if the `target-org` - configuration variable is already set. + -c, --client-select Launch component preview without selecting a component + -n, --name= Name of a component to preview. + -o, --target-org= (required) Username or alias of the target org. Not required if the `target-org` + configuration variable is already set. + --api-version= Override the api version used for api requests made by this command GLOBAL FLAGS --flags-dir= Import flag values from a directory. @@ -248,7 +249,7 @@ EXAMPLES $ sf lightning dev component --name myComponent ``` -_See code: [src/commands/lightning/dev/component.ts](https://github.com/salesforcecli/plugin-lightning-dev/blob/4.4.0/src/commands/lightning/dev/component.ts)_ +_See code: [src/commands/lightning/dev/component.ts](https://github.com/salesforcecli/plugin-lightning-dev/blob/4.4.1-alpha.0/src/commands/lightning/dev/component.ts)_ ## `sf lightning dev site` @@ -304,6 +305,6 @@ EXAMPLES $ sf lightning dev site --name "Partner Central" --target-org myOrg --get-latest ``` -_See code: [src/commands/lightning/dev/site.ts](https://github.com/salesforcecli/plugin-lightning-dev/blob/4.4.0/src/commands/lightning/dev/site.ts)_ +_See code: [src/commands/lightning/dev/site.ts](https://github.com/salesforcecli/plugin-lightning-dev/blob/4.4.1-alpha.0/src/commands/lightning/dev/site.ts)_ diff --git a/package.json b/package.json index 8ea301f2..551d69f4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@salesforce/plugin-lightning-dev", "description": "Lightning development tools for LEX, Mobile, and Experience Sites", - "version": "4.4.0", + "version": "4.4.1-alpha.0", "author": "Salesforce", "bugs": "https://github.com/forcedotcom/cli/issues", "dependencies": {