Skip to content

Commit 2e80525

Browse files
fix: address PR feedback and merge conflicts
- messages: specify 'proxy server' in Ctrl+C stop message - ProxyServer: add label and version to fallback WebAppManifest - ErrorPageRenderer: use local template (getErrorPageTemplate not in package) - tests: add label and version to WebAppManifest fixtures Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 9b9b08e commit 2e80525

8 files changed

Lines changed: 177 additions & 3 deletions

File tree

messages/webapp.dev.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ Proxy URL: %s (open this in your browser)
9090

9191
# info.press-ctrl-c
9292

93-
Press Ctrl+C to stop
93+
Press Ctrl+C to stop the proxy server
9494

9595
# info.dev-server-healthy
9696

src/proxy/ProxyServer.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,8 @@ export class ProxyServer extends EventEmitter {
412412
private initializeProxyHandler(): void {
413413
const manifest: WebAppManifest = this.config.manifest ?? {
414414
name: 'webapp',
415+
label: 'Web App',
416+
version: '1.0.0',
415417
outputDir: 'dist',
416418
};
417419

src/templates/ErrorPageRenderer.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { getErrorPageTemplate } from '@salesforce/webapp-experimental/proxy';
1817
import type { DevServerError } from '../config/types.js';
18+
import { ERROR_PAGE_TEMPLATE } from './error-page-template.js';
1919

2020
export type ErrorPageData = {
2121
status: string;
@@ -35,7 +35,7 @@ export class ErrorPageRenderer {
3535
private template: string;
3636

3737
public constructor() {
38-
this.template = getErrorPageTemplate();
38+
this.template = ERROR_PAGE_TEMPLATE;
3939
}
4040

4141
/**
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/*
2+
* Copyright 2026, Salesforce, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/**
18+
* HTML template for dev server error pages.
19+
* Placeholders: {{PAGE_TITLE}}, {{META_REFRESH}}, {{ERROR_TITLE}}, {{STATUS_CLASS}},
20+
* {{ERROR_STATUS}}, {{MESSAGE_CONTENT}}, {{ERROR_MESSAGE_TEXT}}, {{STDERR_OUTPUT}},
21+
* {{SUGGESTIONS_TITLE}}, {{SUGGESTIONS_LIST}}, {{DEV_SERVER_URL}}, {{PROXY_URL}},
22+
* {{PROXY_PORT}}, {{ORG_TARGET}}, {{WORKSPACE_SCRIPT}}, {{LAST_CHECK_TIME}},
23+
* {{SIMPLE_SECTION_CLASS}}, {{RUNTIME_SECTION_CLASS}}, {{DEV_SERVER_SECTION_CLASS}},
24+
* {{SUGGESTIONS_SECTION_CLASS}}, {{AUTO_REFRESH_CLASS}}, {{AUTO_REFRESH_TEXT}}
25+
*/
26+
export const ERROR_PAGE_TEMPLATE = `<!DOCTYPE html>
27+
<html lang="en">
28+
<head>
29+
<meta charset="UTF-8" />
30+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
31+
<title>{{PAGE_TITLE}} - Salesforce Local Dev Proxy</title>
32+
{{META_REFRESH}}
33+
<style>
34+
* { margin: 0; padding: 0; box-sizing: border-box; }
35+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: linear-gradient(135deg, #1e2a47 0%, #2d3e5f 100%); color: #333; min-height: 100vh; padding: 20px; }
36+
.main-container { max-width: 1400px; margin: 0 auto; background: #e8e8e8; border-radius: 16px; box-shadow: 0 10px 40px rgba(0,0,0,0.3); overflow: hidden; }
37+
.top-header { background: #e8e8e8; padding: 30px 40px; display: flex; justify-content: space-between; align-items: flex-start; border-bottom: 1px solid #ccc; }
38+
.header-left h1 { color: #1a1a1a; font-size: 2.2em; font-weight: 600; margin-bottom: 8px; }
39+
.header-left .subtitle { color: #666; font-size: 1.05em; }
40+
.status-badge { padding: 12px 24px; border-radius: 8px; font-weight: 600; font-size: 0.95em; }
41+
.status-badge.warning { background: #fff3e0; color: #e65100; }
42+
.status-badge.error { background: #ffebee; color: #c62828; }
43+
.content-wrapper { display: grid; grid-template-columns: 1fr 308px; gap: 0; background: #e8e8e8; }
44+
.main-content { padding: 40px; background: white; border-right: 1px solid #ccc; }
45+
.diagnostics-panel { padding: 40px 20px; background: #f5f5f5; }
46+
.content-section { margin-bottom: 30px; }
47+
.content-section h2 { color: #1a1a1a; font-size: 1.4em; margin-bottom: 15px; font-weight: 600; }
48+
.content-section h3 { color: #1a1a1a; font-size: 1.1em; margin-bottom: 12px; font-weight: 600; }
49+
.content-section p { color: #555; line-height: 1.6; margin-bottom: 15px; }
50+
.content-section ul { list-style: disc; padding-left: 25px; color: #555; line-height: 1.8; }
51+
.message-box { background: #fff3e0; border-left: 4px solid #ff9800; padding: 20px; border-radius: 6px; margin-bottom: 25px; }
52+
.message-box p { color: #555; margin: 0; }
53+
.code-output { background: #1e1e1e; border-radius: 8px; padding: 20px; max-height: 400px; overflow-y: auto; font-family: monospace; font-size: 0.9em; line-height: 1.5; margin: 20px 0; }
54+
.code-output pre { color: #ff6b6b; margin: 0; white-space: pre-wrap; word-wrap: break-word; }
55+
.suggestions-box { background: #e3f2fd; border-left: 4px solid #2196f3; padding: 20px; border-radius: 6px; margin: 20px 0; }
56+
.suggestions-box h3 { color: #1a1a1a; font-size: 1.1em; margin-bottom: 15px; }
57+
.suggestions-box ul { list-style: none; padding: 0; }
58+
.suggestions-box li { padding: 8px 0; color: #555; line-height: 1.6; }
59+
.diagnostics-panel h2 { color: #1a1a1a; font-size: 1.15em; margin-bottom: 18px; font-weight: 600; }
60+
.diagnostics-list { list-style: none; padding: 0; margin-bottom: 20px; }
61+
.diagnostics-list li { margin-bottom: 12px; color: #555; line-height: 1.4; font-size: 0.9em; }
62+
.diagnostics-list .label { display: block; font-weight: 600; color: #1a1a1a; margin-bottom: 3px; font-size: 0.85em; }
63+
.diagnostics-list .value { font-family: monospace; font-size: 0.85em; color: #666; word-break: break-word; }
64+
.diagnostics-list .value code { background: #263238; color: #aed581; padding: 2px 5px; border-radius: 3px; font-size: 0.8em; }
65+
.emergency-commands { background: #fff3cd; border: 1px solid #ffc107; border-radius: 4px; padding: 15px; margin-top: 20px; }
66+
.emergency-commands h3 { color: #856404; font-size: 0.8em; margin: 0 0 8px 0; font-weight: 600; }
67+
.emergency-commands p { color: #856404; font-size: 0.75em; margin: 0 0 8px 0; }
68+
.command-box { background: #263238; color: #aed581; padding: 10px; border-radius: 4px; font-family: monospace; font-size: 0.72em; word-break: break-all; }
69+
.footer-section { background: #e8e8e8; padding: 20px 40px; border-top: 1px solid #ccc; text-align: center; }
70+
.help-text { color: #666; font-size: 0.9em; line-height: 1.5; }
71+
.help-text strong { color: #333; }
72+
.hidden { display: none; }
73+
.auto-refresh-indicator { background: #e8f5e9; border-left: 4px solid #4caf50; padding: 12px 16px; border-radius: 6px; margin: 20px 0; color: #2e7d32; font-size: 0.9em; }
74+
@media (max-width: 900px) { .content-wrapper { grid-template-columns: 1fr; } .main-content { border-right: none; border-bottom: 1px solid #ccc; } }
75+
</style>
76+
</head>
77+
<body>
78+
<div class="main-container">
79+
<div class="top-header">
80+
<div class="header-left">
81+
<h1>Local Dev Proxy</h1>
82+
<p class="subtitle">Salesforce preview → Proxy → Your dev server</p>
83+
</div>
84+
<div class="status-badge {{STATUS_CLASS}}">{{ERROR_STATUS}}</div>
85+
</div>
86+
<div class="content-wrapper">
87+
<div class="main-content">
88+
<div id="simple-error-section" class="{{SIMPLE_SECTION_CLASS}}">
89+
<div class="content-section">
90+
<h2>{{ERROR_TITLE}}</h2>
91+
{{MESSAGE_CONTENT}}
92+
</div>
93+
<div class="auto-refresh-indicator {{AUTO_REFRESH_CLASS}}">{{AUTO_REFRESH_TEXT}}</div>
94+
<div class="suggestions-box">
95+
<h3>What to do next</h3>
96+
<ul>
97+
<li>Start your dev server using: <code>npm run dev</code> or <code>yarn dev</code></li>
98+
<li>Verify your dev server is running on the correct port</li>
99+
<li>Check webapplication.json for the correct dev server URL</li>
100+
<li>This page will auto-refresh when the server is detected</li>
101+
</ul>
102+
</div>
103+
</div>
104+
<div id="runtime-error-section" class="{{RUNTIME_SECTION_CLASS}}"></div>
105+
<div id="dev-server-error-section" class="{{DEV_SERVER_SECTION_CLASS}}">
106+
<div class="content-section">
107+
<h2>⚠️ {{ERROR_TITLE}}</h2>
108+
<div class="message-box">
109+
<p>{{ERROR_MESSAGE_TEXT}}</p>
110+
</div>
111+
</div>
112+
<div class="content-section">
113+
<h3>Error Output</h3>
114+
<div class="code-output">
115+
<pre>{{STDERR_OUTPUT}}</pre>
116+
</div>
117+
</div>
118+
<div class="auto-refresh-indicator {{AUTO_REFRESH_CLASS}}">{{AUTO_REFRESH_TEXT}}</div>
119+
<div class="suggestions-box {{SUGGESTIONS_SECTION_CLASS}}">
120+
<h3>{{SUGGESTIONS_TITLE}}</h3>
121+
<ul>
122+
{{SUGGESTIONS_LIST}}
123+
</ul>
124+
</div>
125+
</div>
126+
</div>
127+
<div class="diagnostics-panel">
128+
<h2>Diagnostics</h2>
129+
<ul class="diagnostics-list">
130+
<li class="{{SIMPLE_SECTION_CLASS}}"><span class="label">Dev Server URL:</span><span class="value"><code>{{DEV_SERVER_URL}}</code></span></li>
131+
<li class="{{SIMPLE_SECTION_CLASS}}"><span class="label">Proxy URL:</span><span class="value"><code>{{PROXY_URL}}</code></span></li>
132+
<li class="{{SIMPLE_SECTION_CLASS}}"><span class="label">Workspace Script:</span><span class="value"><code>{{WORKSPACE_SCRIPT}}</code></span></li>
133+
<li class="{{SIMPLE_SECTION_CLASS}}"><span class="label">Target Org:</span><span class="value">{{ORG_TARGET}}</span></li>
134+
<li class="{{SIMPLE_SECTION_CLASS}}"><span class="label">Last Check:</span><span class="value">{{LAST_CHECK_TIME}}</span></li>
135+
</ul>
136+
<div class="emergency-commands">
137+
<h3>⚠️ If Ctrl+C doesn't work</h3>
138+
<p>Copy and run this command in a new terminal to force-stop the proxy:</p>
139+
<div class="command-box">lsof -ti:{{PROXY_PORT}} | xargs kill -9</div>
140+
</div>
141+
</div>
142+
</div>
143+
<div class="footer-section">
144+
<p class="help-text">Salesforce Web App Development Proxy &nbsp;•&nbsp; Press <strong>Ctrl+C</strong> in the terminal to stop the proxy</p>
145+
</div>
146+
</div>
147+
</body>
148+
</html>`;

test/commands/webapp/dev.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ describe('webapp:dev command integration', () => {
2929
it('should have correct WebAppManifest structure', () => {
3030
const manifest: WebAppManifest = {
3131
name: 'testWebApp',
32+
label: 'Test Web App',
33+
version: '1.0.0',
3234
outputDir: 'dist',
3335
dev: {
3436
url: 'http://localhost:5173',
@@ -62,6 +64,8 @@ describe('webapp:dev command integration', () => {
6264
it('should use manifest dev.url when no explicit URL', () => {
6365
const manifest: WebAppManifest = {
6466
name: 'testWebApp',
67+
label: 'Test Web App',
68+
version: '1.0.0',
6569
outputDir: 'dist',
6670
dev: {
6771
url: 'http://localhost:5173',
@@ -74,6 +78,8 @@ describe('webapp:dev command integration', () => {
7478
it('should use dev.command when no URL provided', () => {
7579
const manifest: WebAppManifest = {
7680
name: 'testWebApp',
81+
label: 'Test Web App',
82+
version: '1.0.0',
7783
outputDir: 'dist',
7884
dev: {
7985
command: 'npm run dev',
@@ -88,6 +94,8 @@ describe('webapp:dev command integration', () => {
8894
it('should validate manifest with dev.url', () => {
8995
const manifest: WebAppManifest = {
9096
name: 'testWebApp',
97+
label: 'Test Web App',
98+
version: '1.0.0',
9199
outputDir: 'dist',
92100
dev: {
93101
url: 'http://localhost:5173',
@@ -102,6 +110,8 @@ describe('webapp:dev command integration', () => {
102110
it('should validate manifest with dev.command', () => {
103111
const manifest: WebAppManifest = {
104112
name: 'testWebApp',
113+
label: 'Test Web App',
114+
version: '1.0.0',
105115
outputDir: 'dist',
106116
dev: {
107117
command: 'npm run dev',

test/config/ManifestWatcher.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ describe('ManifestWatcher', () => {
2929

3030
const validManifest: WebAppManifest = {
3131
name: 'testApp',
32+
label: 'Test App',
33+
version: '1.0.0',
3234
outputDir: 'dist',
3335
dev: {
3436
command: 'npm run dev',

test/config/types.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ describe('TypeScript Types', () => {
2121
it('should allow valid WebAppManifest', () => {
2222
const manifest: WebAppManifest = {
2323
name: 'testApp',
24+
label: 'Test App',
25+
version: '1.0.0',
2426
outputDir: 'dist',
2527
};
2628

@@ -31,6 +33,8 @@ describe('TypeScript Types', () => {
3133
it('should allow WebAppManifest with dev config', () => {
3234
const manifest: WebAppManifest = {
3335
name: 'testApp',
36+
label: 'Test App',
37+
version: '1.0.0',
3438
outputDir: 'dist',
3539
dev: {
3640
command: 'npm run dev',
@@ -52,6 +56,8 @@ describe('TypeScript Types', () => {
5256

5357
const manifest: WebAppManifest = {
5458
name: 'testApp',
59+
label: 'Test App',
60+
version: '1.0.0',
5561
outputDir: 'dist',
5662
routing,
5763
};

test/proxy/ProxyServer.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ describe('ProxyServer', () => {
122122
salesforceInstanceUrl: 'https://test.salesforce.com',
123123
manifest: {
124124
name: 'test-app',
125+
label: 'Test App',
126+
version: '1.0.0',
125127
outputDir: 'dist',
126128
},
127129
});
@@ -225,13 +227,17 @@ describe('ProxyServer', () => {
225227
salesforceInstanceUrl: 'https://test.salesforce.com',
226228
manifest: {
227229
name: 'test-app',
230+
label: 'Test App',
231+
version: '1.0.0',
228232
outputDir: 'dist',
229233
},
230234
});
231235

232236
// Update manifest with routing config
233237
proxy.updateManifest({
234238
name: 'test-app',
239+
label: 'Test App',
240+
version: '1.0.0',
235241
outputDir: 'dist',
236242
routing: {
237243
trailingSlash: 'always',

0 commit comments

Comments
 (0)