Skip to content

Commit 94b85a6

Browse files
committed
feat: update webapp dev command with target, root-dir, host, and no-open flags
1 parent c2a536e commit 94b85a6

6 files changed

Lines changed: 209 additions & 36 deletions

File tree

command-snapshot.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
"alias": [],
1212
"command": "webapp:dev",
1313
"flagAliases": [],
14-
"flagChars": ["n", "p"],
15-
"flags": ["flags-dir", "json", "name", "port"],
14+
"flagChars": ["n", "p", "r", "t"],
15+
"flags": ["flags-dir", "host", "json", "name", "no-open", "port", "root-dir", "target"],
1616
"plugin": "@salesforce/plugin-webapp"
1717
},
1818
{

messages/webapp.dev.md

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,50 @@ Preview a web app locally without needing to deploy
44

55
# description
66

7-
Start a local development server to preview your web app without deploying to Salesforce. This enables rapid development with hot reloading and immediate feedback.
7+
Starts a local development server for a Web Application defined in CMS, using the local project files. This enables rapid development with hot reloading and immediate feedback. The command resolves the Web Application metadata, reads its configuration (targets, routes, etc.), and derives the default local path if available.
88

99
# flags.name.summary
1010

11-
Name of your web app
11+
Identifies the Web Application (CMS MD) to use
12+
13+
# flags.target.summary
14+
15+
Selects which Web Application target to use for the preview (e.g., Lightning App, Site)
16+
17+
# flags.root-dir.summary
18+
19+
Optional override for the local project root of this Web Application
1220

1321
# flags.port.summary
1422

15-
Port number for the development server
23+
Port for the dev server
24+
25+
# flags.host.summary
26+
27+
Host to bind to
28+
29+
# flags.no-open.summary
30+
31+
Do not automatically open the browser
1632

1733
# examples
1834

1935
- Start the development server:
2036

21-
<%= config.bin %> <%= command.id %>
37+
<%= config.bin %> <%= command.id %> --name myWebApp
38+
39+
- Start the development server with a specific target:
2240

23-
- Start the development server for a specific web app:
41+
<%= config.bin %> <%= command.id %> --name myWebApp --target "LightningApp"
2442

25-
<%= config.bin %> <%= command.id %> --name myWebApp
43+
- Start the development server on a custom port and host:
44+
45+
<%= config.bin %> <%= command.id %> --name myWebApp --port 8080 --host 0.0.0.0
46+
47+
- Start the development server with custom root directory:
48+
49+
<%= config.bin %> <%= command.id %> --name myWebApp --root-dir ./webapps/myWebApp
2650

27-
- Start the development server on a custom port:
51+
- Start the development server without opening the browser:
2852

29-
<%= config.bin %> <%= command.id %> --name myWebApp --port 8080
53+
<%= config.bin %> <%= command.id %> --name myWebApp --no-open

schemas/webapp-dev.json

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,26 @@
88
"name": {
99
"type": "string"
1010
},
11+
"target": {
12+
"type": "string"
13+
},
14+
"rootDir": {
15+
"type": "string"
16+
},
1117
"port": {
1218
"type": "number"
1319
},
20+
"host": {
21+
"type": "string"
22+
},
23+
"noOpen": {
24+
"type": "boolean"
25+
},
1426
"success": {
1527
"type": "boolean"
1628
}
1729
},
18-
"required": ["port", "success"],
30+
"required": ["name", "port", "host", "noOpen", "success"],
1931
"additionalProperties": false
2032
}
2133
}

src/commands/webapp/dev.ts

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,12 @@ Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
2121
const messages = Messages.loadMessages('@salesforce/plugin-webapp', 'webapp.dev');
2222

2323
export type WebappDevResult = {
24-
name?: string;
24+
name: string;
25+
target?: string;
26+
rootDir?: string;
2527
port: number;
28+
host: string;
29+
noOpen: boolean;
2630
success: boolean;
2731
};
2832

@@ -35,37 +39,71 @@ export default class WebappDev extends SfCommand<WebappDevResult> {
3539
name: Flags.string({
3640
summary: messages.getMessage('flags.name.summary'),
3741
char: 'n',
42+
required: true,
43+
}),
44+
target: Flags.string({
45+
summary: messages.getMessage('flags.target.summary'),
46+
char: 't',
47+
required: false,
48+
}),
49+
'root-dir': Flags.string({
50+
summary: messages.getMessage('flags.root-dir.summary'),
51+
char: 'r',
3852
required: false,
3953
}),
4054
port: Flags.integer({
4155
summary: messages.getMessage('flags.port.summary'),
4256
char: 'p',
43-
default: 3000,
57+
default: 8080,
58+
}),
59+
host: Flags.string({
60+
summary: messages.getMessage('flags.host.summary'),
61+
default: 'localhost',
62+
}),
63+
'no-open': Flags.boolean({
64+
summary: messages.getMessage('flags.no-open.summary'),
65+
default: false,
4466
}),
4567
};
4668

4769
public async run(): Promise<WebappDevResult> {
4870
const { flags } = await this.parse(WebappDev);
4971

50-
if (flags.name) {
51-
this.log(`Starting development server for web app: ${flags.name}`);
72+
this.log(`Starting development server for web app: ${flags.name}`);
73+
74+
if (flags.target) {
75+
this.log(`Using target: ${flags.target}`);
5276
} else {
53-
this.log('Starting development server for web app...');
77+
this.log('Using default target from Web Application configuration');
78+
}
79+
80+
if (flags['root-dir']) {
81+
this.log(`Root directory: ${flags['root-dir']}`);
5482
}
5583

56-
this.log(`Preview server running on port: ${flags.port}`);
57-
this.log('Preview your web app locally without needing to deploy');
84+
this.log(`Server running on http://${flags.host}:${flags.port}`);
85+
86+
if (!flags['no-open']) {
87+
this.log('Opening browser...');
88+
}
5889

5990
// TODO: Implement local development server logic
6091
// This would typically involve:
61-
// 1. Starting a local development server
62-
// 2. Watching for file changes
63-
// 3. Hot reloading functionality
64-
// 4. Proxying API calls to Salesforce org if needed
92+
// 1. Resolving the Web Application metadata from CMS
93+
// 2. Reading configuration (targets, routes, etc.)
94+
// 3. Deriving or using the specified local path
95+
// 4. Starting a local development server on specified host:port
96+
// 5. Watching for file changes with hot reloading
97+
// 6. Opening browser automatically unless --no-open is set
98+
// 7. Proxying API calls to Salesforce org if needed
6599

66100
return {
67101
name: flags.name,
102+
target: flags.target,
103+
rootDir: flags['root-dir'],
68104
port: flags.port,
105+
host: flags.host,
106+
noOpen: flags['no-open'],
69107
success: true,
70108
};
71109
}

test/commands/webapp/dev.nut.ts

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,34 @@ describe('webapp dev NUTs', () => {
2727
await session?.clean();
2828
});
2929

30-
it('should display provided name', () => {
31-
const name = 'World';
32-
const command = `webapp dev --name ${name}`;
30+
it('should start dev server with name', () => {
31+
const command = 'webapp dev --name myWebApp';
3332
const output = execCmd(command, { ensureExitCode: 0 }).shellOutput.stdout;
34-
expect(output).to.contain(name);
33+
expect(output).to.contain('myWebApp');
34+
expect(output).to.contain('Server running on http://localhost:8080');
35+
});
36+
37+
it('should start dev server with target', () => {
38+
const command = 'webapp dev --name myWebApp --target "LightningApp"';
39+
const output = execCmd(command, { ensureExitCode: 0 }).shellOutput.stdout;
40+
expect(output).to.contain('Using target: LightningApp');
41+
});
42+
43+
it('should start dev server with custom port and host', () => {
44+
const command = 'webapp dev --name myWebApp --port 9000 --host 0.0.0.0';
45+
const output = execCmd(command, { ensureExitCode: 0 }).shellOutput.stdout;
46+
expect(output).to.contain('Server running on http://0.0.0.0:9000');
47+
});
48+
49+
it('should start dev server with root-dir', () => {
50+
const command = 'webapp dev --name myWebApp --root-dir ./webapps/myWebApp';
51+
const output = execCmd(command, { ensureExitCode: 0 }).shellOutput.stdout;
52+
expect(output).to.contain('Root directory: ./webapps/myWebApp');
53+
});
54+
55+
it('should start dev server with no-open flag', () => {
56+
const command = 'webapp dev --name myWebApp --no-open';
57+
const output = execCmd(command, { ensureExitCode: 0 }).shellOutput.stdout;
58+
expect(output).to.not.contain('Opening browser...');
3559
});
3660
});

test/commands/webapp/dev.test.ts

Lines changed: 85 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,23 +30,70 @@ describe('webapp dev', () => {
3030
$$.restore();
3131
});
3232

33-
it('runs dev server without name', async () => {
34-
const result = await WebappDev.run([]);
35-
expect(result.port).to.equal(3000);
33+
it('runs dev server with required name', async () => {
34+
const result = await WebappDev.run(['--name', 'myWebApp']);
35+
expect(result.name).to.equal('myWebApp');
36+
expect(result.port).to.equal(8080);
37+
expect(result.host).to.equal('localhost');
38+
expect(result.noOpen).to.be.false;
3639
expect(result.success).to.be.true;
37-
expect(result.name).to.be.undefined;
3840
});
3941

40-
it('runs dev server with name', async () => {
41-
const result = await WebappDev.run(['--name', 'myWebApp']);
42+
it('runs dev server with custom port', async () => {
43+
const result = await WebappDev.run(['--name', 'myWebApp', '--port', '3000']);
4244
expect(result.name).to.equal('myWebApp');
4345
expect(result.port).to.equal(3000);
4446
expect(result.success).to.be.true;
4547
});
4648

47-
it('runs dev server with custom port', async () => {
48-
const result = await WebappDev.run(['--port', '8080']);
49-
expect(result.port).to.equal(8080);
49+
it('runs dev server with custom host', async () => {
50+
const result = await WebappDev.run(['--name', 'myWebApp', '--host', '0.0.0.0']);
51+
expect(result.name).to.equal('myWebApp');
52+
expect(result.host).to.equal('0.0.0.0');
53+
expect(result.success).to.be.true;
54+
});
55+
56+
it('runs dev server with target', async () => {
57+
const result = await WebappDev.run(['--name', 'myWebApp', '--target', 'LightningApp']);
58+
expect(result.name).to.equal('myWebApp');
59+
expect(result.target).to.equal('LightningApp');
60+
expect(result.success).to.be.true;
61+
});
62+
63+
it('runs dev server with root-dir', async () => {
64+
const result = await WebappDev.run(['--name', 'myWebApp', '--root-dir', './webapps/myWebApp']);
65+
expect(result.name).to.equal('myWebApp');
66+
expect(result.rootDir).to.equal('./webapps/myWebApp');
67+
expect(result.success).to.be.true;
68+
});
69+
70+
it('runs dev server with no-open flag', async () => {
71+
const result = await WebappDev.run(['--name', 'myWebApp', '--no-open']);
72+
expect(result.name).to.equal('myWebApp');
73+
expect(result.noOpen).to.be.true;
74+
expect(result.success).to.be.true;
75+
});
76+
77+
it('runs dev server with all flags', async () => {
78+
const result = await WebappDev.run([
79+
'--name',
80+
'myWebApp',
81+
'--target',
82+
'Site',
83+
'--root-dir',
84+
'./webapps/test',
85+
'--port',
86+
'9000',
87+
'--host',
88+
'127.0.0.1',
89+
'--no-open',
90+
]);
91+
expect(result.name).to.equal('myWebApp');
92+
expect(result.target).to.equal('Site');
93+
expect(result.rootDir).to.equal('./webapps/test');
94+
expect(result.port).to.equal(9000);
95+
expect(result.host).to.equal('127.0.0.1');
96+
expect(result.noOpen).to.be.true;
5097
expect(result.success).to.be.true;
5198
});
5299

@@ -57,6 +104,34 @@ describe('webapp dev', () => {
57104
.flatMap((c) => c.args)
58105
.join('\n');
59106
expect(output).to.include('Starting development server for web app: testApp');
60-
expect(output).to.include('Preview server running on port: 3000');
107+
expect(output).to.include('Server running on http://localhost:8080');
108+
expect(output).to.include('Opening browser...');
109+
});
110+
111+
it('outputs target message when specified', async () => {
112+
await WebappDev.run(['--name', 'testApp', '--target', 'LightningApp']);
113+
const output = sfCommandStubs.log
114+
.getCalls()
115+
.flatMap((c) => c.args)
116+
.join('\n');
117+
expect(output).to.include('Using target: LightningApp');
118+
});
119+
120+
it('outputs default target message when not specified', async () => {
121+
await WebappDev.run(['--name', 'testApp']);
122+
const output = sfCommandStubs.log
123+
.getCalls()
124+
.flatMap((c) => c.args)
125+
.join('\n');
126+
expect(output).to.include('Using default target from Web Application configuration');
127+
});
128+
129+
it('does not output browser message with no-open', async () => {
130+
await WebappDev.run(['--name', 'testApp', '--no-open']);
131+
const output = sfCommandStubs.log
132+
.getCalls()
133+
.flatMap((c) => c.args)
134+
.join('\n');
135+
expect(output).to.not.include('Opening browser...');
61136
});
62137
});

0 commit comments

Comments
 (0)