From 68bab95a017b93247af4ff2dd686057a596ae1a3 Mon Sep 17 00:00:00 2001 From: shinoni Date: Tue, 4 Nov 2025 12:22:09 -0800 Subject: [PATCH 01/23] feat: implement webapp commands for One Runtime Apps with Agentforce Vibes - Add webapp:generate command with name, label, target, template, and wizard flags - Add webapp:deploy command with name and options (build/value) flags - Add webapp:retrieve command with name flag - Add webapp:dev command with optional name and port flags - Remove hello world example code - Add comprehensive unit tests and NUT tests (18 tests total, all passing) - Add message files with examples for all commands - Generate JSON schemas for all webapp commands - Update command snapshot for deprecation policy - Update package.json topics from 'hello' to 'webapp' All commands support JSON output and follow Salesforce CLI conventions. Commands include TODO comments for future business logic implementation. --- command-snapshot.json | 26 +++++- messages/hello.world.md | 29 ------ messages/webapp.deploy.md | 25 +++++ messages/webapp.dev.md | 29 ++++++ messages/webapp.generate.md | 41 +++++++++ messages/webapp.retrieve.md | 17 ++++ package.json | 4 +- schemas/webapp-deploy.json | 22 +++++ schemas/webapp-dev.json | 22 +++++ schemas/webapp-generate.json | 28 ++++++ ...{hello-world.json => webapp-retrieve.json} | 10 +- src/commands/webapp/deploy.ts | 70 ++++++++++++++ src/commands/webapp/dev.ts | 72 +++++++++++++++ src/commands/webapp/generate.ts | 92 +++++++++++++++++++ .../{hello/world.ts => webapp/retrieve.ts} | 34 ++++--- .../world.nut.ts => webapp/deploy.nut.ts} | 27 ++---- test/commands/webapp/deploy.test.ts | 56 +++++++++++ test/commands/webapp/dev.nut.ts | 36 ++++++++ test/commands/webapp/dev.test.ts | 62 +++++++++++++ test/commands/webapp/generate.nut.ts | 38 ++++++++ test/commands/webapp/generate.test.ts | 74 +++++++++++++++ test/commands/webapp/retrieve.nut.ts | 36 ++++++++ .../world.test.ts => webapp/retrieve.test.ts} | 34 +++---- 23 files changed, 798 insertions(+), 86 deletions(-) delete mode 100644 messages/hello.world.md create mode 100644 messages/webapp.deploy.md create mode 100644 messages/webapp.dev.md create mode 100644 messages/webapp.generate.md create mode 100644 messages/webapp.retrieve.md create mode 100644 schemas/webapp-deploy.json create mode 100644 schemas/webapp-dev.json create mode 100644 schemas/webapp-generate.json rename schemas/{hello-world.json => webapp-retrieve.json} (59%) create mode 100644 src/commands/webapp/deploy.ts create mode 100644 src/commands/webapp/dev.ts create mode 100644 src/commands/webapp/generate.ts rename src/commands/{hello/world.ts => webapp/retrieve.ts} (62%) rename test/commands/{hello/world.nut.ts => webapp/deploy.nut.ts} (51%) create mode 100644 test/commands/webapp/deploy.test.ts create mode 100644 test/commands/webapp/dev.nut.ts create mode 100644 test/commands/webapp/dev.test.ts create mode 100644 test/commands/webapp/generate.nut.ts create mode 100644 test/commands/webapp/generate.test.ts create mode 100644 test/commands/webapp/retrieve.nut.ts rename test/commands/{hello/world.test.ts => webapp/retrieve.test.ts} (58%) diff --git a/command-snapshot.json b/command-snapshot.json index 6dec3eb..ed109b0 100644 --- a/command-snapshot.json +++ b/command-snapshot.json @@ -1,7 +1,31 @@ [ { "alias": [], - "command": "hello:world", + "command": "webapp:deploy", + "flagAliases": [], + "flagChars": ["n", "o"], + "flags": ["flags-dir", "json", "name", "options"], + "plugin": "@salesforce/plugin-webapp" + }, + { + "alias": [], + "command": "webapp:dev", + "flagAliases": [], + "flagChars": ["n", "p"], + "flags": ["flags-dir", "json", "name", "port"], + "plugin": "@salesforce/plugin-webapp" + }, + { + "alias": [], + "command": "webapp:generate", + "flagAliases": [], + "flagChars": ["l", "n", "r", "t", "w"], + "flags": ["flags-dir", "json", "label", "name", "target", "template", "wizard"], + "plugin": "@salesforce/plugin-webapp" + }, + { + "alias": [], + "command": "webapp:retrieve", "flagAliases": [], "flagChars": ["n"], "flags": ["flags-dir", "json", "name"], diff --git a/messages/hello.world.md b/messages/hello.world.md deleted file mode 100644 index 804f848..0000000 --- a/messages/hello.world.md +++ /dev/null @@ -1,29 +0,0 @@ -# summary - -Say hello. - -# description - -Say hello either to the world or someone you know. - -# flags.name.summary - -The name of the person you'd like to say hello to. - -# flags.name.description - -This person can be anyone in the world! - -# examples - -- Say hello to the world: - - <%= config.bin %> <%= command.id %> - -- Say hello to someone you know: - - <%= config.bin %> <%= command.id %> --name Astro - -# info.hello - -Hello %s at %s. diff --git a/messages/webapp.deploy.md b/messages/webapp.deploy.md new file mode 100644 index 0000000..11c4d47 --- /dev/null +++ b/messages/webapp.deploy.md @@ -0,0 +1,25 @@ +# summary + +Deploy the web app, its assets and associated metadata + +# description + +This command builds and deploys your web app and associated metadata to your Salesforce org. It packages all assets, applies configurations, and ensures proper deployment of all components. One Runtime in Agentforce Vibes goes from Code → Metadata/Files. The default "build" option will run the necessary commands to produce the bundle and metadata to deploy the One Runtime App. + +# flags.name.summary + +Name of your web app + +# flags.options.summary + +Deployment options (build or value) + +# examples + +- Deploy a web app: + + <%= config.bin %> <%= command.id %> --name myWebApp + +- Deploy a web app with specific options: + + <%= config.bin %> <%= command.id %> --name myWebApp --options build diff --git a/messages/webapp.dev.md b/messages/webapp.dev.md new file mode 100644 index 0000000..d96f99b --- /dev/null +++ b/messages/webapp.dev.md @@ -0,0 +1,29 @@ +# summary + +Preview a web app locally without needing to deploy + +# description + +Start a local development server to preview your web app without deploying to Salesforce. This enables rapid development with hot reloading and immediate feedback. + +# flags.name.summary + +Name of your web app + +# flags.port.summary + +Port number for the development server + +# examples + +- Start the development server: + + <%= config.bin %> <%= command.id %> + +- Start the development server for a specific web app: + + <%= config.bin %> <%= command.id %> --name myWebApp + +- Start the development server on a custom port: + + <%= config.bin %> <%= command.id %> --name myWebApp --port 8080 diff --git a/messages/webapp.generate.md b/messages/webapp.generate.md new file mode 100644 index 0000000..0122ad8 --- /dev/null +++ b/messages/webapp.generate.md @@ -0,0 +1,41 @@ +# summary + +Create a web app and associated metadata + +# description + +Generate a new Salesforce web app with the specified configuration. This command creates the basic structure, metadata, and configuration files needed for a One Runtime App with Agentforce Vibes integration. + +# flags.name.summary + +Name of your web app + +# flags.label.summary + +Human readable name of your web app + +# flags.target.summary + +Target platform for the web app (Site, Embed, or Lightning) + +# flags.template.summary + +Template to use for web app generation (pulls from central solution) + +# flags.wizard.summary + +Run in interactive wizard mode + +# examples + +- Create an empty web app: + + <%= config.bin %> <%= command.id %> --name "myWebApp" --label "My first Web App" + +- Create a web app with a specific target: + + <%= config.bin %> <%= command.id %> --name "myWebApp" --label "My Web App" --target Site + +- Create a web app using the wizard: + + <%= config.bin %> <%= command.id %> --name "myWebApp" --label "My Web App" --wizard diff --git a/messages/webapp.retrieve.md b/messages/webapp.retrieve.md new file mode 100644 index 0000000..a517ef0 --- /dev/null +++ b/messages/webapp.retrieve.md @@ -0,0 +1,17 @@ +# summary + +Retrieve the web app, its assets and associated metadata + +# description + +This command retrieves your web app, its assets, and associated metadata from your Salesforce org to your local environment. Useful for syncing remote changes or setting up a local development environment. + +# flags.name.summary + +Name of your web app to retrieve + +# examples + +- Retrieve a web app: + + <%= config.bin %> <%= command.id %> --name myWebApp diff --git a/package.json b/package.json index 94dc5f6..e9861cd 100644 --- a/package.json +++ b/package.json @@ -52,8 +52,8 @@ "@salesforce/plugin-command-reference" ], "topics": { - "hello": { - "description": "Commands to say hello." + "webapp": { + "description": "Work with Salesforce Web Apps" } }, "flexibleTaxonomy": true diff --git a/schemas/webapp-deploy.json b/schemas/webapp-deploy.json new file mode 100644 index 0000000..20fd56b --- /dev/null +++ b/schemas/webapp-deploy.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/WebappDeployResult", + "definitions": { + "WebappDeployResult": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "options": { + "type": "string" + }, + "success": { + "type": "boolean" + } + }, + "required": ["name", "options", "success"], + "additionalProperties": false + } + } +} diff --git a/schemas/webapp-dev.json b/schemas/webapp-dev.json new file mode 100644 index 0000000..4a73aca --- /dev/null +++ b/schemas/webapp-dev.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/WebappDevResult", + "definitions": { + "WebappDevResult": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "port": { + "type": "number" + }, + "success": { + "type": "boolean" + } + }, + "required": ["port", "success"], + "additionalProperties": false + } + } +} diff --git a/schemas/webapp-generate.json b/schemas/webapp-generate.json new file mode 100644 index 0000000..a6627e0 --- /dev/null +++ b/schemas/webapp-generate.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/WebappGenerateResult", + "definitions": { + "WebappGenerateResult": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "label": { + "type": "string" + }, + "target": { + "type": "string" + }, + "template": { + "type": "string" + }, + "wizard": { + "type": "boolean" + } + }, + "required": ["name", "label", "target", "template", "wizard"], + "additionalProperties": false + } + } +} diff --git a/schemas/hello-world.json b/schemas/webapp-retrieve.json similarity index 59% rename from schemas/hello-world.json rename to schemas/webapp-retrieve.json index d28ac19..7899f86 100644 --- a/schemas/hello-world.json +++ b/schemas/webapp-retrieve.json @@ -1,18 +1,18 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$ref": "#/definitions/HelloWorldResult", + "$ref": "#/definitions/WebappRetrieveResult", "definitions": { - "HelloWorldResult": { + "WebappRetrieveResult": { "type": "object", "properties": { "name": { "type": "string" }, - "time": { - "type": "string" + "success": { + "type": "boolean" } }, - "required": ["name", "time"], + "required": ["name", "success"], "additionalProperties": false } } diff --git a/src/commands/webapp/deploy.ts b/src/commands/webapp/deploy.ts new file mode 100644 index 0000000..e669be1 --- /dev/null +++ b/src/commands/webapp/deploy.ts @@ -0,0 +1,70 @@ +/* + * Copyright 2025, Salesforce, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { SfCommand, Flags } from '@salesforce/sf-plugins-core'; +import { Messages } from '@salesforce/core'; + +Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); +const messages = Messages.loadMessages('@salesforce/plugin-webapp', 'webapp.deploy'); + +export type WebappDeployResult = { + name: string; + options: string; + success: boolean; +}; + +export default class WebappDeploy extends SfCommand { + public static readonly summary = messages.getMessage('summary'); + public static readonly description = messages.getMessage('description'); + public static readonly examples = messages.getMessages('examples'); + + public static readonly flags = { + name: Flags.string({ + summary: messages.getMessage('flags.name.summary'), + char: 'n', + required: true, + }), + options: Flags.string({ + summary: messages.getMessage('flags.options.summary'), + char: 'o', + options: ['build', 'value'], + default: 'build', + }), + }; + + public async run(): Promise { + const { flags } = await this.parse(WebappDeploy); + + this.log(`Deploying web app: ${flags.name}`); + this.log(`Options: ${flags.options}`); + this.log('Building and deploying your web app and associated metadata to your org...'); + + // TODO: Implement web app deployment logic + // This would typically involve: + // 1. Building the web app (if options includes 'build') + // 2. Packaging assets into bundle + // 3. Creating/updating metadata files + // 4. Deploying to Salesforce org + + this.log(`Successfully deployed ${flags.name}`); + + return { + name: flags.name, + options: flags.options ?? 'build', + success: true, + }; + } +} diff --git a/src/commands/webapp/dev.ts b/src/commands/webapp/dev.ts new file mode 100644 index 0000000..8c7cfa4 --- /dev/null +++ b/src/commands/webapp/dev.ts @@ -0,0 +1,72 @@ +/* + * Copyright 2025, Salesforce, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { SfCommand, Flags } from '@salesforce/sf-plugins-core'; +import { Messages } from '@salesforce/core'; + +Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); +const messages = Messages.loadMessages('@salesforce/plugin-webapp', 'webapp.dev'); + +export type WebappDevResult = { + name?: string; + port: number; + success: boolean; +}; + +export default class WebappDev extends SfCommand { + public static readonly summary = messages.getMessage('summary'); + public static readonly description = messages.getMessage('description'); + public static readonly examples = messages.getMessages('examples'); + + public static readonly flags = { + name: Flags.string({ + summary: messages.getMessage('flags.name.summary'), + char: 'n', + required: false, + }), + port: Flags.integer({ + summary: messages.getMessage('flags.port.summary'), + char: 'p', + default: 3000, + }), + }; + + public async run(): Promise { + const { flags } = await this.parse(WebappDev); + + if (flags.name) { + this.log(`Starting development server for web app: ${flags.name}`); + } else { + this.log('Starting development server for web app...'); + } + + this.log(`Preview server running on port: ${flags.port}`); + this.log('Preview your web app locally without needing to deploy'); + + // TODO: Implement local development server logic + // This would typically involve: + // 1. Starting a local development server + // 2. Watching for file changes + // 3. Hot reloading functionality + // 4. Proxying API calls to Salesforce org if needed + + return { + name: flags.name, + port: flags.port, + success: true, + }; + } +} diff --git a/src/commands/webapp/generate.ts b/src/commands/webapp/generate.ts new file mode 100644 index 0000000..611bd04 --- /dev/null +++ b/src/commands/webapp/generate.ts @@ -0,0 +1,92 @@ +/* + * Copyright 2025, Salesforce, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { SfCommand, Flags } from '@salesforce/sf-plugins-core'; +import { Messages } from '@salesforce/core'; + +Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); +const messages = Messages.loadMessages('@salesforce/plugin-webapp', 'webapp.generate'); + +export type WebappGenerateResult = { + name: string; + label: string; + target: string; + template: string; + wizard: boolean; +}; + +export default class WebappGenerate extends SfCommand { + public static readonly summary = messages.getMessage('summary'); + public static readonly description = messages.getMessage('description'); + public static readonly examples = messages.getMessages('examples'); + + public static readonly flags = { + name: Flags.string({ + summary: messages.getMessage('flags.name.summary'), + char: 'n', + required: true, + }), + label: Flags.string({ + summary: messages.getMessage('flags.label.summary'), + char: 'l', + required: true, + }), + target: Flags.string({ + summary: messages.getMessage('flags.target.summary'), + char: 't', + options: ['Site', 'Embed', 'Lightning'], + default: 'empty', + }), + template: Flags.string({ + summary: messages.getMessage('flags.template.summary'), + char: 'r', + default: 'empty', + }), + wizard: Flags.boolean({ + summary: messages.getMessage('flags.wizard.summary'), + char: 'w', + default: false, + }), + }; + + public async run(): Promise { + const { flags } = await this.parse(WebappGenerate); + + this.log('Generating your web app, give us a moment...'); + this.log(`Name: ${flags.name}`); + this.log(`Label: ${flags.label}`); + this.log(`Target: ${flags.target}`); + this.log(`Template: ${flags.template}`); + this.log(`Wizard mode: ${flags.wizard}`); + + // TODO: Implement web app generation logic + // This would typically involve: + // 1. Creating webapp.json configuration + // 2. Setting up SFDX project structure + // 3. Generating metadata files + // 4. Creating necessary bundle structure + + this.log('Your Web App has been created, have fun!'); + + return { + name: flags.name, + label: flags.label, + target: flags.target ?? 'empty', + template: flags.template ?? 'empty', + wizard: flags.wizard, + }; + } +} diff --git a/src/commands/hello/world.ts b/src/commands/webapp/retrieve.ts similarity index 62% rename from src/commands/hello/world.ts rename to src/commands/webapp/retrieve.ts index 4cfa97b..21e754a 100644 --- a/src/commands/hello/world.ts +++ b/src/commands/webapp/retrieve.ts @@ -18,34 +18,44 @@ import { SfCommand, Flags } from '@salesforce/sf-plugins-core'; import { Messages } from '@salesforce/core'; Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); -const messages = Messages.loadMessages('@salesforce/plugin-webapp', 'hello.world'); +const messages = Messages.loadMessages('@salesforce/plugin-webapp', 'webapp.retrieve'); -export type HelloWorldResult = { +export type WebappRetrieveResult = { name: string; - time: string; + success: boolean; }; -export default class World extends SfCommand { +export default class WebappRetrieve extends SfCommand { public static readonly summary = messages.getMessage('summary'); public static readonly description = messages.getMessage('description'); public static readonly examples = messages.getMessages('examples'); public static readonly flags = { name: Flags.string({ - char: 'n', summary: messages.getMessage('flags.name.summary'), - description: messages.getMessage('flags.name.description'), - default: 'World', + char: 'n', + required: true, }), }; - public async run(): Promise { - const { flags } = await this.parse(World); - const time = new Date().toDateString(); - this.log(messages.getMessage('info.hello', [flags.name, time])); + public async run(): Promise { + const { flags } = await this.parse(WebappRetrieve); + + this.log(`Retrieving web app: ${flags.name}`); + this.log('Retrieving your web app, its assets and associated metadata from your org...'); + + // TODO: Implement web app retrieval logic + // This would typically involve: + // 1. Connecting to the Salesforce org + // 2. Retrieving web app metadata + // 3. Downloading assets and bundle + // 4. Saving locally with proper structure + + this.log(`Successfully retrieved ${flags.name}`); + return { name: flags.name, - time, + success: true, }; } } diff --git a/test/commands/hello/world.nut.ts b/test/commands/webapp/deploy.nut.ts similarity index 51% rename from test/commands/hello/world.nut.ts rename to test/commands/webapp/deploy.nut.ts index 61ac34d..9636552 100644 --- a/test/commands/hello/world.nut.ts +++ b/test/commands/webapp/deploy.nut.ts @@ -13,31 +13,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - import { execCmd, TestSession } from '@salesforce/cli-plugins-testkit'; import { expect } from 'chai'; -import { HelloWorldResult } from '../../../src/commands/hello/world.js'; -let testSession: TestSession; +describe('webapp deploy NUTs', () => { + let session: TestSession; -describe('hello world NUTs', () => { - before('prepare session', async () => { - testSession = await TestSession.create(); + before(async () => { + session = await TestSession.create({ devhubAuthStrategy: 'NONE' }); }); after(async () => { - await testSession?.clean(); - }); - - it('should say hello to the world', () => { - const result = execCmd('hello world --json', { ensureExitCode: 0 }).jsonOutput?.result; - expect(result?.name).to.equal('World'); + await session?.clean(); }); - it('should say hello to a given person', () => { - const result = execCmd('hello world --name Astro --json', { - ensureExitCode: 0, - }).jsonOutput?.result; - expect(result?.name).to.equal('Astro'); + it('should display provided name', () => { + const name = 'World'; + const command = `webapp deploy --name ${name}`; + const output = execCmd(command, { ensureExitCode: 0 }).shellOutput.stdout; + expect(output).to.contain(name); }); }); diff --git a/test/commands/webapp/deploy.test.ts b/test/commands/webapp/deploy.test.ts new file mode 100644 index 0000000..6844399 --- /dev/null +++ b/test/commands/webapp/deploy.test.ts @@ -0,0 +1,56 @@ +/* + * Copyright 2025, Salesforce, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { TestContext } from '@salesforce/core/testSetup'; +import { expect } from 'chai'; +import { stubSfCommandUx } from '@salesforce/sf-plugins-core'; +import WebappDeploy from '../../../src/commands/webapp/deploy.js'; + +describe('webapp deploy', () => { + const $$ = new TestContext(); + let sfCommandStubs: ReturnType; + + beforeEach(() => { + sfCommandStubs = stubSfCommandUx($$.SANDBOX); + }); + + afterEach(() => { + $$.restore(); + }); + + it('deploys webapp with required name flag', async () => { + const result = await WebappDeploy.run(['--name', 'myWebApp']); + expect(result.name).to.equal('myWebApp'); + expect(result.options).to.equal('build'); + expect(result.success).to.be.true; + }); + + it('runs with --json and custom options', async () => { + const result = await WebappDeploy.run(['--name', 'testApp', '--options', 'value']); + expect(result.name).to.equal('testApp'); + expect(result.options).to.equal('value'); + expect(result.success).to.be.true; + }); + + it('outputs deployment messages', async () => { + await WebappDeploy.run(['--name', 'myWebApp']); + const output = sfCommandStubs.log + .getCalls() + .flatMap((c) => c.args) + .join('\n'); + expect(output).to.include('Deploying web app: myWebApp'); + expect(output).to.include('Successfully deployed myWebApp'); + }); +}); diff --git a/test/commands/webapp/dev.nut.ts b/test/commands/webapp/dev.nut.ts new file mode 100644 index 0000000..5d37797 --- /dev/null +++ b/test/commands/webapp/dev.nut.ts @@ -0,0 +1,36 @@ +/* + * Copyright 2025, Salesforce, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { execCmd, TestSession } from '@salesforce/cli-plugins-testkit'; +import { expect } from 'chai'; + +describe('webapp dev NUTs', () => { + let session: TestSession; + + before(async () => { + session = await TestSession.create({ devhubAuthStrategy: 'NONE' }); + }); + + after(async () => { + await session?.clean(); + }); + + it('should display provided name', () => { + const name = 'World'; + const command = `webapp dev --name ${name}`; + const output = execCmd(command, { ensureExitCode: 0 }).shellOutput.stdout; + expect(output).to.contain(name); + }); +}); diff --git a/test/commands/webapp/dev.test.ts b/test/commands/webapp/dev.test.ts new file mode 100644 index 0000000..9a8fa52 --- /dev/null +++ b/test/commands/webapp/dev.test.ts @@ -0,0 +1,62 @@ +/* + * Copyright 2025, Salesforce, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { TestContext } from '@salesforce/core/testSetup'; +import { expect } from 'chai'; +import { stubSfCommandUx } from '@salesforce/sf-plugins-core'; +import WebappDev from '../../../src/commands/webapp/dev.js'; + +describe('webapp dev', () => { + const $$ = new TestContext(); + let sfCommandStubs: ReturnType; + + beforeEach(() => { + sfCommandStubs = stubSfCommandUx($$.SANDBOX); + }); + + afterEach(() => { + $$.restore(); + }); + + it('runs dev server without name', async () => { + const result = await WebappDev.run([]); + expect(result.port).to.equal(3000); + expect(result.success).to.be.true; + expect(result.name).to.be.undefined; + }); + + it('runs dev server with name', async () => { + const result = await WebappDev.run(['--name', 'myWebApp']); + expect(result.name).to.equal('myWebApp'); + expect(result.port).to.equal(3000); + expect(result.success).to.be.true; + }); + + it('runs dev server with custom port', async () => { + const result = await WebappDev.run(['--port', '8080']); + expect(result.port).to.equal(8080); + expect(result.success).to.be.true; + }); + + it('outputs dev server messages', async () => { + await WebappDev.run(['--name', 'testApp']); + const output = sfCommandStubs.log + .getCalls() + .flatMap((c) => c.args) + .join('\n'); + expect(output).to.include('Starting development server for web app: testApp'); + expect(output).to.include('Preview server running on port: 3000'); + }); +}); diff --git a/test/commands/webapp/generate.nut.ts b/test/commands/webapp/generate.nut.ts new file mode 100644 index 0000000..e639f74 --- /dev/null +++ b/test/commands/webapp/generate.nut.ts @@ -0,0 +1,38 @@ +/* + * Copyright 2025, Salesforce, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { execCmd, TestSession } from '@salesforce/cli-plugins-testkit'; +import { expect } from 'chai'; + +describe('webapp generate NUTs', () => { + let session: TestSession; + + before(async () => { + session = await TestSession.create({ devhubAuthStrategy: 'NONE' }); + }); + + after(async () => { + await session?.clean(); + }); + + it('should display provided name', () => { + const name = 'TestApp'; + const label = 'Test Application'; + const command = `webapp generate --name ${name} --label "${label}"`; + const output = execCmd(command, { ensureExitCode: 0 }).shellOutput.stdout; + expect(output).to.contain(name); + expect(output).to.contain(label); + }); +}); diff --git a/test/commands/webapp/generate.test.ts b/test/commands/webapp/generate.test.ts new file mode 100644 index 0000000..7d44921 --- /dev/null +++ b/test/commands/webapp/generate.test.ts @@ -0,0 +1,74 @@ +/* + * Copyright 2025, Salesforce, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { TestContext } from '@salesforce/core/testSetup'; +import { expect } from 'chai'; +import { stubSfCommandUx } from '@salesforce/sf-plugins-core'; +import WebappGenerate from '../../../src/commands/webapp/generate.js'; + +describe('webapp generate', () => { + const $$ = new TestContext(); + let sfCommandStubs: ReturnType; + + beforeEach(() => { + sfCommandStubs = stubSfCommandUx($$.SANDBOX); + }); + + afterEach(() => { + $$.restore(); + }); + + it('generates webapp with required flags', async () => { + const result = await WebappGenerate.run(['--name', 'myWebApp', '--label', 'My Web App']); + expect(result.name).to.equal('myWebApp'); + expect(result.label).to.equal('My Web App'); + expect(result.target).to.equal('empty'); + expect(result.template).to.equal('empty'); + expect(result.wizard).to.be.false; + }); + + it('generates webapp with target and template', async () => { + const result = await WebappGenerate.run([ + '--name', + 'testApp', + '--label', + 'Test App', + '--target', + 'Site', + '--template', + 'default', + ]); + expect(result.name).to.equal('testApp'); + expect(result.label).to.equal('Test App'); + expect(result.target).to.equal('Site'); + expect(result.template).to.equal('default'); + }); + + it('generates webapp with wizard mode', async () => { + const result = await WebappGenerate.run(['--name', 'wizardApp', '--label', 'Wizard App', '--wizard']); + expect(result.name).to.equal('wizardApp'); + expect(result.wizard).to.be.true; + }); + + it('outputs generation messages', async () => { + await WebappGenerate.run(['--name', 'myApp', '--label', 'My App']); + const output = sfCommandStubs.log + .getCalls() + .flatMap((c) => c.args) + .join('\n'); + expect(output).to.include('Generating your web app'); + expect(output).to.include('Your Web App has been created'); + }); +}); diff --git a/test/commands/webapp/retrieve.nut.ts b/test/commands/webapp/retrieve.nut.ts new file mode 100644 index 0000000..5655ede --- /dev/null +++ b/test/commands/webapp/retrieve.nut.ts @@ -0,0 +1,36 @@ +/* + * Copyright 2025, Salesforce, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { execCmd, TestSession } from '@salesforce/cli-plugins-testkit'; +import { expect } from 'chai'; + +describe('webapp retrieve NUTs', () => { + let session: TestSession; + + before(async () => { + session = await TestSession.create({ devhubAuthStrategy: 'NONE' }); + }); + + after(async () => { + await session?.clean(); + }); + + it('should display provided name', () => { + const name = 'World'; + const command = `webapp retrieve --name ${name}`; + const output = execCmd(command, { ensureExitCode: 0 }).shellOutput.stdout; + expect(output).to.contain(name); + }); +}); diff --git a/test/commands/hello/world.test.ts b/test/commands/webapp/retrieve.test.ts similarity index 58% rename from test/commands/hello/world.test.ts rename to test/commands/webapp/retrieve.test.ts index fa5c824..dfcc0ce 100644 --- a/test/commands/hello/world.test.ts +++ b/test/commands/webapp/retrieve.test.ts @@ -16,9 +16,9 @@ import { TestContext } from '@salesforce/core/testSetup'; import { expect } from 'chai'; import { stubSfCommandUx } from '@salesforce/sf-plugins-core'; -import World from '../../../src/commands/hello/world.js'; +import WebappRetrieve from '../../../src/commands/webapp/retrieve.js'; -describe('hello world', () => { +describe('webapp retrieve', () => { const $$ = new TestContext(); let sfCommandStubs: ReturnType; @@ -30,31 +30,25 @@ describe('hello world', () => { $$.restore(); }); - it('runs hello world', async () => { - await World.run([]); - const output = sfCommandStubs.log - .getCalls() - .flatMap((c) => c.args) - .join('\n'); - expect(output).to.include('Hello World'); + it('retrieves webapp with required name flag', async () => { + const result = await WebappRetrieve.run(['--name', 'myWebApp']); + expect(result.name).to.equal('myWebApp'); + expect(result.success).to.be.true; }); - it('runs hello world with --json and no provided name', async () => { - const result = await World.run([]); - expect(result.name).to.equal('World'); + it('runs with --json', async () => { + const result = await WebappRetrieve.run(['--name', 'testApp']); + expect(result.name).to.equal('testApp'); + expect(result.success).to.be.true; }); - it('runs hello world --name Astro', async () => { - await World.run(['--name', 'Astro']); + it('outputs retrieval messages', async () => { + await WebappRetrieve.run(['--name', 'myWebApp']); const output = sfCommandStubs.log .getCalls() .flatMap((c) => c.args) .join('\n'); - expect(output).to.include('Hello Astro'); - }); - - it('runs hello world --name Astro --json', async () => { - const result = await World.run(['--name', 'Astro', '--json']); - expect(result.name).to.equal('Astro'); + expect(output).to.include('Retrieving web app: myWebApp'); + expect(output).to.include('Successfully retrieved myWebApp'); }); }); From 1f51195da847953dd1cf68a80490034c2a13df52 Mon Sep 17 00:00:00 2001 From: shinoni Date: Tue, 4 Nov 2025 12:31:24 -0800 Subject: [PATCH 02/23] docs: use generic context instead of Agentforce Vibes references - Update webapp:generate description to use generic web app language - Update webapp:deploy description to remove One Runtime App references - Make messaging product-agnostic --- messages/webapp.deploy.md | 2 +- messages/webapp.generate.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/messages/webapp.deploy.md b/messages/webapp.deploy.md index 11c4d47..b09ceec 100644 --- a/messages/webapp.deploy.md +++ b/messages/webapp.deploy.md @@ -4,7 +4,7 @@ Deploy the web app, its assets and associated metadata # description -This command builds and deploys your web app and associated metadata to your Salesforce org. It packages all assets, applies configurations, and ensures proper deployment of all components. One Runtime in Agentforce Vibes goes from Code → Metadata/Files. The default "build" option will run the necessary commands to produce the bundle and metadata to deploy the One Runtime App. +This command builds and deploys your web app and associated metadata to your Salesforce org. It packages all assets, applies configurations, and ensures proper deployment of all components. The default "build" option will run the necessary commands to produce the bundle and metadata to deploy your web app. # flags.name.summary diff --git a/messages/webapp.generate.md b/messages/webapp.generate.md index 0122ad8..e8376c0 100644 --- a/messages/webapp.generate.md +++ b/messages/webapp.generate.md @@ -4,7 +4,7 @@ Create a web app and associated metadata # description -Generate a new Salesforce web app with the specified configuration. This command creates the basic structure, metadata, and configuration files needed for a One Runtime App with Agentforce Vibes integration. +Generate a new Salesforce web app with the specified configuration. This command creates the basic structure, metadata, and configuration files needed for your web application. # flags.name.summary From 3912930e4b8fcb05509c2a59bf45ec58d8671f72 Mon Sep 17 00:00:00 2001 From: shinoni Date: Wed, 5 Nov 2025 13:25:22 -0800 Subject: [PATCH 03/23] fix: change deploy options from 'value' to 'validate' - Update webapp:deploy --options flag to use 'build' or 'validate' - Update message file and test to reflect correct option - Regenerate JSON schemas --- messages/webapp.deploy.md | 2 +- src/commands/webapp/deploy.ts | 2 +- test/commands/webapp/deploy.test.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/messages/webapp.deploy.md b/messages/webapp.deploy.md index b09ceec..b829f9c 100644 --- a/messages/webapp.deploy.md +++ b/messages/webapp.deploy.md @@ -12,7 +12,7 @@ Name of your web app # flags.options.summary -Deployment options (build or value) +Deployment options (build or validate) # examples diff --git a/src/commands/webapp/deploy.ts b/src/commands/webapp/deploy.ts index e669be1..b781572 100644 --- a/src/commands/webapp/deploy.ts +++ b/src/commands/webapp/deploy.ts @@ -40,7 +40,7 @@ export default class WebappDeploy extends SfCommand { options: Flags.string({ summary: messages.getMessage('flags.options.summary'), char: 'o', - options: ['build', 'value'], + options: ['build', 'validate'], default: 'build', }), }; diff --git a/test/commands/webapp/deploy.test.ts b/test/commands/webapp/deploy.test.ts index 6844399..0f38c33 100644 --- a/test/commands/webapp/deploy.test.ts +++ b/test/commands/webapp/deploy.test.ts @@ -38,9 +38,9 @@ describe('webapp deploy', () => { }); it('runs with --json and custom options', async () => { - const result = await WebappDeploy.run(['--name', 'testApp', '--options', 'value']); + const result = await WebappDeploy.run(['--name', 'testApp', '--options', 'validate']); expect(result.name).to.equal('testApp'); - expect(result.options).to.equal('value'); + expect(result.options).to.equal('validate'); expect(result.success).to.be.true; }); From c96604090de4519111284596ba7cd00ab0f6678b Mon Sep 17 00:00:00 2001 From: shinoni Date: Wed, 5 Nov 2025 13:32:54 -0800 Subject: [PATCH 04/23] feat: add webapp version create command with optional version flag --- command-snapshot.json | 8 +++ messages/webapp.version.create.md | 25 ++++++++ package.json | 7 ++- schemas/webapp-version-create.json | 22 +++++++ src/commands/webapp/version/create.ts | 69 +++++++++++++++++++++ test/commands/webapp/version/create.nut.ts | 44 +++++++++++++ test/commands/webapp/version/create.test.ts | 57 +++++++++++++++++ 7 files changed, 231 insertions(+), 1 deletion(-) create mode 100644 messages/webapp.version.create.md create mode 100644 schemas/webapp-version-create.json create mode 100644 src/commands/webapp/version/create.ts create mode 100644 test/commands/webapp/version/create.nut.ts create mode 100644 test/commands/webapp/version/create.test.ts diff --git a/command-snapshot.json b/command-snapshot.json index ed109b0..fb5ee36 100644 --- a/command-snapshot.json +++ b/command-snapshot.json @@ -30,5 +30,13 @@ "flagChars": ["n"], "flags": ["flags-dir", "json", "name"], "plugin": "@salesforce/plugin-webapp" + }, + { + "alias": [], + "command": "webapp:version:create", + "flagAliases": [], + "flagChars": ["n", "v"], + "flags": ["flags-dir", "json", "name", "version"], + "plugin": "@salesforce/plugin-webapp" } ] diff --git a/messages/webapp.version.create.md b/messages/webapp.version.create.md new file mode 100644 index 0000000..9cfc709 --- /dev/null +++ b/messages/webapp.version.create.md @@ -0,0 +1,25 @@ +# summary + +Create a version for a web app + +# description + +Create a new version for your Salesforce web app. This command generates version metadata and tags the web app with the specified version number. + +# flags.name.summary + +Name of your web app + +# flags.version.summary + +Version number (e.g., 1.0.0) + +# examples + +- Create a version for a web app: + + <%= config.bin %> <%= command.id %> --name "myWebApp" --version "1.0.0" + +- Create a version without specifying version number: + + <%= config.bin %> <%= command.id %> --name "myWebApp" diff --git a/package.json b/package.json index e9861cd..f38df1a 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,12 @@ ], "topics": { "webapp": { - "description": "Work with Salesforce Web Apps" + "description": "Work with Salesforce Web Apps", + "subtopics": { + "version": { + "description": "description for webapp.version" + } + } } }, "flexibleTaxonomy": true diff --git a/schemas/webapp-version-create.json b/schemas/webapp-version-create.json new file mode 100644 index 0000000..3f5fb59 --- /dev/null +++ b/schemas/webapp-version-create.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/WebappVersionCreateResult", + "definitions": { + "WebappVersionCreateResult": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "success": { + "type": "boolean" + } + }, + "required": ["name", "success"], + "additionalProperties": false + } + } +} diff --git a/src/commands/webapp/version/create.ts b/src/commands/webapp/version/create.ts new file mode 100644 index 0000000..7dd7711 --- /dev/null +++ b/src/commands/webapp/version/create.ts @@ -0,0 +1,69 @@ +/* + * Copyright 2025, Salesforce, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { SfCommand, Flags } from '@salesforce/sf-plugins-core'; +import { Messages } from '@salesforce/core'; + +Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); +const messages = Messages.loadMessages('@salesforce/plugin-webapp', 'webapp.version.create'); + +export type WebappVersionCreateResult = { + name: string; + version?: string; + success: boolean; +}; + +export default class WebappVersionCreate extends SfCommand { + public static readonly summary = messages.getMessage('summary'); + public static readonly description = messages.getMessage('description'); + public static readonly examples = messages.getMessages('examples'); + + public static readonly flags = { + name: Flags.string({ + summary: messages.getMessage('flags.name.summary'), + char: 'n', + required: true, + }), + version: Flags.string({ + summary: messages.getMessage('flags.version.summary'), + char: 'v', + required: false, + }), + }; + + public async run(): Promise { + const { flags } = await this.parse(WebappVersionCreate); + + this.log(`Creating version for web app: ${flags.name}`); + if (flags.version) { + this.log(`Version: ${flags.version}`); + } + + // TODO: Implement version creation logic + // This would typically involve: + // 1. Validating the version format + // 2. Creating version metadata + // 3. Tagging or versioning the web app + + this.log('Version created successfully'); + + return { + name: flags.name, + version: flags.version, + success: true, + }; + } +} diff --git a/test/commands/webapp/version/create.nut.ts b/test/commands/webapp/version/create.nut.ts new file mode 100644 index 0000000..1404ce7 --- /dev/null +++ b/test/commands/webapp/version/create.nut.ts @@ -0,0 +1,44 @@ +/* + * Copyright 2025, Salesforce, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { execCmd, TestSession } from '@salesforce/cli-plugins-testkit'; +import { expect } from 'chai'; + +describe('webapp version create NUTs', () => { + let session: TestSession; + + before(async () => { + session = await TestSession.create({ devhubAuthStrategy: 'NONE' }); + }); + + after(async () => { + await session?.clean(); + }); + + it('should create version for web app', () => { + const command = 'webapp version create --name myWebApp --version 1.0.0'; + const output = execCmd(command, { ensureExitCode: 0 }).shellOutput.stdout; + expect(output).to.contain('myWebApp'); + expect(output).to.contain('1.0.0'); + expect(output).to.contain('Version created successfully'); + }); + + it('should create version without version flag', () => { + const command = 'webapp version create --name myWebApp'; + const output = execCmd(command, { ensureExitCode: 0 }).shellOutput.stdout; + expect(output).to.contain('myWebApp'); + expect(output).to.contain('Version created successfully'); + }); +}); diff --git a/test/commands/webapp/version/create.test.ts b/test/commands/webapp/version/create.test.ts new file mode 100644 index 0000000..af7a81e --- /dev/null +++ b/test/commands/webapp/version/create.test.ts @@ -0,0 +1,57 @@ +/* + * Copyright 2025, Salesforce, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { TestContext } from '@salesforce/core/testSetup'; +import { expect } from 'chai'; +import { stubSfCommandUx } from '@salesforce/sf-plugins-core'; +import WebappVersionCreate from '../../../../src/commands/webapp/version/create.js'; + +describe('webapp version create', () => { + const $$ = new TestContext(); + let sfCommandStubs: ReturnType; + + beforeEach(() => { + sfCommandStubs = stubSfCommandUx($$.SANDBOX); + }); + + afterEach(() => { + $$.restore(); + }); + + it('creates version with name and version flags', async () => { + const result = await WebappVersionCreate.run(['--name', 'myWebApp', '--version', '1.0.0']); + expect(result.name).to.equal('myWebApp'); + expect(result.version).to.equal('1.0.0'); + expect(result.success).to.be.true; + }); + + it('creates version with only name flag', async () => { + const result = await WebappVersionCreate.run(['--name', 'testApp']); + expect(result.name).to.equal('testApp'); + expect(result.version).to.be.undefined; + expect(result.success).to.be.true; + }); + + it('outputs version creation messages', async () => { + await WebappVersionCreate.run(['--name', 'myApp', '--version', '2.0.0']); + const output = sfCommandStubs.log + .getCalls() + .flatMap((c) => c.args) + .join('\n'); + expect(output).to.include('Creating version for web app: myApp'); + expect(output).to.include('Version: 2.0.0'); + expect(output).to.include('Version created successfully'); + }); +}); From c2a536e14352ee1df711af907192a10e32618650 Mon Sep 17 00:00:00 2001 From: shinoni Date: Wed, 5 Nov 2025 13:41:38 -0800 Subject: [PATCH 05/23] feat: add --no-overwrite and --ignore flags to webapp retrieve command --- command-snapshot.json | 4 +-- messages/webapp.retrieve.md | 20 +++++++++++++ schemas/webapp-retrieve.json | 8 ++++- src/commands/webapp/retrieve.ts | 26 ++++++++++++++++- test/commands/webapp/retrieve.nut.ts | 24 ++++++++++++++- test/commands/webapp/retrieve.test.ts | 42 +++++++++++++++++++++++++++ 6 files changed, 119 insertions(+), 5 deletions(-) diff --git a/command-snapshot.json b/command-snapshot.json index fb5ee36..4b419d8 100644 --- a/command-snapshot.json +++ b/command-snapshot.json @@ -27,8 +27,8 @@ "alias": [], "command": "webapp:retrieve", "flagAliases": [], - "flagChars": ["n"], - "flags": ["flags-dir", "json", "name"], + "flagChars": ["i", "n"], + "flags": ["flags-dir", "ignore", "json", "name", "no-overwrite"], "plugin": "@salesforce/plugin-webapp" }, { diff --git a/messages/webapp.retrieve.md b/messages/webapp.retrieve.md index a517ef0..42c744f 100644 --- a/messages/webapp.retrieve.md +++ b/messages/webapp.retrieve.md @@ -10,8 +10,28 @@ This command retrieves your web app, its assets, and associated metadata from yo Name of your web app to retrieve +# flags.no-overwrite.summary + +Prevent overwriting existing local files + +# flags.ignore.summary + +File pattern to ignore during retrieval (e.g., "dist/\*\*") + # examples - Retrieve a web app: <%= config.bin %> <%= command.id %> --name myWebApp + +- Retrieve a web app with overwrite protection: + + <%= config.bin %> <%= command.id %> --name myWebApp --no-overwrite + +- Retrieve a web app while ignoring specific files: + + <%= config.bin %> <%= command.id %> --name myWebApp --ignore "dist/\*\*" + +- Retrieve with both options: + + <%= config.bin %> <%= command.id %> --name "myWebApp" --no-overwrite --ignore "dist/\*\*" diff --git a/schemas/webapp-retrieve.json b/schemas/webapp-retrieve.json index 7899f86..91cdfa5 100644 --- a/schemas/webapp-retrieve.json +++ b/schemas/webapp-retrieve.json @@ -8,11 +8,17 @@ "name": { "type": "string" }, + "noOverwrite": { + "type": "boolean" + }, + "ignore": { + "type": "string" + }, "success": { "type": "boolean" } }, - "required": ["name", "success"], + "required": ["name", "noOverwrite", "success"], "additionalProperties": false } } diff --git a/src/commands/webapp/retrieve.ts b/src/commands/webapp/retrieve.ts index 21e754a..7887a23 100644 --- a/src/commands/webapp/retrieve.ts +++ b/src/commands/webapp/retrieve.ts @@ -22,6 +22,8 @@ const messages = Messages.loadMessages('@salesforce/plugin-webapp', 'webapp.retr export type WebappRetrieveResult = { name: string; + noOverwrite: boolean; + ignore?: string; success: boolean; }; @@ -36,12 +38,30 @@ export default class WebappRetrieve extends SfCommand { char: 'n', required: true, }), + 'no-overwrite': Flags.boolean({ + summary: messages.getMessage('flags.no-overwrite.summary'), + default: false, + }), + ignore: Flags.string({ + summary: messages.getMessage('flags.ignore.summary'), + char: 'i', + required: false, + }), }; public async run(): Promise { const { flags } = await this.parse(WebappRetrieve); this.log(`Retrieving web app: ${flags.name}`); + + if (flags['no-overwrite']) { + this.log('Overwrite protection enabled - existing files will not be replaced'); + } + + if (flags.ignore) { + this.log(`Ignoring pattern: ${flags.ignore}`); + } + this.log('Retrieving your web app, its assets and associated metadata from your org...'); // TODO: Implement web app retrieval logic @@ -49,12 +69,16 @@ export default class WebappRetrieve extends SfCommand { // 1. Connecting to the Salesforce org // 2. Retrieving web app metadata // 3. Downloading assets and bundle - // 4. Saving locally with proper structure + // 4. Applying ignore patterns if specified + // 5. Checking for existing files if no-overwrite is set + // 6. Saving locally with proper structure this.log(`Successfully retrieved ${flags.name}`); return { name: flags.name, + noOverwrite: flags['no-overwrite'], + ignore: flags.ignore, success: true, }; } diff --git a/test/commands/webapp/retrieve.nut.ts b/test/commands/webapp/retrieve.nut.ts index 5655ede..b7e96f2 100644 --- a/test/commands/webapp/retrieve.nut.ts +++ b/test/commands/webapp/retrieve.nut.ts @@ -28,9 +28,31 @@ describe('webapp retrieve NUTs', () => { }); it('should display provided name', () => { - const name = 'World'; + const name = 'myWebApp'; const command = `webapp retrieve --name ${name}`; const output = execCmd(command, { ensureExitCode: 0 }).shellOutput.stdout; expect(output).to.contain(name); }); + + it('should retrieve with no-overwrite flag', () => { + const command = 'webapp retrieve --name myWebApp --no-overwrite'; + const output = execCmd(command, { ensureExitCode: 0 }).shellOutput.stdout; + expect(output).to.contain('Overwrite protection enabled'); + expect(output).to.contain('Successfully retrieved myWebApp'); + }); + + it('should retrieve with ignore pattern', () => { + const command = 'webapp retrieve --name myWebApp --ignore "dist/**"'; + const output = execCmd(command, { ensureExitCode: 0 }).shellOutput.stdout; + expect(output).to.contain('Ignoring pattern: dist/**'); + expect(output).to.contain('Successfully retrieved myWebApp'); + }); + + it('should retrieve with both no-overwrite and ignore', () => { + const command = 'webapp retrieve --name myWebApp --no-overwrite --ignore "dist/**"'; + const output = execCmd(command, { ensureExitCode: 0 }).shellOutput.stdout; + expect(output).to.contain('Overwrite protection enabled'); + expect(output).to.contain('Ignoring pattern: dist/**'); + expect(output).to.contain('Successfully retrieved myWebApp'); + }); }); diff --git a/test/commands/webapp/retrieve.test.ts b/test/commands/webapp/retrieve.test.ts index dfcc0ce..d89bfd1 100644 --- a/test/commands/webapp/retrieve.test.ts +++ b/test/commands/webapp/retrieve.test.ts @@ -33,6 +33,30 @@ describe('webapp retrieve', () => { it('retrieves webapp with required name flag', async () => { const result = await WebappRetrieve.run(['--name', 'myWebApp']); expect(result.name).to.equal('myWebApp'); + expect(result.noOverwrite).to.be.false; + expect(result.ignore).to.be.undefined; + expect(result.success).to.be.true; + }); + + it('retrieves webapp with no-overwrite flag', async () => { + const result = await WebappRetrieve.run(['--name', 'myWebApp', '--no-overwrite']); + expect(result.name).to.equal('myWebApp'); + expect(result.noOverwrite).to.be.true; + expect(result.success).to.be.true; + }); + + it('retrieves webapp with ignore pattern', async () => { + const result = await WebappRetrieve.run(['--name', 'myWebApp', '--ignore', 'dist/**']); + expect(result.name).to.equal('myWebApp'); + expect(result.ignore).to.equal('dist/**'); + expect(result.success).to.be.true; + }); + + it('retrieves webapp with both no-overwrite and ignore flags', async () => { + const result = await WebappRetrieve.run(['--name', 'myWebApp', '--no-overwrite', '--ignore', 'dist/**']); + expect(result.name).to.equal('myWebApp'); + expect(result.noOverwrite).to.be.true; + expect(result.ignore).to.equal('dist/**'); expect(result.success).to.be.true; }); @@ -51,4 +75,22 @@ describe('webapp retrieve', () => { expect(output).to.include('Retrieving web app: myWebApp'); expect(output).to.include('Successfully retrieved myWebApp'); }); + + it('outputs overwrite protection message', async () => { + await WebappRetrieve.run(['--name', 'myWebApp', '--no-overwrite']); + const output = sfCommandStubs.log + .getCalls() + .flatMap((c) => c.args) + .join('\n'); + expect(output).to.include('Overwrite protection enabled'); + }); + + it('outputs ignore pattern message', async () => { + await WebappRetrieve.run(['--name', 'myWebApp', '--ignore', 'dist/**']); + const output = sfCommandStubs.log + .getCalls() + .flatMap((c) => c.args) + .join('\n'); + expect(output).to.include('Ignoring pattern: dist/**'); + }); }); From 94b85a6b9f60207346fbf31050b63c35b045d030 Mon Sep 17 00:00:00 2001 From: shinoni Date: Wed, 5 Nov 2025 14:19:55 -0800 Subject: [PATCH 06/23] feat: update webapp dev command with target, root-dir, host, and no-open flags --- command-snapshot.json | 4 +- messages/webapp.dev.md | 40 +++++++++++--- schemas/webapp-dev.json | 14 ++++- src/commands/webapp/dev.ts | 60 ++++++++++++++++---- test/commands/webapp/dev.nut.ts | 32 +++++++++-- test/commands/webapp/dev.test.ts | 95 ++++++++++++++++++++++++++++---- 6 files changed, 209 insertions(+), 36 deletions(-) diff --git a/command-snapshot.json b/command-snapshot.json index 4b419d8..ceda20e 100644 --- a/command-snapshot.json +++ b/command-snapshot.json @@ -11,8 +11,8 @@ "alias": [], "command": "webapp:dev", "flagAliases": [], - "flagChars": ["n", "p"], - "flags": ["flags-dir", "json", "name", "port"], + "flagChars": ["n", "p", "r", "t"], + "flags": ["flags-dir", "host", "json", "name", "no-open", "port", "root-dir", "target"], "plugin": "@salesforce/plugin-webapp" }, { diff --git a/messages/webapp.dev.md b/messages/webapp.dev.md index d96f99b..d5d5cf0 100644 --- a/messages/webapp.dev.md +++ b/messages/webapp.dev.md @@ -4,26 +4,50 @@ Preview a web app locally without needing to deploy # description -Start a local development server to preview your web app without deploying to Salesforce. This enables rapid development with hot reloading and immediate feedback. +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. # flags.name.summary -Name of your web app +Identifies the Web Application (CMS MD) to use + +# flags.target.summary + +Selects which Web Application target to use for the preview (e.g., Lightning App, Site) + +# flags.root-dir.summary + +Optional override for the local project root of this Web Application # flags.port.summary -Port number for the development server +Port for the dev server + +# flags.host.summary + +Host to bind to + +# flags.no-open.summary + +Do not automatically open the browser # examples - Start the development server: - <%= config.bin %> <%= command.id %> + <%= config.bin %> <%= command.id %> --name myWebApp + +- Start the development server with a specific target: -- Start the development server for a specific web app: + <%= config.bin %> <%= command.id %> --name myWebApp --target "LightningApp" - <%= config.bin %> <%= command.id %> --name myWebApp +- Start the development server on a custom port and host: + + <%= config.bin %> <%= command.id %> --name myWebApp --port 8080 --host 0.0.0.0 + +- Start the development server with custom root directory: + + <%= config.bin %> <%= command.id %> --name myWebApp --root-dir ./webapps/myWebApp -- Start the development server on a custom port: +- Start the development server without opening the browser: - <%= config.bin %> <%= command.id %> --name myWebApp --port 8080 + <%= config.bin %> <%= command.id %> --name myWebApp --no-open diff --git a/schemas/webapp-dev.json b/schemas/webapp-dev.json index 4a73aca..1bdcd96 100644 --- a/schemas/webapp-dev.json +++ b/schemas/webapp-dev.json @@ -8,14 +8,26 @@ "name": { "type": "string" }, + "target": { + "type": "string" + }, + "rootDir": { + "type": "string" + }, "port": { "type": "number" }, + "host": { + "type": "string" + }, + "noOpen": { + "type": "boolean" + }, "success": { "type": "boolean" } }, - "required": ["port", "success"], + "required": ["name", "port", "host", "noOpen", "success"], "additionalProperties": false } } diff --git a/src/commands/webapp/dev.ts b/src/commands/webapp/dev.ts index 8c7cfa4..89a8b06 100644 --- a/src/commands/webapp/dev.ts +++ b/src/commands/webapp/dev.ts @@ -21,8 +21,12 @@ Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); const messages = Messages.loadMessages('@salesforce/plugin-webapp', 'webapp.dev'); export type WebappDevResult = { - name?: string; + name: string; + target?: string; + rootDir?: string; port: number; + host: string; + noOpen: boolean; success: boolean; }; @@ -35,37 +39,71 @@ export default class WebappDev extends SfCommand { name: Flags.string({ summary: messages.getMessage('flags.name.summary'), char: 'n', + required: true, + }), + target: Flags.string({ + summary: messages.getMessage('flags.target.summary'), + char: 't', + required: false, + }), + 'root-dir': Flags.string({ + summary: messages.getMessage('flags.root-dir.summary'), + char: 'r', required: false, }), port: Flags.integer({ summary: messages.getMessage('flags.port.summary'), char: 'p', - default: 3000, + default: 8080, + }), + host: Flags.string({ + summary: messages.getMessage('flags.host.summary'), + default: 'localhost', + }), + 'no-open': Flags.boolean({ + summary: messages.getMessage('flags.no-open.summary'), + default: false, }), }; public async run(): Promise { const { flags } = await this.parse(WebappDev); - if (flags.name) { - this.log(`Starting development server for web app: ${flags.name}`); + this.log(`Starting development server for web app: ${flags.name}`); + + if (flags.target) { + this.log(`Using target: ${flags.target}`); } else { - this.log('Starting development server for web app...'); + this.log('Using default target from Web Application configuration'); + } + + if (flags['root-dir']) { + this.log(`Root directory: ${flags['root-dir']}`); } - this.log(`Preview server running on port: ${flags.port}`); - this.log('Preview your web app locally without needing to deploy'); + this.log(`Server running on http://${flags.host}:${flags.port}`); + + if (!flags['no-open']) { + this.log('Opening browser...'); + } // TODO: Implement local development server logic // This would typically involve: - // 1. Starting a local development server - // 2. Watching for file changes - // 3. Hot reloading functionality - // 4. Proxying API calls to Salesforce org if needed + // 1. Resolving the Web Application metadata from CMS + // 2. Reading configuration (targets, routes, etc.) + // 3. Deriving or using the specified local path + // 4. Starting a local development server on specified host:port + // 5. Watching for file changes with hot reloading + // 6. Opening browser automatically unless --no-open is set + // 7. Proxying API calls to Salesforce org if needed return { name: flags.name, + target: flags.target, + rootDir: flags['root-dir'], port: flags.port, + host: flags.host, + noOpen: flags['no-open'], success: true, }; } diff --git a/test/commands/webapp/dev.nut.ts b/test/commands/webapp/dev.nut.ts index 5d37797..0b26353 100644 --- a/test/commands/webapp/dev.nut.ts +++ b/test/commands/webapp/dev.nut.ts @@ -27,10 +27,34 @@ describe('webapp dev NUTs', () => { await session?.clean(); }); - it('should display provided name', () => { - const name = 'World'; - const command = `webapp dev --name ${name}`; + it('should start dev server with name', () => { + const command = 'webapp dev --name myWebApp'; const output = execCmd(command, { ensureExitCode: 0 }).shellOutput.stdout; - expect(output).to.contain(name); + expect(output).to.contain('myWebApp'); + expect(output).to.contain('Server running on http://localhost:8080'); + }); + + it('should start dev server with target', () => { + const command = 'webapp dev --name myWebApp --target "LightningApp"'; + const output = execCmd(command, { ensureExitCode: 0 }).shellOutput.stdout; + expect(output).to.contain('Using target: LightningApp'); + }); + + it('should start dev server with custom port and host', () => { + const command = 'webapp dev --name myWebApp --port 9000 --host 0.0.0.0'; + const output = execCmd(command, { ensureExitCode: 0 }).shellOutput.stdout; + expect(output).to.contain('Server running on http://0.0.0.0:9000'); + }); + + it('should start dev server with root-dir', () => { + const command = 'webapp dev --name myWebApp --root-dir ./webapps/myWebApp'; + const output = execCmd(command, { ensureExitCode: 0 }).shellOutput.stdout; + expect(output).to.contain('Root directory: ./webapps/myWebApp'); + }); + + it('should start dev server with no-open flag', () => { + const command = 'webapp dev --name myWebApp --no-open'; + const output = execCmd(command, { ensureExitCode: 0 }).shellOutput.stdout; + expect(output).to.not.contain('Opening browser...'); }); }); diff --git a/test/commands/webapp/dev.test.ts b/test/commands/webapp/dev.test.ts index 9a8fa52..739135d 100644 --- a/test/commands/webapp/dev.test.ts +++ b/test/commands/webapp/dev.test.ts @@ -30,23 +30,70 @@ describe('webapp dev', () => { $$.restore(); }); - it('runs dev server without name', async () => { - const result = await WebappDev.run([]); - expect(result.port).to.equal(3000); + it('runs dev server with required name', async () => { + const result = await WebappDev.run(['--name', 'myWebApp']); + expect(result.name).to.equal('myWebApp'); + expect(result.port).to.equal(8080); + expect(result.host).to.equal('localhost'); + expect(result.noOpen).to.be.false; expect(result.success).to.be.true; - expect(result.name).to.be.undefined; }); - it('runs dev server with name', async () => { - const result = await WebappDev.run(['--name', 'myWebApp']); + it('runs dev server with custom port', async () => { + const result = await WebappDev.run(['--name', 'myWebApp', '--port', '3000']); expect(result.name).to.equal('myWebApp'); expect(result.port).to.equal(3000); expect(result.success).to.be.true; }); - it('runs dev server with custom port', async () => { - const result = await WebappDev.run(['--port', '8080']); - expect(result.port).to.equal(8080); + it('runs dev server with custom host', async () => { + const result = await WebappDev.run(['--name', 'myWebApp', '--host', '0.0.0.0']); + expect(result.name).to.equal('myWebApp'); + expect(result.host).to.equal('0.0.0.0'); + expect(result.success).to.be.true; + }); + + it('runs dev server with target', async () => { + const result = await WebappDev.run(['--name', 'myWebApp', '--target', 'LightningApp']); + expect(result.name).to.equal('myWebApp'); + expect(result.target).to.equal('LightningApp'); + expect(result.success).to.be.true; + }); + + it('runs dev server with root-dir', async () => { + const result = await WebappDev.run(['--name', 'myWebApp', '--root-dir', './webapps/myWebApp']); + expect(result.name).to.equal('myWebApp'); + expect(result.rootDir).to.equal('./webapps/myWebApp'); + expect(result.success).to.be.true; + }); + + it('runs dev server with no-open flag', async () => { + const result = await WebappDev.run(['--name', 'myWebApp', '--no-open']); + expect(result.name).to.equal('myWebApp'); + expect(result.noOpen).to.be.true; + expect(result.success).to.be.true; + }); + + it('runs dev server with all flags', async () => { + const result = await WebappDev.run([ + '--name', + 'myWebApp', + '--target', + 'Site', + '--root-dir', + './webapps/test', + '--port', + '9000', + '--host', + '127.0.0.1', + '--no-open', + ]); + expect(result.name).to.equal('myWebApp'); + expect(result.target).to.equal('Site'); + expect(result.rootDir).to.equal('./webapps/test'); + expect(result.port).to.equal(9000); + expect(result.host).to.equal('127.0.0.1'); + expect(result.noOpen).to.be.true; expect(result.success).to.be.true; }); @@ -57,6 +104,34 @@ describe('webapp dev', () => { .flatMap((c) => c.args) .join('\n'); expect(output).to.include('Starting development server for web app: testApp'); - expect(output).to.include('Preview server running on port: 3000'); + expect(output).to.include('Server running on http://localhost:8080'); + expect(output).to.include('Opening browser...'); + }); + + it('outputs target message when specified', async () => { + await WebappDev.run(['--name', 'testApp', '--target', 'LightningApp']); + const output = sfCommandStubs.log + .getCalls() + .flatMap((c) => c.args) + .join('\n'); + expect(output).to.include('Using target: LightningApp'); + }); + + it('outputs default target message when not specified', async () => { + await WebappDev.run(['--name', 'testApp']); + const output = sfCommandStubs.log + .getCalls() + .flatMap((c) => c.args) + .join('\n'); + expect(output).to.include('Using default target from Web Application configuration'); + }); + + it('does not output browser message with no-open', async () => { + await WebappDev.run(['--name', 'testApp', '--no-open']); + const output = sfCommandStubs.log + .getCalls() + .flatMap((c) => c.args) + .join('\n'); + expect(output).to.not.include('Opening browser...'); }); }); From dc18bf462d9620d22b042d48730545c2e8b7c07f Mon Sep 17 00:00:00 2001 From: shinoni Date: Wed, 5 Nov 2025 15:10:23 -0800 Subject: [PATCH 07/23] docs: simplify webapp generate description --- messages/webapp.generate.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/webapp.generate.md b/messages/webapp.generate.md index e8376c0..741c8e0 100644 --- a/messages/webapp.generate.md +++ b/messages/webapp.generate.md @@ -4,7 +4,7 @@ Create a web app and associated metadata # description -Generate a new Salesforce web app with the specified configuration. This command creates the basic structure, metadata, and configuration files needed for your web application. +Create a web app and associated metadata # flags.name.summary From c995404f788220c37cf558ac67ff8a0de0f2c5fc Mon Sep 17 00:00:00 2001 From: shinoni Date: Wed, 5 Nov 2025 15:11:55 -0800 Subject: [PATCH 08/23] docs: simplify webapp deploy description --- messages/webapp.deploy.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/webapp.deploy.md b/messages/webapp.deploy.md index b829f9c..7bd5302 100644 --- a/messages/webapp.deploy.md +++ b/messages/webapp.deploy.md @@ -4,7 +4,7 @@ Deploy the web app, its assets and associated metadata # description -This command builds and deploys your web app and associated metadata to your Salesforce org. It packages all assets, applies configurations, and ensures proper deployment of all components. The default "build" option will run the necessary commands to produce the bundle and metadata to deploy your web app. +Deploy the web app, its assets and associated metadata # flags.name.summary From 4ec88a7883341aae37a3d6eea69283a286ef0ab8 Mon Sep 17 00:00:00 2001 From: shinoni Date: Wed, 5 Nov 2025 15:17:29 -0800 Subject: [PATCH 09/23] docs: update webapp deploy description to avoid duplication --- messages/webapp.deploy.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/webapp.deploy.md b/messages/webapp.deploy.md index 7bd5302..c48b0ed 100644 --- a/messages/webapp.deploy.md +++ b/messages/webapp.deploy.md @@ -4,7 +4,7 @@ Deploy the web app, its assets and associated metadata # description -Deploy the web app, its assets and associated metadata +Build and deploy your web app to your Salesforce org # flags.name.summary From 4df6aeb3f788178631536539b1f1abf44cc3ac94 Mon Sep 17 00:00:00 2001 From: shinoni Date: Wed, 5 Nov 2025 15:18:45 -0800 Subject: [PATCH 10/23] docs: revert webapp deploy description to match summary --- messages/webapp.deploy.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/webapp.deploy.md b/messages/webapp.deploy.md index c48b0ed..7bd5302 100644 --- a/messages/webapp.deploy.md +++ b/messages/webapp.deploy.md @@ -4,7 +4,7 @@ Deploy the web app, its assets and associated metadata # description -Build and deploy your web app to your Salesforce org +Deploy the web app, its assets and associated metadata # flags.name.summary From e11684c1e683f50204f406edbc7a3ed09942a9fe Mon Sep 17 00:00:00 2001 From: shinoni Date: Wed, 5 Nov 2025 15:31:46 -0800 Subject: [PATCH 11/23] docs: simplify webapp dev name flag description --- 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 d5d5cf0..490d307 100644 --- a/messages/webapp.dev.md +++ b/messages/webapp.dev.md @@ -8,7 +8,7 @@ Starts a local development server for a Web Application defined in CMS, using th # flags.name.summary -Identifies the Web Application (CMS MD) to use +Identifies the Web Application # flags.target.summary From 1996678738de8cceac4ae9c219d8eec1e4970534 Mon Sep 17 00:00:00 2001 From: shinoni Date: Wed, 5 Nov 2025 15:35:49 -0800 Subject: [PATCH 12/23] docs: simplify webapp dev description --- 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 490d307..cd50c2b 100644 --- a/messages/webapp.dev.md +++ b/messages/webapp.dev.md @@ -4,7 +4,7 @@ Preview a web app locally without needing to deploy # description -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. +Starts a local development server for a Web Application, using the local project files. This enables rapid development with hot reloading and immediate feedback. # flags.name.summary From 87997f21af1baebfb7624c56bd7d54cce7c1679e Mon Sep 17 00:00:00 2001 From: shinoni Date: Wed, 5 Nov 2025 15:39:00 -0800 Subject: [PATCH 13/23] docs: add detailed description and validate example for webapp deploy --- messages/webapp.deploy.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/messages/webapp.deploy.md b/messages/webapp.deploy.md index 7bd5302..3bf44fe 100644 --- a/messages/webapp.deploy.md +++ b/messages/webapp.deploy.md @@ -4,7 +4,7 @@ Deploy the web app, its assets and associated metadata # description -Deploy the web app, its assets and associated metadata +This command builds and deploys your web app, its assets, and associated metadata to your Salesforce org. Use the build option to package and deploy, or the validate option to check deployment without making changes. # flags.name.summary @@ -20,6 +20,10 @@ Deployment options (build or validate) <%= config.bin %> <%= command.id %> --name myWebApp -- Deploy a web app with specific options: +- Deploy a web app with build option: <%= config.bin %> <%= command.id %> --name myWebApp --options build + +- Validate a web app deployment: + + <%= config.bin %> <%= command.id %> --name myWebApp --options validate From a2a695c87c4bd2c9d3c3981261f5399f06cc5935 Mon Sep 17 00:00:00 2001 From: shinoni Date: Wed, 5 Nov 2025 15:41:11 -0800 Subject: [PATCH 14/23] docs: refine webapp deploy description wording --- messages/webapp.deploy.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/webapp.deploy.md b/messages/webapp.deploy.md index 3bf44fe..1554a3b 100644 --- a/messages/webapp.deploy.md +++ b/messages/webapp.deploy.md @@ -4,7 +4,7 @@ Deploy the web app, its assets and associated metadata # description -This command builds and deploys your web app, its assets, and associated metadata to your Salesforce org. Use the build option to package and deploy, or the validate option to check deployment without making changes. +This command deploys your web app, its assets, and associated metadata to your Salesforce org. Use the build option to build and deploy, or the validate option to check deployment without making changes. # flags.name.summary From fa5d8e2aad384b02b36208ad3e8f3092be0015d0 Mon Sep 17 00:00:00 2001 From: shinoni Date: Wed, 5 Nov 2025 15:44:15 -0800 Subject: [PATCH 15/23] docs: add detailed description for webapp generate --- messages/webapp.generate.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/webapp.generate.md b/messages/webapp.generate.md index 741c8e0..e213308 100644 --- a/messages/webapp.generate.md +++ b/messages/webapp.generate.md @@ -4,7 +4,7 @@ Create a web app and associated metadata # description -Create a web app and associated metadata +This command creates a new web app with the specified configuration, including the basic structure and metadata files. You can specify a target platform (Site, Embed, or Lightning), use a template for quick setup, or run in interactive wizard mode for guided configuration. # flags.name.summary From 3c3fa0de07c044d9220b46a8e266e9f36f970ad6 Mon Sep 17 00:00:00 2001 From: shinoni Date: Wed, 5 Nov 2025 15:45:32 -0800 Subject: [PATCH 16/23] docs: simplify webapp generate description --- messages/webapp.generate.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/webapp.generate.md b/messages/webapp.generate.md index e213308..e19966e 100644 --- a/messages/webapp.generate.md +++ b/messages/webapp.generate.md @@ -4,7 +4,7 @@ Create a web app and associated metadata # description -This command creates a new web app with the specified configuration, including the basic structure and metadata files. You can specify a target platform (Site, Embed, or Lightning), use a template for quick setup, or run in interactive wizard mode for guided configuration. +This command creates a new web app with the specified configuration, including the basic structure and metadata files. # flags.name.summary From c678dae01c4d72b6f61adb3d58ece1c0d1004428 Mon Sep 17 00:00:00 2001 From: shinoni Date: Wed, 5 Nov 2025 15:48:22 -0800 Subject: [PATCH 17/23] docs: add .ignore file explanation to webapp retrieve --- messages/webapp.retrieve.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/messages/webapp.retrieve.md b/messages/webapp.retrieve.md index 42c744f..480ccab 100644 --- a/messages/webapp.retrieve.md +++ b/messages/webapp.retrieve.md @@ -6,6 +6,8 @@ Retrieve the web app, its assets and associated metadata This command retrieves your web app, its assets, and associated metadata from your Salesforce org to your local environment. Useful for syncing remote changes or setting up a local development environment. +You can create a project-specific `.ignore` file (similar to `.gitignore`) to exclude files from retrieval. For example, add `node_modules` to ignore dependency directories, or `dist/**` to skip build artifacts. + # flags.name.summary Name of your web app to retrieve From e9e9fda70ca74e9df980dc5df25f600347cdeba4 Mon Sep 17 00:00:00 2001 From: shinoni Date: Wed, 5 Nov 2025 15:49:10 -0800 Subject: [PATCH 18/23] revert: remove .ignore file documentation from webapp retrieve --- messages/webapp.retrieve.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/messages/webapp.retrieve.md b/messages/webapp.retrieve.md index 480ccab..42c744f 100644 --- a/messages/webapp.retrieve.md +++ b/messages/webapp.retrieve.md @@ -6,8 +6,6 @@ Retrieve the web app, its assets and associated metadata This command retrieves your web app, its assets, and associated metadata from your Salesforce org to your local environment. Useful for syncing remote changes or setting up a local development environment. -You can create a project-specific `.ignore` file (similar to `.gitignore`) to exclude files from retrieval. For example, add `node_modules` to ignore dependency directories, or `dist/**` to skip build artifacts. - # flags.name.summary Name of your web app to retrieve From 190613563df3deb593c740e5732b3a4b0f077236 Mon Sep 17 00:00:00 2001 From: shinoni Date: Wed, 5 Nov 2025 16:25:37 -0800 Subject: [PATCH 19/23] docs: add comprehensive command reference documentation --- COMMANDS.md | 211 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 COMMANDS.md diff --git a/COMMANDS.md b/COMMANDS.md new file mode 100644 index 0000000..b182235 --- /dev/null +++ b/COMMANDS.md @@ -0,0 +1,211 @@ +# Commands + + + +- [`sf webapp deploy`](#sf-webapp-deploy) +- [`sf webapp dev`](#sf-webapp-dev) +- [`sf webapp generate`](#sf-webapp-generate) +- [`sf webapp retrieve`](#sf-webapp-retrieve) +- [`sf webapp version create`](#sf-webapp-version-create) + +## `sf webapp deploy` + +Deploy the web app, its assets and associated metadata + +``` +USAGE + $ sf webapp deploy -n [--json] [--flags-dir ] [-o build|validate] + +FLAGS + -n, --name= (required) Name of your web app + -o, --options=