From ce709c774627048521507d3aa01f4e66498026f0 Mon Sep 17 00:00:00 2001 From: Deepu Mungamuri Date: Fri, 13 Feb 2026 21:01:05 +0530 Subject: [PATCH 1/4] feat: add Vite proxy detection and TTY-aware messaging - Detect Vite WebApp proxy via health check, skip standalone proxy when active - Use getErrorPageTemplate from @salesforce/webapp-experimental/proxy - TTY-aware stop message (Ctrl+C in terminal vs VS Code command palette) - Add info.ready-for-development-vite for Vite proxy case - Simplify ready-for-development to show only URL to open - Resolve PR review comments on messaging Co-authored-by: Cursor --- README.md | 505 +++++++++++++++----- SF_WEBAPP_DEV_GUIDE.md | 682 ---------------------------- messages/webapp.dev.md | 15 +- package.json | 2 +- src/commands/webapp/dev.ts | 111 +++-- src/proxy/ProxyServer.ts | 7 +- src/server/DevServerManager.ts | 18 +- test/commands/webapp/dev.test.ts | 157 +++++++ test/config/ManifestWatcher.test.ts | 25 +- test/config/types.test.ts | 20 +- test/proxy/ProxyServer.test.ts | 6 + yarn.lock | 31 +- 12 files changed, 701 insertions(+), 878 deletions(-) delete mode 100644 SF_WEBAPP_DEV_GUIDE.md diff --git a/README.md b/README.md index 7cbe849..24ecc47 100644 --- a/README.md +++ b/README.md @@ -42,186 +42,453 @@ Additionally, there are some additional tests that the Salesforce CLI will enfor # Salesforce CLI Webapp Plugin +> **Develop web applications with seamless Salesforce integration** + A Salesforce CLI plugin for building and deploying web applications that integrate with Salesforce. This plugin provides tools for local development, packaging, and deployment of webapps with built-in Salesforce authentication. This plugin is bundled with the [Salesforce CLI](https://developer.salesforce.com/tools/sfdxcli). For more information on the CLI, read the [getting started guide](https://developer.salesforce.com/docs/atlas.en-us.sfdx_setup.meta/sfdx_setup/sfdx_setup_intro.htm). We always recommend using the latest version of these commands bundled with the CLI, however, you can install a specific version or tag if needed. -## Features +## Key Features + +- **Auto-Discovery**: Automatically finds webapps in `webapplications/` folder +- **Optional Manifest**: `webapplication.json` is optional - uses sensible defaults +- **Auto-Selection**: Automatically selects webapp when running from inside its folder +- **Interactive Selection**: Prompts with arrow-key navigation to select webapp at project root +- **Authentication Injection**: Automatically adds Salesforce auth headers to API calls +- **Intelligent Routing**: Routes requests to dev server or Salesforce based on URL patterns +- **Hot Module Replacement**: Full HMR support for Vite, Webpack, and other bundlers +- **Manifest Hot Reload**: Edit `webapplication.json` while running - changes apply automatically +- **Health Monitoring**: Displays helpful error pages when dev server is down with auto-refresh +- **Framework Agnostic**: Works with any web framework (React, Vue, Angular, etc.) + +--- + +## Install + +```bash +sf plugins install @salesforce/plugin-webapp@x.y.z +``` -- 🔐 **Local Development Proxy** - Run webapps locally with automatic Salesforce authentication -- 🌐 **Intelligent Request Routing** - Automatically routes requests between Salesforce APIs and dev servers -- 🔄 **Dev Server Management** - Spawns and monitors dev servers (Vite, CRA, Next.js) -- 🎨 **Beautiful Error Handling** - HTML error pages with auto-refresh and diagnostics -- 💚 **Health Monitoring** - Periodic health checks with status updates -- 🔧 **Hot Config Reload** - Detects `webapplication.json` changes automatically +--- ## Quick Start -1. **Install the plugin:** +### 1. Create your webapp in the SFDX project structure - ```bash - sf plugins install @salesforce/plugin-webapp - ``` +``` +my-sfdx-project/ +├── sfdx-project.json +└── force-app/main/default/webapplications/ + └── my-app/ + ├── my-app.webapplication-meta.xml # Required: identifies as webapp + ├── package.json + ├── src/ + └── webapplication.json # Optional: dev configuration +``` -2. **Authenticate with Salesforce:** +### 2. Run the command - ```bash - sf org login web --alias myorg - ``` +```bash +sf webapp dev --target-org myOrg --open +``` -3. **Create webapplication.json:** - - ```json - { - "name": "myapp", - "label": "My Web App", - "version": "1.0.0", - "apiVersion": "60.0", - "outputDir": "dist", - "dev": { - "command": "npm run dev" - } - } - ``` +### 3. Start developing -4. **Start development:** - ```bash - sf webapp dev --name myapp --target-org myorg --open - ``` +Browser opens with your app running and Salesforce authentication ready. -## Documentation +- **With Vite plugin**: Open `http://localhost:5173` (Vite handles proxy) +- **Without Vite plugin**: Open `http://localhost:4545` (standalone proxy) -📚 **[Complete Guide](SF_WEBAPP_DEV_GUIDE.md)** - Comprehensive documentation covering: +> **Note**: `{name}.webapplication-meta.xml` is **required** to identify a valid webapp. The `webapplication.json` is optional - if not present, defaults to `npm run dev` command. -- Overview and architecture -- Getting started (5-minute quick start) -- Building the plugin -- Command usage and options -- File structure and components -- VSCode integration -- Advanced features (hot reload, error capture, etc.) -- Troubleshooting and FAQ +--- -## Install +## Commands + +### `sf webapp dev` + +Start a local development proxy server for webapp development with Salesforce authentication. ```bash -sf plugins install @salesforce/plugin-webapp@x.y.z +sf webapp dev [OPTIONS] ``` -## Issues +#### Options -Please report any issues at https://github.com/forcedotcom/cli/issues +| Option | Short | Description | Default | +| -------------- | ----- | ----------------------------------------------- | ------------- | +| `--target-org` | `-o` | Salesforce org alias or username | Required | +| `--name` | `-n` | Web application name (from webapplication.json) | Auto-discover | +| `--url` | `-u` | Explicit dev server URL | Auto-detect | +| `--port` | `-p` | Proxy server port | 4545 | +| `--open` | `-b` | Open browser automatically | false | -## Contributing +#### Examples -1. Please read our [Code of Conduct](CODE_OF_CONDUCT.md) -2. Create a new issue before starting your project so that we can keep track of - what you are trying to add/fix. That way, we can also offer suggestions or - let you know if there is already an effort in progress. -3. Fork this repository. -4. [Build the plugin locally](#build) -5. Create a _topic_ branch in your fork. Note, this step is recommended but technically not required if contributing using a fork. -6. Edit the code in your fork. -7. Write appropriate tests for your changes. Try to achieve at least 95% code coverage on any new code. No pull request will be accepted without unit tests. -8. Sign CLA (see [CLA](#cla) below). -9. Send us a pull request when you are done. We'll review your code, suggest any needed changes, and merge it in. +```bash +# Simplest - auto-discovers webapp +sf webapp dev --target-org myOrg -### CLA +# With browser auto-open +sf webapp dev --target-org myOrg --open -External contributors will be required to sign a Contributor's License -Agreement. You can do so by going to https://cla.salesforce.com/sign-cla. +# Specify webapp by name (when multiple exist) +sf webapp dev --name myApp --target-org myOrg -### Build +# Custom proxy port +sf webapp dev --target-org myOrg --port 8080 + +# Connect to existing dev server (proxy-only mode) +sf webapp dev --target-org myOrg --url http://localhost:5173 + +# Debug mode +SF_LOG_LEVEL=debug sf webapp dev --target-org myOrg +``` -To build the plugin locally, make sure to have yarn installed and run the following commands: +--- + +## Configuration + +### webapplication.json Schema + +The `webapplication.json` file is **optional**. If not present, defaults are used. + +| Field | Type | Description | Default | +| ------------- | ------ | ----------------------------------------- | ------------- | +| `name` | string | Unique identifier (used with --name flag) | Folder name | +| `dev.command` | string | Command to start the dev server | `npm run dev` | +| `dev.url` | string | Dev server URL (skip starting server) | Auto-detect | + +#### Examples + +**No manifest (uses defaults):** +``` +webapplications/my-app/ +├── my-app.webapplication-meta.xml +├── package.json # Has "scripts": { "dev": "vite" } +└── src/ +``` + +**Custom dev command:** +```json +{ + "dev": { + "command": "npm start" + } +} +``` + +**Explicit URL (dev server already running):** +```json +{ + "dev": { + "url": "http://localhost:5173" + } +} +``` + +--- + +## Webapp Discovery + +The command discovers webapps using a deterministic algorithm. Webapps are identified by the presence of a `{name}.webapplication-meta.xml` file (SFDX metadata format). + +### Discovery Behavior + +| Scenario | Behavior | +| ----------------------------------- | --------------------------------------------------------- | +| `--name myApp` provided | Finds webapp by name, starts dev server | +| Running from inside webapp folder | Auto-selects that webapp | +| `--name` conflicts with current dir | Error: must match current webapp or run from project root | +| At SFDX project root | Prompts for webapp selection | +| Outside SFDX project with meta.xml | Uses current directory as standalone webapp | +| No webapp found | Shows error with helpful message | + +### Folder Structure + +``` +my-sfdx-project/ +├── sfdx-project.json # SFDX project marker +└── force-app/main/default/ + └── webapplications/ # Standard SFDX location + ├── app-one/ + │ ├── app-one.webapplication-meta.xml # Required + │ ├── webapplication.json # Optional + │ ├── package.json + │ └── src/ + └── app-two/ + ├── app-two.webapplication-meta.xml # Required + ├── package.json + └── src/ +``` + +### Interactive Selection + +When at the SFDX project root, you'll see an interactive prompt to select a webapp: + +``` +? Select the webapp to run: (Use arrow keys) +❯ MyApp + app-two + CustomName +``` + +--- + +## Vite Integration (Recommended) + +When using **Vite** as your bundler, the `@salesforce/vite-plugin-webapp-experimental` package provides built-in proxy functionality. + +### Setup + +**1. Install the Vite plugin** ```bash -# Clone the repository -git clone git@github.com:salesforcecli/plugin-webapp +npm install -D @salesforce/vite-plugin-webapp-experimental +``` -# Install the dependencies and compile -yarn && yarn build +**2. Configure vite.config.ts** + +```typescript +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import salesforce from '@salesforce/vite-plugin-webapp-experimental'; + +export default defineConfig({ + plugins: [ + react(), + salesforce() // No configuration needed + ], +}); +``` + +**3. Run the dev command** + +```bash +sf webapp dev --target-org myOrg +``` + +### How It Works + +The CLI automatically detects whether your Vite dev server has the Salesforce plugin by sending a health check request. If the plugin responds with `X-Salesforce-WebApp-Proxy: true`, the CLI skips starting its standalone proxy. + +| Scenario | Proxy Behavior | +|-----------------------------|------------------------------------------------| +| Vite plugin **present** | Uses Vite's built-in proxy (open `:5173`) | +| Vite plugin **not present** | CLI creates standalone proxy (open `:4545`) | + +### Benefits + +| Feature | Vite Plugin | Standalone Proxy | +|-------------------------|--------------------|---------------------------| +| Single port to access | ✅ (5173) | ❌ (proxy 4545, dev 5173) | +| Simpler browser URL | ✅ `localhost:5173`| `localhost:4545` | +| HMR through same port | ✅ Native | ✅ Forwarded | + +--- + +## The `--url` Flag + +The `--url` flag provides control over which dev server URL the proxy uses. + +| Scenario | What Happens | +| ------------------------ | ----------------------------------------------------------------- | +| `--url` is reachable | **Proxy-only mode**: Skips starting dev server, only starts proxy | +| `--url` is NOT reachable | Starts dev server, warns if actual URL differs from `--url` | +| No `--url` provided | Starts dev server automatically, detects URL | + +### Example: Connect to Existing Dev Server + +```bash +# Terminal 1: Start your dev server manually +npm run dev +# Output: Local: http://localhost:5173/ + +# Terminal 2: Connect proxy to your running server +sf webapp dev --url http://localhost:5173 --target-org myOrg +``` + +--- + +## Troubleshooting + +### "No webapp found" or "No valid webapps" + +Ensure your webapp has the required `.webapplication-meta.xml` file: + +``` +webapplications/my-app/ +├── my-app.webapplication-meta.xml # Required! +├── package.json +└── webapplication.json # Optional ``` -To use your plugin, run using the local `./bin/dev` or `./bin/dev.cmd` file. +### "You are inside webapp X but specified --name Y" + +**Solutions:** +- Remove `--name` to use the current webapp +- Navigate to the project root and use `--name` + +### "Dependencies Not Installed" / "command not found" ```bash -# Run using local run file. -./bin/dev hello world +cd webapplications/my-app +npm install ``` -There should be no differences when running via the Salesforce CLI or using the local run file. However, it can be useful to link the plugin to do some additional testing or run your commands from anywhere on your machine. +### "Port 4545 already in use" ```bash -# Link your plugin to the sf cli -sf plugins link . -# To verify -sf plugins +sf webapp dev --port 8080 --target-org myOrg ``` -## Commands +### "Authentication Failed" -### `sf webapp dev` +```bash +sf org login web --alias myOrg +``` -Start a local development proxy server for webapp development with Salesforce authentication. +### Debug Mode + +```bash +# Terminal 1: Tail logs +tail -f ~/.sf/sf-$(date +%Y-%m-%d).log | grep --line-buffered WebappDev + +# Terminal 2: Run with debug +SF_LOG_LEVEL=debug sf webapp dev --target-org myOrg +``` + +--- + +## Architecture + +### Request Flow + +The command supports two proxy modes: + +**With Vite Plugin:** +``` +Browser → Vite Dev Server (:5173) → Salesforce (with auth) + ↓ + Proxy handles: + • /services/* → Salesforce + • Everything else → Vite HMR +``` + +**Standalone Proxy:** +``` +Browser → Proxy Server (:4545) → Salesforce (with auth) + ↓ + Dev Server (:5173) for static assets +``` + +### Request Routing + +| URL Path | Routed To | +|-----------------------------|---------------------| +| `/services/*`, `/lwr/apex/*`| Salesforce (+ auth) | +| Everything else | Dev Server | + +--- + +## VSCode Integration + +The command integrates with the Salesforce VSCode UI Preview extension (`salesforcedx-vscode-ui-preview`): + +1. Extension detects `webapplication.json` in workspace +2. User clicks "Preview" button +3. Extension executes: `sf webapp dev --target-org --open` +4. Browser opens with the app running + +--- + +## JSON Output + +For scripting and CI/CD: ```bash -USAGE - $ sf webapp dev --name --target-org [options] +sf webapp dev --target-org myOrg --json +``` + +```json +{ + "status": 0, + "result": { + "url": "http://localhost:4545", + "devServerUrl": "http://localhost:5173" + } +} +``` -REQUIRED FLAGS - -n, --name= Name of the webapp (must match webapplication.json) - -o, --target-org= Salesforce org to authenticate against +--- -OPTIONAL FLAGS - -u, --url= Dev server URL (overrides webapplication.json) - -p, --port= Proxy server port (default: 4545) - --open Open browser automatically +## Issues -GLOBAL FLAGS - --flags-dir= Import flag values from a directory - --json Format output as json +Please report any issues at https://github.com/forcedotcom/cli/issues -DESCRIPTION - Start a local development proxy server for webapp development. +## Contributing - This command starts a local HTTP proxy server that handles Salesforce - authentication and routes requests between your local dev server and - Salesforce APIs. It automatically spawns and monitors your dev server, - detects the URL, and provides health monitoring. +1. Please read our [Code of Conduct](CODE_OF_CONDUCT.md) +2. Create a new issue before starting your project so that we can keep track of what you are trying to add/fix. +3. Fork this repository. +4. [Build the plugin locally](#build) +5. Create a _topic_ branch in your fork. +6. Edit the code in your fork. +7. Write appropriate tests for your changes. Try to achieve at least 95% code coverage on any new code. +8. Sign CLA (see [CLA](#cla) below). +9. Send us a pull request when you are done. -EXAMPLES - Start proxy with automatic dev server management: +### CLA - $ sf webapp dev --name myapp --target-org myorg --open +External contributors will be required to sign a Contributor's License Agreement. You can do so by going to https://cla.salesforce.com/sign-cla. - Use existing dev server: +### Build - $ sf webapp dev --name myapp --target-org myorg --url http://localhost:5173 --open +```bash +# Clone the repository +git clone git@github.com:salesforcecli/plugin-webapp - Use custom proxy port: +# Install dependencies and compile +yarn && yarn build - $ sf webapp dev --name myapp --target-org myorg --port 8080 --open +# Run using local dev file +./bin/dev webapp dev --target-org myOrg -SUPPORTED DEV SERVERS - - Vite - - Create React App (Webpack) - - Next.js - - Any server that outputs http://localhost:PORT +# Link to SF CLI for testing +sf plugins link . +sf plugins # Verify + +# After code changes, just rebuild +yarn build +``` -FEATURES - - Automatic Salesforce authentication injection - - Intelligent request routing (Salesforce vs dev server) - - WebSocket support for Hot Module Replacement (HMR) - - Beautiful HTML error pages with auto-refresh - - Periodic health monitoring (every 5s) - - Configuration file watching (webapplication.json) - - Graceful shutdown on Ctrl+C +### Project Structure -SEE ALSO - - Complete Guide: SF_WEBAPP_DEV_GUIDE.md +``` +plugin-webapp/ +├── src/ +│ ├── commands/webapp/ +│ │ └── dev.ts # Main command implementation +│ ├── config/ +│ │ ├── manifest.ts # Manifest type definitions +│ │ ├── ManifestWatcher.ts # File watching and hot reload +│ │ ├── webappDiscovery.ts # Auto-discovery logic +│ │ └── types.ts # Shared TypeScript types +│ ├── proxy/ +│ │ └── ProxyServer.ts # HTTP/WebSocket proxy server +│ ├── server/ +│ │ └── DevServerManager.ts # Dev server process management +│ ├── error/ +│ │ └── DevServerErrorParser.ts # Parse dev server errors +│ └── templates/ +│ ├── ErrorPageRenderer.ts # Browser error page generation +│ └── error-page.html # Error page HTML template +├── messages/ +│ └── webapp.dev.md # CLI messages and help text +└── schemas/ + └── webapp-dev.json # JSON schema for output ``` diff --git a/SF_WEBAPP_DEV_GUIDE.md b/SF_WEBAPP_DEV_GUIDE.md deleted file mode 100644 index 1459476..0000000 --- a/SF_WEBAPP_DEV_GUIDE.md +++ /dev/null @@ -1,682 +0,0 @@ -# Salesforce Webapp Dev Command Guide - -> **Develop web applications with seamless Salesforce integration** - ---- - -## Overview - -The `sf webapp dev` command enables local development of modern web applications (React, Vue, Angular, etc.) with automatic Salesforce authentication. It intelligently discovers your webapp configuration, handles proxy routing, injects authentication headers, and supports hot reload - so you can focus on building your app. - -### Key Features - -- **Auto-Discovery**: Automatically finds webapps in `webapplications/` folder -- **Optional Manifest**: `webapplication.json` is optional - uses sensible defaults -- **Auto-Selection**: Automatically selects webapp when running from inside its folder -- **Interactive Selection**: Prompts with arrow-key navigation when multiple webapps exist -- **Authentication Injection**: Automatically adds Salesforce auth headers to API calls -- **Intelligent Routing**: Routes requests to dev server or Salesforce based on URL patterns -- **Hot Module Replacement**: Full HMR support for Vite, Webpack, and other bundlers -- **Error Detection**: Displays helpful error pages with fix suggestions -- **Framework Agnostic**: Works with any web framework - ---- - -## Quick Start - -### 1. Create your webapp in the SFDX project structure - -``` -my-sfdx-project/ -├── sfdx-project.json -└── force-app/main/default/webapplications/ - └── my-app/ # Your webapp folder - ├── my-app.webapplication-meta.xml # Required: identifies as webapp - ├── package.json - ├── src/ - └── webapplication.json # Optional: dev configuration -``` - -### 2. Run the command - -```bash -sf webapp dev --target-org myOrg --open -``` - -### 3. Start developing - -Browser opens to `http://localhost:4545` with your app running and Salesforce authentication ready. - -> **Note**: -> -> - `{name}.webapplication-meta.xml` is **required** to identify a valid webapp -> - `webapplication.json` is optional for dev configuration. If not present, defaults to: -> - **Name**: From meta.xml filename or folder name -> - **Dev command**: `npm run dev` -> - **Manifest watching**: Disabled - ---- - -## Command Syntax - -```bash -sf webapp dev [OPTIONS] -``` - -### Options - -| Option | Short | Description | Default | -| -------------- | ----- | ----------------------------------------------- | ------------- | -| `--target-org` | `-o` | Salesforce org alias or username | Required | -| `--name` | `-n` | Web application name (from webapplication.json) | Auto-discover | -| `--url` | `-u` | Explicit dev server URL | Auto-detect | -| `--port` | `-p` | Proxy server port | 4545 | -| `--open` | `-b` | Open browser automatically | false | - -### Examples - -```bash -# Simplest - auto-discovers webapplication.json -sf webapp dev --target-org myOrg - -# With browser auto-open -sf webapp dev --target-org myOrg --open - -# Specify webapp by name (when multiple exist) -sf webapp dev --name myApp --target-org myOrg - -# Custom port -sf webapp dev --target-org myOrg --port 8080 - -# Explicit dev server URL (skip auto-detection) -sf webapp dev --target-org myOrg --url http://localhost:5173 - -# Debug mode -SF_LOG_LEVEL=debug sf webapp dev --target-org myOrg -``` - ---- - -## Webapp Discovery - -The command discovers webapps using a simplified, deterministic algorithm. Webapps are identified by the presence of a `{name}.webapplication-meta.xml` file (SFDX metadata format). The optional `webapplication.json` file provides dev configuration. - -### How Discovery Works - -```mermaid -flowchart TD - Start["sf webapp dev"] --> CheckInside{"Inside webapplications/
webapp folder?"} - - CheckInside -->|Yes| HasNameInside{"--name provided?"} - HasNameInside -->|Yes, different| ErrorConflict["Error: --name conflicts
with current directory"] - HasNameInside -->|No or same| AutoSelect["Auto-select current webapp"] - - CheckInside -->|No| CheckSFDX{"In SFDX project?
(sfdx-project.json)"} - - CheckSFDX -->|Yes| CheckPath["Check force-app/main/
default/webapplications/"] - CheckPath --> HasName{"--name provided?"} - - CheckSFDX -->|No| CheckMetaXml{"Current dir has
.webapplication-meta.xml?"} - CheckMetaXml -->|Yes| UseStandalone["Use current dir as webapp"] - CheckMetaXml -->|No| ErrorNone["Error: No webapp found"] - - HasName -->|Yes| SearchByName["Find webapp by name"] - HasName -->|No| Prompt["Interactive selection prompt
(always, even if 1 webapp)"] - - SearchByName --> UseWebapp["Use webapp"] - AutoSelect --> UseWebapp - UseStandalone --> UseWebapp - Prompt --> UseWebapp - - UseWebapp --> StartDev["Start dev server and proxy"] -``` - -### Discovery Behavior - -| Scenario | Behavior | -| ----------------------------------- | --------------------------------------------------------- | -| `--name myApp` provided | Finds webapp by name, starts dev server | -| Running from inside webapp folder | Auto-selects that webapp | -| `--name` conflicts with current dir | Error: must match current webapp or run from project root | -| At SFDX project root | **Always prompts** for webapp selection | -| Outside SFDX project with meta.xml | Uses current directory as standalone webapp | -| No webapp found | Shows error with helpful message | - -### Folder Structure (SFDX Project) - -``` -my-sfdx-project/ -├── sfdx-project.json # SFDX project marker -└── force-app/main/default/ - └── webapplications/ # Standard SFDX location - ├── app-one/ # Webapp 1 (with dev config) - │ ├── app-one.webapplication-meta.xml # Required: identifies as webapp - │ ├── webapplication.json # Optional: dev configuration - │ ├── package.json - │ └── src/ - └── app-two/ # Webapp 2 (no dev config) - ├── app-two.webapplication-meta.xml # Required - ├── package.json - └── src/ -``` - -### Discovery Strategy - -The command uses a simplified, deterministic approach: - -1. **Inside webapp folder**: If running from `webapplications//` or deeper, auto-selects that webapp -2. **SFDX project root**: Uses fixed path `force-app/main/default/webapplications/` -3. **Standalone**: If current directory has a `.webapplication-meta.xml` file, uses it directly - -**Important**: Only directories containing a `{name}.webapplication-meta.xml` file are recognized as valid webapps. - -### Interactive Selection - -When multiple webapps are found, you'll see an interactive prompt: - -``` -Found 3 webapps in project -? Select the webapp to run: (Use arrow keys) -❯ MyApp - My Application (webapplications/app-one) - app-two (webapplications/app-two) [no manifest] - CustomName (webapplications/app-three) -``` - -Format: - -- **With manifest + label**: `Name - Label (path)` -- **With manifest, no label**: `Name (path)` -- **No manifest**: `name (path) [no manifest]` - ---- - -## Architecture - -### Request Flow - -``` -┌─────────────────────────────────────────────────┐ -│ Your Browser │ -│ http://localhost:4545 │ -└───────────────────┬─────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────────┐ -│ Proxy Server (Port 4545) │ -│ │ -│ Routes requests based on URL pattern: │ -│ • /services/* → Salesforce (with auth) │ -│ • Everything else → Dev Server │ -└─────────┬─────────────────────┬─────────────────┘ - │ │ - ▼ ▼ -┌─────────────────┐ ┌────────────────────────┐ -│ Dev Server │ │ Salesforce Instance │ -│ (localhost:5173)│ │ + Auth Headers Added │ -│ React/Vue/etc │ │ + API Calls │ -└─────────────────┘ └────────────────────────┘ -``` - -### How Requests Are Handled - -**Static assets (JS, CSS, HTML, images):** - -``` -Browser → Proxy → Dev Server → Response -``` - -**Salesforce API calls (`/services/*`):** - -``` -Browser → Proxy → [Auth Headers Injected] → Salesforce → Response -``` - ---- - -## Configuration - -### webapplication.json Schema - -The `webapplication.json` file is **optional**. All fields are also optional - missing fields use defaults. - -#### All Fields (All Optional) - -```json -{ - "name": "myApp", - "label": "My Application", - "version": "1.0.0", - "outputDir": "dist", - "dev": { - "command": "npm run dev" - } -} -``` - -| Field | Type | Description | Default | -| ----------- | ------ | ----------------------------------------- | ------------------ | -| `name` | string | Unique identifier (used with --name flag) | Folder name | -| `label` | string | Human-readable display name | None | -| `version` | string | Semantic version (e.g., "1.0.0") | None | -| `outputDir` | string | Build output directory | None (deploy only) | - -#### Dev Configuration - -**Option A: No manifest (uses defaults)** - -If no `webapplication.json` exists: - -- Dev command: `npm run dev` -- Name: folder name -- Manifest watching: disabled - -**Option B: Minimal manifest** - -```json -{ - "dev": { - "command": "npm start" - } -} -``` - -Only specify what you need to override. - -**Option C: Explicit URL (dev server already running)** - -```json -{ - "dev": { - "url": "http://localhost:5173" - } -} -``` - -Use this when you want to start the dev server yourself. - -#### Routing Configuration (Optional) - -```json -{ - "routing": { - "rewrites": [{ "route": "/api/:path*", "target": "/services/apexrest/:path*" }], - "redirects": [{ "route": "/old-path", "target": "/new-path", "statusCode": 301 }], - "trailingSlash": "never", - "fallback": "/index.html" - } -} -``` - -### Example: Minimal (No Manifest) - -``` -webapplications/ -└── my-dashboard/ - ├── package.json # Has "scripts": { "dev": "vite" } - └── src/ -``` - -Run: `sf webapp dev --target-org myOrg` - -Console output: - -``` -Warning: No webapplication.json found for webapp "my-dashboard" - Location: my-dashboard - Using defaults: - → Name: "my-dashboard" (derived from folder) - → Command: "npm run dev" - → Manifest watching: disabled - 💡 To customize, create a webapplication.json file in your webapp directory. - -✅ Using webapp: my-dashboard (webapplications/my-dashboard) - -✅ Ready for development! - → Proxy: http://localhost:4545 (open this in your browser) - → Dev server: http://localhost:5173 -Press Ctrl+C to stop -``` - -### Example: Full Configuration - -```json -{ - "name": "salesDashboard", - "label": "Sales Dashboard", - "description": "Real-time sales analytics dashboard", - "version": "2.1.0", - "outputDir": "dist", - "dev": { - "command": "npm run dev" - }, - "routing": { - "rewrites": [{ "route": "/api/:path*", "target": "/services/apexrest/:path*" }], - "trailingSlash": "never" - } -} -``` - ---- - -## Features - -### Manifest Hot Reload - -Edit `webapplication.json` while running - changes apply automatically: - -```bash -# Console output when you change webapplication.json: -Manifest changed detected -✓ Manifest reloaded successfully -Dev server URL updated to: http://localhost:5174 -``` - -> **Note**: Manifest watching is only enabled when `webapplication.json` exists. Webapps without manifests don't have this feature. - -### Health Monitoring - -The proxy continuously monitors dev server availability: - -- Displays "No Dev Server Detected" page when server is down -- Auto-refreshes when server comes back up -- Shows helpful suggestions for common issues - -### WebSocket Support - -Full Hot Module Replacement support through the proxy: - -- Vite HMR (`/@vite/*`, `/__vite_hmr`) -- Webpack HMR (`/__webpack_hmr`) -- Works with React Fast Refresh, Vue HMR, etc. - -### Code Builder Support - -Automatically detects Salesforce Code Builder environment and binds to `0.0.0.0` for proper port forwarding in cloud environments. - ---- - -## The `--url` Flag - -The `--url` flag provides control over which dev server URL the proxy uses. It has smart behavior depending on whether the URL is already available. - -### Behavior - -| Scenario | What Happens | -| ------------------------ | ----------------------------------------------------------------- | -| `--url` is reachable | **Proxy-only mode**: Skips starting dev server, only starts proxy | -| `--url` is NOT reachable | Starts dev server, warns if actual URL differs from `--url` | -| No `--url` provided | Starts dev server automatically, detects URL | - -### Use Case 1: Connect to Existing Dev Server (Proxy-Only Mode) - -If you prefer to manage your dev server separately: - -```bash -# Terminal 1: Start your dev server manually -cd my-webapp -npm run dev -# Output: Local: http://localhost:5173/ - -# Terminal 2: Connect proxy to your running server -sf webapp dev --url http://localhost:5173 --target-org myOrg -``` - -**Output:** - -``` -✅ URL http://localhost:5173 is already available, skipping dev server startup (proxy-only mode) -✅ Ready for development! - → Proxy: http://localhost:4545 - → Dev server: http://localhost:5173 -``` - -### Use Case 2: URL Mismatch Warning - -If you specify a `--url` that doesn't match where the dev server actually starts: - -```bash -# No dev server running, specify wrong port -sf webapp dev --url http://localhost:9999 --target-org myOrg -``` - -**Output:** - -``` -Warning: ⚠️ The --url flag (http://localhost:9999) does not match the actual dev server URL (http://localhost:5173/). -The proxy will use the actual dev server URL. -``` - -The command continues working with the actual dev server URL. - -### Important Notes - -- The `--url` flag checks **only** the URL you specify, not other ports -- If you have a dev server on port 5173 but specify `--url http://localhost:9999`: - - Command checks 9999 → not available - - Starts a NEW dev server → may get port 5174 (if 5173 is taken) - - Warns about mismatch (9999 ≠ 5174) -- To use an existing dev server, specify its **exact** URL with `--url` - ---- - -## Troubleshooting - -### "No webapp found" or "No valid webapps" - -Ensure your webapp has the required `.webapplication-meta.xml` file: - -``` -force-app/main/default/webapplications/ -└── my-app/ - ├── my-app.webapplication-meta.xml # Required! - ├── package.json - └── webapplication.json # Optional (for dev config) -``` - -The `.webapplication-meta.xml` file identifies a valid SFDX webapp. Without it, the directory is ignored. - -### "You are inside webapp X but specified --name Y" - -This error occurs when you're inside one webapp folder but try to run a different webapp: - -```bash -# You're in FirstWebApp folder but trying to run SecondWebApp -cd webapplications/FirstWebApp -sf webapp dev --name SecondWebApp --target-org myOrg # Error! -``` - -**Solutions:** - -- Remove `--name` to use the current webapp -- Navigate to the project root and use `--name` -- Navigate to the correct webapp folder - -### "No webapp found with name X" - -The `--name` flag matches either: - -1. The `name` field in `webapplication.json` -2. The folder name (if no manifest or no name in manifest) - -```bash -# This looks for webapp named "myApp" -sf webapp dev --name myApp --target-org myOrg -``` - -### "Dependencies Not Installed" / "command not found" - -Install dependencies in your webapp folder: - -```bash -cd webapplications/my-app -npm install -``` - -### "No Dev Server Detected" - -1. Ensure dev server is running: `npm run dev` -2. Verify URL in `webapplication.json` is correct -3. Try explicit URL: `sf webapp dev --url http://localhost:5173 --target-org myOrg` - -### "Port 4545 already in use" - -```bash -# Use a different port -sf webapp dev --port 8080 --target-org myOrg - -# Or find and kill the process using the port -lsof -i :4545 -kill -9 -``` - -### "Authentication Failed" - -Re-authorize your Salesforce org: - -```bash -sf org login web --alias myOrg -``` - -### Debug Mode - -Enable detailed logging by setting `SF_LOG_LEVEL=debug`. Debug logs are written to the SF CLI log file (not stdout). - -**Step 1: Start log tail in Terminal 1** - -```bash -# Tail today's log file, filtering for webapp messages -tail -f ~/.sf/sf-$(date +%Y-%m-%d).log | grep --line-buffered WebappDev - -# Or for cleaner output (requires jq): -tail -f ~/.sf/sf-$(date +%Y-%m-%d).log | grep --line-buffered WebappDev | jq -r '.msg' -``` - -**Step 2: Run command in Terminal 2** - -```bash -SF_LOG_LEVEL=debug sf webapp dev --target-org myOrg -``` - -**Example debug output:** - -``` -Discovering webapplication.json manifest(s)... -Using webapp: myApp at webapplications/my-app -Manifest loaded: myApp -Starting dev server with command: npm run dev -Dev server ready at: http://localhost:5173/ -Using authentication for org: user@example.com -Starting proxy server on port 4545... -Proxy server running on http://localhost:4545 -``` - ---- - -## VSCode Integration - -The command integrates with the Salesforce VSCode UI Preview extension (`salesforcedx-vscode-ui-preview`): - -1. Extension detects `webapplication.json` in workspace -2. User clicks "Preview" button on the file -3. Extension executes: `sf webapp dev --target-org --open` -4. If multiple webapps exist, uses `--name` to specify which one -5. Browser opens with the app running - ---- - -## JSON Output - -For scripting and CI/CD, use the `--json` flag: - -```bash -sf webapp dev --target-org myOrg --json -``` - -Output: - -```json -{ - "status": 0, - "result": { - "url": "http://localhost:4545", - "devServerUrl": "http://localhost:5173" - } -} -``` - ---- - -## Plugin Development - -### Building the Plugin - -```bash -cd /path/to/plugin-webapp - -# Install dependencies -yarn install - -# Build -yarn build - -# Link to SF CLI -sf plugins link . - -# Verify installation -sf plugins -``` - -### After Code Changes - -```bash -yarn build # Rebuild - no re-linking needed -``` - -### Project Structure - -``` -plugin-webapp/ -├── src/ -│ ├── commands/webapp/ -│ │ └── dev.ts # Main command implementation -│ ├── auth/ -│ │ └── org.ts # Salesforce authentication -│ ├── config/ -│ │ ├── manifest.ts # Manifest type definitions -│ │ ├── ManifestWatcher.ts # File watching and hot reload -│ │ ├── webappDiscovery.ts # Auto-discovery logic -│ │ └── types.ts # Shared TypeScript types -│ ├── proxy/ -│ │ ├── ProxyServer.ts # HTTP/WebSocket proxy server -│ │ ├── handler.ts # Request routing and forwarding -│ │ └── routing.ts # URL pattern matching -│ ├── server/ -│ │ └── DevServerManager.ts # Dev server process management -│ ├── error/ -│ │ ├── ErrorHandler.ts # Error creation utilities -│ │ ├── DevServerErrorParser.ts -│ │ └── ErrorPageRenderer.ts -│ └── templates/ -│ └── error-page.html # Error page template -├── messages/ -│ └── webapp.dev.md # CLI messages and help text -└── schemas/ - └── webapp-dev.json # JSON schema for output -``` - -### Key Components - -| Component | Purpose | -| ---------------------- | ------------------------------------------------ | -| `dev.ts` | Command orchestration and lifecycle | -| `webappDiscovery.ts` | SFDX project detection and webapp discovery | -| `org.ts` | Salesforce authentication token management | -| `ProxyServer.ts` | HTTP proxy with WebSocket support | -| `handler.ts` | Request routing to dev server or Salesforce | -| `DevServerManager.ts` | Dev server process spawning and monitoring | -| `ManifestWatcher.ts` | webapplication.json file watching for hot reload | -| `ErrorPageRenderer.ts` | Browser error page generation | - ---- - -**Repository:** [github.com/salesforcecli/plugin-webapp](https://github.com/salesforcecli/plugin-webapp) diff --git a/messages/webapp.dev.md b/messages/webapp.dev.md index 1e70110..f6e8f16 100644 --- a/messages/webapp.dev.md +++ b/messages/webapp.dev.md @@ -82,7 +82,7 @@ Dev server URL: %s # info.proxy-url -Proxy URL: %s (open this in your browser) +Proxy URL: %s (open this URL in your browser) # info.ready-for-development @@ -93,6 +93,10 @@ Proxy URL: %s (open this in your browser) Press Ctrl+C to stop the proxy server +# info.server-running + +Dev server is running. Stop it by running **SFDX: Close Live Preview** from the VS Code command palette. + # info.dev-server-healthy ✓ Dev server is responding at: %s @@ -178,3 +182,12 @@ Using default dev command: %s ⚠️ The --url flag (%s) does not match the actual dev server URL (%s). The proxy will use the actual dev server URL. + +# info.vite-proxy-detected + +Vite WebApp proxy detected at %s - using Vite's built-in proxy (standalone proxy skipped) + +# info.ready-for-development-vite + +✅ Ready for development! + → Dev server: %s (Vite proxy active - open this URL in your browser) diff --git a/package.json b/package.json index 4acd8bb..b7bc154 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "@oclif/plugin-command-snapshot": "^5.3.8", "@salesforce/cli-plugins-testkit": "^5.3.41", "@salesforce/dev-scripts": "^11.0.4", - "@salesforce/plugin-command-reference": "^3.1.79", + "@salesforce/plugin-command-reference": "^3.1.77", "@types/http-proxy": "^1.17.14", "@types/micromatch": "^4.0.10", "eslint-plugin-sf-plugin": "^1.20.33", diff --git a/src/commands/webapp/dev.ts b/src/commands/webapp/dev.ts index fba519e..6dfa0d7 100644 --- a/src/commands/webapp/dev.ts +++ b/src/commands/webapp/dev.ts @@ -118,6 +118,30 @@ export default class WebappDev extends SfCommand { } } + /** + * Check if Vite's WebAppProxyHandler is active at the dev server URL. + * The Vite plugin responds to a health check query parameter with a custom header + * when the proxy middleware is active. + * + * @param devServerUrl - The dev server URL to check + * @returns true if Vite's proxy is handling requests, false otherwise + */ + private static async checkViteProxyActive(devServerUrl: string): Promise { + try { + // The Vite plugin uses a query parameter for health checks, not a path + const healthUrl = new URL(devServerUrl); + healthUrl.searchParams.set('sfProxyHealthCheck', 'true'); + const response = await fetch(healthUrl.toString(), { + method: 'GET', + signal: AbortSignal.timeout(3000), // 3 second timeout + }); + return response.headers.get('X-Salesforce-WebApp-Proxy') === 'true'; + } catch { + // Health check failed - Vite proxy not active + return false; + } + } + // eslint-disable-next-line complexity public async run(): Promise { const { flags } = await this.parse(WebappDev); @@ -290,7 +314,7 @@ export default class WebappDev extends SfCommand { const actualDevServerUrl = await new Promise((resolve, reject) => { const timeout = setTimeout(() => { reject( - new SfError('Dev server did not start within 30 seconds.', 'DevServerTimeoutError', [ + new SfError('❌ Dev server did not start within 30 seconds.', 'DevServerTimeoutError', [ 'The dev server may be taking longer than expected to start', 'Check if the dev server command is correct in webapplication.json', 'Try running the dev server command manually to see if it starts', @@ -327,51 +351,78 @@ export default class WebappDev extends SfCommand { // Ensure devServerUrl is set (should always be set by step 3) if (!devServerUrl) { throw new SfError( - 'Unable to determine dev server URL. Please specify --url or configure dev.url in webapplication.json.', + '❌ Unable to determine dev server URL. Please specify --url or configure dev.url in webapplication.json.', 'DevServerUrlError' ); } - // Step 5: Start proxy server - this.logger.debug(`Starting proxy server on port ${flags.port}...`); - const salesforceInstanceUrl = orgConnection.instanceUrl; - this.proxyServer = new ProxyServer({ - devServerUrl, - salesforceInstanceUrl, - port: flags.port, - manifest: manifest ?? undefined, - orgAlias: orgUsername, - }); + // Step 5: Check for Vite proxy and conditionally start standalone proxy + this.logger.debug('Checking if Vite WebApp proxy is active...'); + const viteProxyActive = await WebappDev.checkViteProxyActive(devServerUrl); - await this.proxyServer.start(); - const proxyUrl = this.proxyServer.getProxyUrl(); - this.logger.debug(`Proxy server running on ${proxyUrl}`); + // Track the final URL to open in browser (either proxy or dev server) + let finalUrl: string; - // Listen for dev server status changes (minimal output) - this.proxyServer.on('dev-server-up', (url: string) => { - this.logger?.debug(messages.getMessage('info.dev-server-detected', [url])); - }); + if (viteProxyActive) { + // Vite's WebAppProxyHandler is handling the proxy - skip standalone proxy + this.log(messages.getMessage('info.vite-proxy-detected', [devServerUrl])); + this.logger.debug('Vite proxy detected, skipping standalone proxy server'); + finalUrl = devServerUrl; + } else { + // Start standalone proxy server + this.logger.debug(`Starting proxy server on port ${flags.port}...`); + const salesforceInstanceUrl = orgConnection.instanceUrl; + this.proxyServer = new ProxyServer({ + devServerUrl, + salesforceInstanceUrl, + port: flags.port, + manifest: manifest ?? undefined, + orgAlias: orgUsername, + }); - this.proxyServer.on('dev-server-down', (url: string) => { - this.log(messages.getMessage('warning.dev-server-unreachable-status', [url])); - this.log(messages.getMessage('info.start-dev-server-hint')); - }); + await this.proxyServer.start(); + const proxyUrl = this.proxyServer.getProxyUrl(); + this.logger.debug(`Proxy server running on ${proxyUrl}`); + + // Listen for dev server status changes (minimal output) + this.proxyServer.on('dev-server-up', (url: string) => { + this.logger?.debug(messages.getMessage('info.dev-server-detected', [url])); + }); + + this.proxyServer.on('dev-server-down', (url: string) => { + this.log(messages.getMessage('warning.dev-server-unreachable-status', [url])); + this.log(messages.getMessage('info.start-dev-server-hint')); + }); - // Step 6: Check if dev server is reachable (non-blocking warning) - if (devServerUrl) { + finalUrl = proxyUrl; + } + + // Step 6: Check if dev server is reachable (non-blocking warning) - only when using standalone proxy + if (!viteProxyActive && devServerUrl) { await this.checkDevServerHealth(devServerUrl); } // Step 7: Open browser if requested if (flags.open) { this.logger.debug('Opening browser...'); - await WebappDev.openBrowser(proxyUrl); + await WebappDev.openBrowser(finalUrl); } // Display usage instructions this.log(''); - this.log(messages.getMessage('info.ready-for-development', [proxyUrl])); - this.log(messages.getMessage('info.press-ctrl-c')); + if (viteProxyActive) { + this.log(messages.getMessage('info.ready-for-development-vite', [devServerUrl])); + } else { + this.log(messages.getMessage('info.ready-for-development', [finalUrl])); + } + // Show appropriate stop message based on execution context + // In TTY (interactive terminal): show "Press Ctrl+C to stop" + // In non-TTY (IDE, CI, piped): show generic "Server running" message + if (process.stdout.isTTY) { + this.log(messages.getMessage('info.press-ctrl-c')); + } else { + this.log(messages.getMessage('info.server-running')); + } this.log(''); // Keep the command running until interrupted or dev server exits @@ -403,7 +454,7 @@ export default class WebappDev extends SfCommand { // Return result (never reached, but required for type safety) return { - url: proxyUrl, + url: finalUrl, devServerUrl: devServerUrl ?? '', }; } catch (error) { @@ -417,7 +468,7 @@ export default class WebappDev extends SfCommand { // Wrap unknown errors const errorMessage = error instanceof Error ? error.message : String(error); - throw new SfError(`Failed to start webapp dev command: ${errorMessage}`, 'UnexpectedError', [ + throw new SfError(`❌ Failed to start webapp dev command: ${errorMessage}`, 'UnexpectedError', [ 'This is an unexpected error', 'Please try again', 'If the problem persists, check the command logs with SF_LOG_LEVEL=debug', diff --git a/src/proxy/ProxyServer.ts b/src/proxy/ProxyServer.ts index 62d0af1..d184689 100644 --- a/src/proxy/ProxyServer.ts +++ b/src/proxy/ProxyServer.ts @@ -176,11 +176,6 @@ export class ProxyServer extends EventEmitter { this.server.on('connection', (socket) => { this.activeConnections.add(socket); - socket.on('error', (err) => { - // Handle ECONNRESET and other socket errors gracefully - // These can happen when the dev server crashes or a client disconnects abruptly - this.logger.debug(`Socket error (${err.message}), cleaning up connection`); - }); socket.once('close', () => { this.activeConnections.delete(socket); }); @@ -412,6 +407,8 @@ export class ProxyServer extends EventEmitter { private initializeProxyHandler(): void { const manifest: WebAppManifest = this.config.manifest ?? { name: 'webapp', + label: 'WebApp', + version: '1.0.0', outputDir: 'dist', }; diff --git a/src/server/DevServerManager.ts b/src/server/DevServerManager.ts index fa6f16b..6475d0d 100644 --- a/src/server/DevServerManager.ts +++ b/src/server/DevServerManager.ts @@ -210,7 +210,7 @@ export class DevServerManager extends EventEmitter { // Validate that command is provided if (!this.options.command) { throw new SfError( - 'Dev server command is required when explicit URL is not provided', + '❌ Dev server command is required when explicit URL is not provided', 'DevServerCommandRequired', ['Provide a "command" in DevServerOptions', 'Or provide an "explicitUrl" to skip spawning'] ); @@ -232,7 +232,7 @@ export class DevServerManager extends EventEmitter { } catch (error) { const sfError = error instanceof Error ? error : new Error(error instanceof Object ? JSON.stringify(error) : String(error)); - throw new SfError(`Failed to spawn dev server process: ${sfError.message}`, 'DevServerSpawnError', [ + throw new SfError(`❌ Failed to spawn dev server process: ${sfError.message}`, 'DevServerSpawnError', [ `Verify the command is correct: ${this.options.command}`, 'Check that the executable exists in your PATH', 'Ensure you have the necessary dependencies installed', @@ -430,10 +430,12 @@ export class DevServerManager extends EventEmitter { this.logger.error(`Dev server error: ${parsedError.title}`); this.logger.debug(`Error type: ${parsedError.type}`); - // Emit the parsed DevServerError directly so the receiver (dev.ts) - // can access stderrLines, title, and type for the error page. - // Previously this was wrapped in SfError which lost those properties. - this.emit('error', parsedError); + // Convert to SfError for proper error handling + // Use just the message (not title) since title will be shown separately + // Prefix with ❌ for visual consistency with success messages (✅) + const sfError = new SfError(`❌ ${parsedError.message}`, 'DevServerError', parsedError.suggestions); + + this.emit('error', sfError); } // Reset state @@ -450,7 +452,7 @@ export class DevServerManager extends EventEmitter { private handleProcessError(error: Error): void { this.logger.error(`Dev server process error: ${error.message}`); - const sfError = new SfError(`Dev server process error: ${error.message}`, 'DevServerProcessError', [ + const sfError = new SfError(`❌ Dev server process error: ${error.message}`, 'DevServerProcessError', [ 'Check that the command is correct in webapplication.json', 'Verify all dependencies are installed', 'Try running the command manually to see the error', @@ -469,7 +471,7 @@ export class DevServerManager extends EventEmitter { this.logger.error('Dev server failed to start within timeout period'); const error = new SfError( - `Dev server did not start within ${this.options.startupTimeout / 1000} seconds`, + `❌ Dev server did not start within ${this.options.startupTimeout / 1000} seconds`, 'DevServerStartupTimeout', [ 'The dev server may be taking longer than expected to start', diff --git a/test/commands/webapp/dev.test.ts b/test/commands/webapp/dev.test.ts index 3a0fcbd..28bb0ad 100644 --- a/test/commands/webapp/dev.test.ts +++ b/test/commands/webapp/dev.test.ts @@ -15,6 +15,7 @@ */ import { expect } from 'chai'; +import sinon from 'sinon'; import { TestContext } from '@salesforce/core/testSetup'; import type { WebAppManifest, WebAppDevResult } from '../../../src/config/types.js'; @@ -25,10 +26,157 @@ describe('webapp:dev command integration', () => { $$.restore(); }); + describe('Vite Proxy Detection', () => { + let fetchStub: sinon.SinonStub; + + beforeEach(() => { + fetchStub = sinon.stub(global, 'fetch'); + }); + + afterEach(() => { + fetchStub.restore(); + }); + + /** + * Helper function that mirrors the checkViteProxyActive logic from dev.ts + * This allows us to test the detection behavior without needing to run the full command + */ + async function checkViteProxyActive(devServerUrl: string): Promise { + try { + const healthUrl = new URL(devServerUrl); + healthUrl.searchParams.set('sfProxyHealthCheck', 'true'); + const response = await fetch(healthUrl.toString(), { + method: 'GET', + signal: AbortSignal.timeout(3000), + }); + return response.headers.get('X-Salesforce-WebApp-Proxy') === 'true'; + } catch { + return false; + } + } + + it('should return true when X-Salesforce-WebApp-Proxy header is present and true', async () => { + const mockHeaders = new Headers(); + mockHeaders.set('X-Salesforce-WebApp-Proxy', 'true'); + + fetchStub.resolves({ + ok: true, + headers: mockHeaders, + } as Response); + + const result = await checkViteProxyActive('http://localhost:5173'); + + expect(result).to.be.true; + expect(fetchStub.calledOnce).to.be.true; + + // Verify the correct URL with query parameter was called + const calledUrl = fetchStub.firstCall.args[0] as string; + expect(calledUrl).to.include('sfProxyHealthCheck=true'); + }); + + it('should return false when X-Salesforce-WebApp-Proxy header is not present', async () => { + const mockHeaders = new Headers(); + // No X-Salesforce-WebApp-Proxy header + + fetchStub.resolves({ + ok: true, + headers: mockHeaders, + } as Response); + + const result = await checkViteProxyActive('http://localhost:5173'); + + expect(result).to.be.false; + }); + + it('should return false when X-Salesforce-WebApp-Proxy header is present but not "true"', async () => { + const mockHeaders = new Headers(); + mockHeaders.set('X-Salesforce-WebApp-Proxy', 'false'); + + fetchStub.resolves({ + ok: true, + headers: mockHeaders, + } as Response); + + const result = await checkViteProxyActive('http://localhost:5173'); + + expect(result).to.be.false; + }); + + it('should return false when fetch throws an error (network failure)', async () => { + fetchStub.rejects(new Error('Network error')); + + const result = await checkViteProxyActive('http://localhost:5173'); + + expect(result).to.be.false; + }); + + it('should return false when fetch times out', async () => { + fetchStub.rejects(new DOMException('The operation was aborted', 'AbortError')); + + const result = await checkViteProxyActive('http://localhost:5173'); + + expect(result).to.be.false; + }); + + it('should return false when dev server is not reachable (connection refused)', async () => { + fetchStub.rejects(new TypeError('Failed to fetch')); + + const result = await checkViteProxyActive('http://localhost:5173'); + + expect(result).to.be.false; + }); + + it('should construct correct health check URL with query parameter', async () => { + const mockHeaders = new Headers(); + mockHeaders.set('X-Salesforce-WebApp-Proxy', 'true'); + + fetchStub.resolves({ + ok: true, + headers: mockHeaders, + } as Response); + + await checkViteProxyActive('http://localhost:5173'); + + const calledUrl = fetchStub.firstCall.args[0] as string; + expect(calledUrl).to.equal('http://localhost:5173/?sfProxyHealthCheck=true'); + }); + + it('should preserve existing query parameters when adding health check', async () => { + const mockHeaders = new Headers(); + mockHeaders.set('X-Salesforce-WebApp-Proxy', 'true'); + + fetchStub.resolves({ + ok: true, + headers: mockHeaders, + } as Response); + + await checkViteProxyActive('http://localhost:5173/?existing=param'); + + const calledUrl = fetchStub.firstCall.args[0] as string; + expect(calledUrl).to.include('existing=param'); + expect(calledUrl).to.include('sfProxyHealthCheck=true'); + }); + + it('should use GET method for health check request', async () => { + const mockHeaders = new Headers(); + fetchStub.resolves({ + ok: true, + headers: mockHeaders, + } as Response); + + await checkViteProxyActive('http://localhost:5173'); + + const options = fetchStub.firstCall.args[1] as RequestInit; + expect(options.method).to.equal('GET'); + }); + }); + describe('Type Definitions', () => { it('should have correct WebAppManifest structure', () => { const manifest: WebAppManifest = { name: 'testWebApp', + label: 'Test Web App', + version: '1.0.0', outputDir: 'dist', dev: { url: 'http://localhost:5173', @@ -62,6 +210,8 @@ describe('webapp:dev command integration', () => { it('should use manifest dev.url when no explicit URL', () => { const manifest: WebAppManifest = { name: 'testWebApp', + label: 'Test Web App', + version: '1.0.0', outputDir: 'dist', dev: { url: 'http://localhost:5173', @@ -74,6 +224,8 @@ describe('webapp:dev command integration', () => { it('should use dev.command when no URL provided', () => { const manifest: WebAppManifest = { name: 'testWebApp', + label: 'Test Web App', + version: '1.0.0', outputDir: 'dist', dev: { command: 'npm run dev', @@ -88,6 +240,8 @@ describe('webapp:dev command integration', () => { it('should validate manifest with dev.url', () => { const manifest: WebAppManifest = { name: 'testWebApp', + label: 'Test Web App', + version: '1.0.0', outputDir: 'dist', dev: { url: 'http://localhost:5173', @@ -96,12 +250,15 @@ describe('webapp:dev command integration', () => { // Basic validation expect(manifest.name).to.be.a('string'); + expect(manifest.version).to.match(/^\d+\.\d+\.\d+$/); expect(manifest.dev?.url).to.include('http'); }); it('should validate manifest with dev.command', () => { const manifest: WebAppManifest = { name: 'testWebApp', + label: 'Test Web App', + version: '1.0.0', outputDir: 'dist', dev: { command: 'npm run dev', diff --git a/test/config/ManifestWatcher.test.ts b/test/config/ManifestWatcher.test.ts index 16d35a3..d8d7533 100644 --- a/test/config/ManifestWatcher.test.ts +++ b/test/config/ManifestWatcher.test.ts @@ -29,6 +29,8 @@ describe('ManifestWatcher', () => { const validManifest: WebAppManifest = { name: 'testApp', + label: 'Test Application', + version: '1.0.0', outputDir: 'dist', dev: { command: 'npm run dev', @@ -142,7 +144,7 @@ describe('ManifestWatcher', () => { }); describe('Partial Manifest Support', () => { - it('should accept manifest with only dev.command (no name, outputDir)', async () => { + it('should accept manifest with only dev.command (no name, label, version, outputDir)', async () => { const partialManifest = { dev: { command: 'npm run dev', @@ -158,6 +160,7 @@ describe('ManifestWatcher', () => { expect(manifest).to.exist; expect(manifest?.dev?.command).to.equal('npm run dev'); expect(manifest?.name).to.be.undefined; + expect(manifest?.label).to.be.undefined; await watcher.stop(); }); @@ -213,6 +216,8 @@ describe('ManifestWatcher', () => { const manifest = watcher.getManifest(); expect(manifest).to.exist; expect(manifest?.name).to.equal('myApp'); + expect(manifest?.label).to.be.undefined; + expect(manifest?.version).to.be.undefined; expect(manifest?.outputDir).to.be.undefined; await watcher.stop(); @@ -221,6 +226,8 @@ describe('ManifestWatcher', () => { it('should accept manifest without optional dev config', async () => { const minimalManifest = { name: 'testApp', + label: 'Test App', + version: '1.0.0', outputDir: 'dist', }; @@ -271,13 +278,13 @@ describe('ManifestWatcher', () => { // Wait a bit then modify the file setTimeout(() => { - const updated = { ...validManifest, name: 'updatedApp' }; + const updated = { ...validManifest, version: '2.0.0' }; writeFileSync(testManifestPath, JSON.stringify(updated, null, 2)); }, 200); const event = await changePromise; expect(event.type).to.equal('changed'); - expect(event.manifest?.name).to.equal('updatedApp'); + expect(event.manifest?.version).to.equal('2.0.0'); await watcher.stop(); }); @@ -348,22 +355,22 @@ describe('ManifestWatcher', () => { // Make multiple rapid changes setTimeout(() => { - writeFileSync(testManifestPath, JSON.stringify({ ...validManifest, name: 'change1' }, null, 2)); + writeFileSync(testManifestPath, JSON.stringify({ ...validManifest, version: '1.0.1' }, null, 2)); }, 100); setTimeout(() => { - writeFileSync(testManifestPath, JSON.stringify({ ...validManifest, name: 'change2' }, null, 2)); + writeFileSync(testManifestPath, JSON.stringify({ ...validManifest, version: '1.0.2' }, null, 2)); }, 150); setTimeout(() => { - writeFileSync(testManifestPath, JSON.stringify({ ...validManifest, name: 'change3' }, null, 2)); + writeFileSync(testManifestPath, JSON.stringify({ ...validManifest, version: '1.0.3' }, null, 2)); }, 200); // Check that only one change event was emitted after debounce await new Promise((resolve) => setTimeout(resolve, 800)); expect(changeCount).to.equal(1); - expect(watcher.getManifest()?.name).to.equal('change3'); + expect(watcher.getManifest()?.version).to.equal('1.0.3'); await watcher.stop(); }); }); @@ -418,12 +425,12 @@ describe('ManifestWatcher', () => { eventEmitted = true; }); - writeFileSync(testManifestPath, JSON.stringify({ ...validManifest, name: 'changedApp' }, null, 2)); + writeFileSync(testManifestPath, JSON.stringify({ ...validManifest, version: '2.0.0' }, null, 2)); await new Promise((resolve) => setTimeout(resolve, 300)); expect(eventEmitted).to.be.false; - expect(watcher.getManifest()?.name).to.equal('testApp'); // Still old value + expect(watcher.getManifest()?.version).to.equal('1.0.0'); // Still old version await watcher.stop(); }); diff --git a/test/config/types.test.ts b/test/config/types.test.ts index 19bdcdc..85528a8 100644 --- a/test/config/types.test.ts +++ b/test/config/types.test.ts @@ -21,16 +21,20 @@ describe('TypeScript Types', () => { it('should allow valid WebAppManifest', () => { const manifest: WebAppManifest = { name: 'testApp', + label: 'Test Application', + version: '1.0.0', outputDir: 'dist', }; expect(manifest.name).to.equal('testApp'); - expect(manifest.outputDir).to.equal('dist'); + expect(manifest.version).to.equal('1.0.0'); }); it('should allow WebAppManifest with dev config', () => { const manifest: WebAppManifest = { name: 'testApp', + label: 'Test Application', + version: '1.0.0', outputDir: 'dist', dev: { command: 'npm run dev', @@ -42,6 +46,18 @@ describe('TypeScript Types', () => { expect(manifest.dev?.url).to.equal('http://localhost:5173'); }); + it('should allow optional description', () => { + const manifest: WebAppManifest = { + name: 'testApp', + label: 'Test Application', + description: 'This is a test app', + version: '1.0.0', + outputDir: 'dist', + }; + + expect(manifest.description).to.equal('This is a test app'); + }); + it('should allow WebAppManifest with routing config', () => { const routing: RoutingConfig = { rewrites: [{ route: '/api/:id', target: 'api/handler' }], @@ -52,6 +68,8 @@ describe('TypeScript Types', () => { const manifest: WebAppManifest = { name: 'testApp', + label: 'Test Application', + version: '1.0.0', outputDir: 'dist', routing, }; diff --git a/test/proxy/ProxyServer.test.ts b/test/proxy/ProxyServer.test.ts index 0569cdb..916b61f 100644 --- a/test/proxy/ProxyServer.test.ts +++ b/test/proxy/ProxyServer.test.ts @@ -122,6 +122,8 @@ describe('ProxyServer', () => { salesforceInstanceUrl: 'https://test.salesforce.com', manifest: { name: 'test-app', + label: 'Test App', + version: '1.0.0', outputDir: 'dist', }, }); @@ -225,6 +227,8 @@ describe('ProxyServer', () => { salesforceInstanceUrl: 'https://test.salesforce.com', manifest: { name: 'test-app', + label: 'Test App', + version: '1.0.0', outputDir: 'dist', }, }); @@ -232,6 +236,8 @@ describe('ProxyServer', () => { // Update manifest with routing config proxy.updateManifest({ name: 'test-app', + label: 'Test App', + version: '2.0.0', outputDir: 'dist', routing: { trailingSlash: 'always', diff --git a/yarn.lock b/yarn.lock index 47bcbd0..7d2c94f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1739,10 +1739,10 @@ dependencies: "@salesforce/ts-types" "^2.0.12" -"@salesforce/plugin-command-reference@^3.1.79": - version "3.1.79" - resolved "https://registry.yarnpkg.com/@salesforce/plugin-command-reference/-/plugin-command-reference-3.1.79.tgz#36f1ea069d134ee45489882e28cc9a9bf6db6709" - integrity sha512-t3DH+Ez2ESrY8M8zO6yEodsFq7IYKBseFFRwqFxTf0bIVZcWh4i7UO8oUFQyT9Px+Q3TcnSXw4X9njopxTc2lQ== +"@salesforce/plugin-command-reference@^3.1.77": + version "3.1.78" + resolved "https://registry.npmjs.org/@salesforce/plugin-command-reference/-/plugin-command-reference-3.1.78.tgz" + integrity sha512-8GhAUhYTDD51pAFN6h/wrhD46ZJEs53EMDa//jNIe06nFxs/A2iheJSaVNyf9p+ft1KGTvhg+5WXQBFW5daFsA== dependencies: "@oclif/core" "^4" "@salesforce/core" "^8.23.3" @@ -1758,19 +1758,6 @@ resolved "https://registry.npmjs.org/@salesforce/prettier-config/-/prettier-config-0.0.3.tgz" integrity sha512-hYOhoPTCSYMDYn+U1rlEk16PoBeAJPkrdg4/UtAzupM1mRRJOwEPMG1d7U8DxJFKuXW3DMEYWr2MwAIBDaHmFg== -"@salesforce/sdk-core@^1.22.0": - version "1.22.0" - resolved "https://registry.yarnpkg.com/@salesforce/sdk-core/-/sdk-core-1.22.0.tgz#6488c2a64954ef554253f7d6293239d3e3ba9e61" - integrity sha512-L3GT267pg8iRJFXLUg+DVjn76UgJSwexXhWsAV5WDiLEkXlEwKdGFmpmKYbDx9M9sUN3NckiYw+trWGRjUEHNw== - -"@salesforce/sdk-data@^1.22.0": - version "1.22.0" - resolved "https://registry.yarnpkg.com/@salesforce/sdk-data/-/sdk-data-1.22.0.tgz#2dbf26f8b29f4bcc56aaf070baa74dbe64d02cd6" - integrity sha512-KH5RcQfyXj0jjvpI7gv54+e7qhiOBZ+XjuBA6UsOuk4bRvRfvqtwxCl1qSTLTU6iEoburudq6ixu1n7A6MOG+g== - dependencies: - "@conduit-client/salesforce-lightning-service-worker" "^3.7.0" - "@salesforce/sdk-core" "^1.22.0" - "@salesforce/sf-plugins-core@^11.3.12": version "11.3.12" resolved "https://registry.npmjs.org/@salesforce/sf-plugins-core/-/sf-plugins-core-11.3.12.tgz" @@ -1810,13 +1797,13 @@ resolved "https://registry.npmjs.org/@salesforce/ts-types/-/ts-types-2.0.12.tgz" integrity sha512-BIJyduJC18Kc8z+arUm5AZ9VkPRyw1KKAm+Tk+9LT99eOzhNilyfKzhZ4t+tG2lIGgnJpmytZfVDZ0e2kFul8g== -"@salesforce/webapp-experimental@^1.23.0": - version "1.23.0" - resolved "https://registry.yarnpkg.com/@salesforce/webapp-experimental/-/webapp-experimental-1.23.0.tgz#b95ebfebd3254361732e8edcfbdc56a8b819a948" - integrity sha512-5EKzZ6MFnCzmKdHSSt+28riAIgFQ+5PfPRQg3Gl0mnUA3GzN9XwzEq8hI/r52sDlKPvtb2x+MKAxyYs/182OEg== +"@salesforce/webapp-experimental@^0.2.0": + version "0.2.0" + resolved "https://registry.npmjs.org/@salesforce/webapp-experimental/-/webapp-experimental-0.2.0.tgz" + integrity sha512-+E7b8u88ABJcgj7YSYiwXaF+LY9lCElibtEQbfuBdDXSUXjaMImwdZjhuyUtndIYWSXM+X38kjnIAFiikjqBIQ== dependencies: + "@conduit-client/salesforce-lightning-service-worker" "^3.7.0" "@salesforce/core" "^8.23.4" - "@salesforce/sdk-data" "^1.22.0" axios "^1.7.7" micromatch "^4.0.8" path-to-regexp "^8.3.0" From f6e73044aaacf7f2ed380eb2a4b2090bec4b1ded Mon Sep 17 00:00:00 2001 From: Deepu Mungamuri Date: Fri, 13 Feb 2026 21:10:44 +0530 Subject: [PATCH 2/4] fix: remove label/version to match @salesforce/webapp-experimental 1.x type - Run yarn install to resolve 1.23.0+ (was 0.2.0 from stale lock) - ProxyServer: use minimal fallback { name, outputDir } per PR #22 - Tests: remove label/version from manifest fixtures - ManifestWatcher tests: fix assertions for simplified type Co-authored-by: Cursor --- src/proxy/ProxyServer.ts | 2 -- test/commands/webapp/dev.test.ts | 11 ----------- test/config/ManifestWatcher.test.ts | 14 ++++---------- test/config/types.test.ts | 20 +------------------- test/proxy/ProxyServer.test.ts | 6 ------ yarn.lock | 23 ++++++++++++++++++----- 6 files changed, 23 insertions(+), 53 deletions(-) diff --git a/src/proxy/ProxyServer.ts b/src/proxy/ProxyServer.ts index d184689..8e3f977 100644 --- a/src/proxy/ProxyServer.ts +++ b/src/proxy/ProxyServer.ts @@ -407,8 +407,6 @@ export class ProxyServer extends EventEmitter { private initializeProxyHandler(): void { const manifest: WebAppManifest = this.config.manifest ?? { name: 'webapp', - label: 'WebApp', - version: '1.0.0', outputDir: 'dist', }; diff --git a/test/commands/webapp/dev.test.ts b/test/commands/webapp/dev.test.ts index 28bb0ad..161b7f5 100644 --- a/test/commands/webapp/dev.test.ts +++ b/test/commands/webapp/dev.test.ts @@ -175,8 +175,6 @@ describe('webapp:dev command integration', () => { it('should have correct WebAppManifest structure', () => { const manifest: WebAppManifest = { name: 'testWebApp', - label: 'Test Web App', - version: '1.0.0', outputDir: 'dist', dev: { url: 'http://localhost:5173', @@ -210,8 +208,6 @@ describe('webapp:dev command integration', () => { it('should use manifest dev.url when no explicit URL', () => { const manifest: WebAppManifest = { name: 'testWebApp', - label: 'Test Web App', - version: '1.0.0', outputDir: 'dist', dev: { url: 'http://localhost:5173', @@ -224,8 +220,6 @@ describe('webapp:dev command integration', () => { it('should use dev.command when no URL provided', () => { const manifest: WebAppManifest = { name: 'testWebApp', - label: 'Test Web App', - version: '1.0.0', outputDir: 'dist', dev: { command: 'npm run dev', @@ -240,8 +234,6 @@ describe('webapp:dev command integration', () => { it('should validate manifest with dev.url', () => { const manifest: WebAppManifest = { name: 'testWebApp', - label: 'Test Web App', - version: '1.0.0', outputDir: 'dist', dev: { url: 'http://localhost:5173', @@ -250,15 +242,12 @@ describe('webapp:dev command integration', () => { // Basic validation expect(manifest.name).to.be.a('string'); - expect(manifest.version).to.match(/^\d+\.\d+\.\d+$/); expect(manifest.dev?.url).to.include('http'); }); it('should validate manifest with dev.command', () => { const manifest: WebAppManifest = { name: 'testWebApp', - label: 'Test Web App', - version: '1.0.0', outputDir: 'dist', dev: { command: 'npm run dev', diff --git a/test/config/ManifestWatcher.test.ts b/test/config/ManifestWatcher.test.ts index d8d7533..54d72a2 100644 --- a/test/config/ManifestWatcher.test.ts +++ b/test/config/ManifestWatcher.test.ts @@ -29,8 +29,6 @@ describe('ManifestWatcher', () => { const validManifest: WebAppManifest = { name: 'testApp', - label: 'Test Application', - version: '1.0.0', outputDir: 'dist', dev: { command: 'npm run dev', @@ -160,7 +158,6 @@ describe('ManifestWatcher', () => { expect(manifest).to.exist; expect(manifest?.dev?.command).to.equal('npm run dev'); expect(manifest?.name).to.be.undefined; - expect(manifest?.label).to.be.undefined; await watcher.stop(); }); @@ -216,8 +213,6 @@ describe('ManifestWatcher', () => { const manifest = watcher.getManifest(); expect(manifest).to.exist; expect(manifest?.name).to.equal('myApp'); - expect(manifest?.label).to.be.undefined; - expect(manifest?.version).to.be.undefined; expect(manifest?.outputDir).to.be.undefined; await watcher.stop(); @@ -226,8 +221,6 @@ describe('ManifestWatcher', () => { it('should accept manifest without optional dev config', async () => { const minimalManifest = { name: 'testApp', - label: 'Test App', - version: '1.0.0', outputDir: 'dist', }; @@ -284,7 +277,7 @@ describe('ManifestWatcher', () => { const event = await changePromise; expect(event.type).to.equal('changed'); - expect(event.manifest?.version).to.equal('2.0.0'); + expect((event.manifest as unknown as Record)?.version).to.equal('2.0.0'); await watcher.stop(); }); @@ -370,7 +363,7 @@ describe('ManifestWatcher', () => { await new Promise((resolve) => setTimeout(resolve, 800)); expect(changeCount).to.equal(1); - expect(watcher.getManifest()?.version).to.equal('1.0.3'); + expect((watcher.getManifest() as unknown as Record)?.version).to.equal('1.0.3'); await watcher.stop(); }); }); @@ -430,7 +423,8 @@ describe('ManifestWatcher', () => { await new Promise((resolve) => setTimeout(resolve, 300)); expect(eventEmitted).to.be.false; - expect(watcher.getManifest()?.version).to.equal('1.0.0'); // Still old version + // Watcher stopped before write, so manifest unchanged (validManifest has no version) + expect(watcher.getManifest()).to.deep.equal(validManifest); await watcher.stop(); }); diff --git a/test/config/types.test.ts b/test/config/types.test.ts index 85528a8..19bdcdc 100644 --- a/test/config/types.test.ts +++ b/test/config/types.test.ts @@ -21,20 +21,16 @@ describe('TypeScript Types', () => { it('should allow valid WebAppManifest', () => { const manifest: WebAppManifest = { name: 'testApp', - label: 'Test Application', - version: '1.0.0', outputDir: 'dist', }; expect(manifest.name).to.equal('testApp'); - expect(manifest.version).to.equal('1.0.0'); + expect(manifest.outputDir).to.equal('dist'); }); it('should allow WebAppManifest with dev config', () => { const manifest: WebAppManifest = { name: 'testApp', - label: 'Test Application', - version: '1.0.0', outputDir: 'dist', dev: { command: 'npm run dev', @@ -46,18 +42,6 @@ describe('TypeScript Types', () => { expect(manifest.dev?.url).to.equal('http://localhost:5173'); }); - it('should allow optional description', () => { - const manifest: WebAppManifest = { - name: 'testApp', - label: 'Test Application', - description: 'This is a test app', - version: '1.0.0', - outputDir: 'dist', - }; - - expect(manifest.description).to.equal('This is a test app'); - }); - it('should allow WebAppManifest with routing config', () => { const routing: RoutingConfig = { rewrites: [{ route: '/api/:id', target: 'api/handler' }], @@ -68,8 +52,6 @@ describe('TypeScript Types', () => { const manifest: WebAppManifest = { name: 'testApp', - label: 'Test Application', - version: '1.0.0', outputDir: 'dist', routing, }; diff --git a/test/proxy/ProxyServer.test.ts b/test/proxy/ProxyServer.test.ts index 916b61f..0569cdb 100644 --- a/test/proxy/ProxyServer.test.ts +++ b/test/proxy/ProxyServer.test.ts @@ -122,8 +122,6 @@ describe('ProxyServer', () => { salesforceInstanceUrl: 'https://test.salesforce.com', manifest: { name: 'test-app', - label: 'Test App', - version: '1.0.0', outputDir: 'dist', }, }); @@ -227,8 +225,6 @@ describe('ProxyServer', () => { salesforceInstanceUrl: 'https://test.salesforce.com', manifest: { name: 'test-app', - label: 'Test App', - version: '1.0.0', outputDir: 'dist', }, }); @@ -236,8 +232,6 @@ describe('ProxyServer', () => { // Update manifest with routing config proxy.updateManifest({ name: 'test-app', - label: 'Test App', - version: '2.0.0', outputDir: 'dist', routing: { trailingSlash: 'always', diff --git a/yarn.lock b/yarn.lock index 7d2c94f..f37d94e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1758,6 +1758,19 @@ resolved "https://registry.npmjs.org/@salesforce/prettier-config/-/prettier-config-0.0.3.tgz" integrity sha512-hYOhoPTCSYMDYn+U1rlEk16PoBeAJPkrdg4/UtAzupM1mRRJOwEPMG1d7U8DxJFKuXW3DMEYWr2MwAIBDaHmFg== +"@salesforce/sdk-core@^1.29.1": + version "1.29.1" + resolved "https://registry.yarnpkg.com/@salesforce/sdk-core/-/sdk-core-1.29.1.tgz#4679e9e2cc8c34fafb302610312f1f8eb249349e" + integrity sha512-Xc2Hh1yzV+vMj8+Ot4SjBIJgqU8OZJ51H3Hg6clKlzlzcuBzSTprHB4Cw252NWOGJ7UtG0rLGG8Q6F3qEg2SMQ== + +"@salesforce/sdk-data@^1.29.1": + version "1.29.1" + resolved "https://registry.yarnpkg.com/@salesforce/sdk-data/-/sdk-data-1.29.1.tgz#4a1ad906d597d9e0ffca4e8c2991e2e54b90db7d" + integrity sha512-8TjrB8GiEgMEnVveQahYFd+8ak5Dr5xJgj0DoIDgIkToc1t6UzPVZAP4D/XO+aydPnzYO1ZXSPbemYOa3ar+uA== + dependencies: + "@conduit-client/salesforce-lightning-service-worker" "^3.7.0" + "@salesforce/sdk-core" "^1.29.1" + "@salesforce/sf-plugins-core@^11.3.12": version "11.3.12" resolved "https://registry.npmjs.org/@salesforce/sf-plugins-core/-/sf-plugins-core-11.3.12.tgz" @@ -1797,13 +1810,13 @@ resolved "https://registry.npmjs.org/@salesforce/ts-types/-/ts-types-2.0.12.tgz" integrity sha512-BIJyduJC18Kc8z+arUm5AZ9VkPRyw1KKAm+Tk+9LT99eOzhNilyfKzhZ4t+tG2lIGgnJpmytZfVDZ0e2kFul8g== -"@salesforce/webapp-experimental@^0.2.0": - version "0.2.0" - resolved "https://registry.npmjs.org/@salesforce/webapp-experimental/-/webapp-experimental-0.2.0.tgz" - integrity sha512-+E7b8u88ABJcgj7YSYiwXaF+LY9lCElibtEQbfuBdDXSUXjaMImwdZjhuyUtndIYWSXM+X38kjnIAFiikjqBIQ== +"@salesforce/webapp-experimental@^1.23.0": + version "1.29.1" + resolved "https://registry.yarnpkg.com/@salesforce/webapp-experimental/-/webapp-experimental-1.29.1.tgz#98648cc166b34c1b479f9545e87d10b61dd76adc" + integrity sha512-i5ZzGs7hrjv3Fbrvmm7v8OTVoJRewWvqzXIB5JRW5l2mcz1HxK2DXriBbLHHOmYRcVSrNdN/VS8PW7YwcTuHZw== dependencies: - "@conduit-client/salesforce-lightning-service-worker" "^3.7.0" "@salesforce/core" "^8.23.4" + "@salesforce/sdk-data" "^1.29.1" axios "^1.7.7" micromatch "^4.0.8" path-to-regexp "^8.3.0" From 1fefb2897eac9e4e8b7d37ff74116cd33b4dba87 Mon Sep 17 00:00:00 2001 From: Deepu Mungamuri Date: Fri, 13 Feb 2026 21:35:15 +0530 Subject: [PATCH 3/4] fix: group ready-for-development messages, simplify Vite message format Co-authored-by: Cursor --- messages/webapp.dev.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/messages/webapp.dev.md b/messages/webapp.dev.md index f6e8f16..f0900ba 100644 --- a/messages/webapp.dev.md +++ b/messages/webapp.dev.md @@ -89,6 +89,11 @@ Proxy URL: %s (open this URL in your browser) ✅ Ready for development! → %s (open this URL in your browser) +# info.ready-for-development-vite + +✅ Ready for development! + → %s (Vite proxy active - open this URL in your browser) + # info.press-ctrl-c Press Ctrl+C to stop the proxy server @@ -186,8 +191,3 @@ The proxy will use the actual dev server URL. # info.vite-proxy-detected Vite WebApp proxy detected at %s - using Vite's built-in proxy (standalone proxy skipped) - -# info.ready-for-development-vite - -✅ Ready for development! - → Dev server: %s (Vite proxy active - open this URL in your browser) From c6b33f8232b66f92b1ba33d5be4476de981f9c75 Mon Sep 17 00:00:00 2001 From: Deepu Mungamuri Date: Fri, 13 Feb 2026 21:38:04 +0530 Subject: [PATCH 4/4] fix: update server-running message - remove bold, add quotes Co-authored-by: Cursor --- messages/webapp.dev.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/webapp.dev.md b/messages/webapp.dev.md index f0900ba..a27d832 100644 --- a/messages/webapp.dev.md +++ b/messages/webapp.dev.md @@ -100,7 +100,7 @@ Press Ctrl+C to stop the proxy server # info.server-running -Dev server is running. Stop it by running **SFDX: Close Live Preview** from the VS Code command palette. +Dev server is running. Stop it by running "SFDX: Close Live Preview" from the VS Code command palette. # info.dev-server-healthy