From 0efa415a0c285617336b7171b11486093fd87e02 Mon Sep 17 00:00:00 2001 From: aman06it <36499868+aman06it@users.noreply.github.com> Date: Fri, 22 May 2026 16:08:35 +0530 Subject: [PATCH 1/6] feat: add microfrontend template generator command Exposes `sf template generate microfrontend` to scaffold a microfrontend LWC bundle, paired with the new generator in salesforcedx-templates. --- command-snapshot.json | 19 ++++ messages/microfrontend.md | 51 +++++++++++ .../template/generate/microfrontend.ts | 89 +++++++++++++++++++ 3 files changed, 159 insertions(+) create mode 100644 messages/microfrontend.md create mode 100644 src/commands/template/generate/microfrontend.ts diff --git a/command-snapshot.json b/command-snapshot.json index 2d0b1663..af08956c 100644 --- a/command-snapshot.json +++ b/command-snapshot.json @@ -103,6 +103,25 @@ "flags": ["api-version", "flags-dir", "internal", "json", "loglevel", "name", "output-dir", "template"], "plugin": "@salesforce/plugin-templates" }, + { + "alias": [], + "command": "template:generate:microfrontend", + "flagAliases": ["apiversion", "outputdir"], + "flagChars": ["d", "i", "n", "s"], + "flags": [ + "api-version", + "flags-dir", + "internal", + "json", + "loglevel", + "name", + "output-dir", + "sandbox", + "shell-title", + "src" + ], + "plugin": "@salesforce/plugin-templates" + }, { "alias": ["force:project:create", "project:generate"], "command": "template:generate:project", diff --git a/messages/microfrontend.md b/messages/microfrontend.md new file mode 100644 index 00000000..6de05278 --- /dev/null +++ b/messages/microfrontend.md @@ -0,0 +1,51 @@ +# examples + +- Generate an MFE wrapper LWC in the current directory: + + <%= config.bin %> <%= command.id %> --name MyMfeWrapper --src https://app.example.com --sandbox allow-forms --shell-title "Expense Report Widget" + +- Generate an MFE wrapper LWC in the "force-app/main/default/lwc" directory with multiple sandbox tokens: + + <%= config.bin %> <%= command.id %> --name MyMfeWrapper --src https://app.example.com --sandbox allow-forms --sandbox allow-scripts --shell-title "Expense Report Widget" --output-dir force-app/main/default/lwc + +# summary + +Generate a Lightning Web Component that wraps the lightning-mfe-shell base component. + +# description + +Generates a Lightning Web Component bundle that consumes the first-party component, pre-wired with the three required attributes: the widget URL (src), iframe sandbox tokens, and an accessible iframe title (shell-title). + +The generated bundle contains four files (.html, .js, .js-meta.xml, .css) in a directory named with the camelCased component name. The bundle must live under a parent folder named "lwc". + +# flags.name.summary + +PascalCase name of the generated component. + +# flags.name.description + +The component name is also used (camelCased) as the LWC folder name and file stem. Must contain only alphanumeric characters and start with a letter. + +# flags.src.summary + +Absolute https URL the iframe will load. + +# flags.src.description + +The URL is bound to the "src" attribute as a reactive property in the generated LWC. Must use https; plain http is only allowed for localhost or 127.0.0.1 (for local development). + +# flags.sandbox.summary + +Iframe sandbox token (specify the flag multiple times to set more than one token). + +# flags.sandbox.description + +Each token is written into the space-separated "sandbox" attribute on . Only W3C-defined sandbox tokens are accepted. + +# flags.shell-title.summary + +Accessible title for the embedded iframe. + +# flags.shell-title.description + +Written to the "shell-title" attribute on and used as the iframe's accessible name (announced by screen readers). Must be a non-empty string. diff --git a/src/commands/template/generate/microfrontend.ts b/src/commands/template/generate/microfrontend.ts new file mode 100644 index 00000000..0e5746e9 --- /dev/null +++ b/src/commands/template/generate/microfrontend.ts @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2026, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { Flags, loglevel, orgApiVersionFlagWithDeprecations, SfCommand, Ux } from '@salesforce/sf-plugins-core'; +import { CreateOutput, MicrofrontendOptions, TemplateType } from '@salesforce/templates'; +import { Messages } from '@salesforce/core'; +import { getCustomTemplates, runGenerator } from '../../../utils/templateCommand.js'; +import { internalFlag, outputDirFlagLightning } from '../../../utils/flags.js'; + +Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); +const messages = Messages.loadMessages('@salesforce/plugin-templates', 'microfrontend'); + +// Keep in-sync with VALID_SANDBOX_TOKENS in salesforcedx-templates/src/generators/microfrontendGenerator.ts +const SANDBOX_TOKENS = [ + 'allow-forms', + 'allow-modals', + 'allow-orientation-lock', + 'allow-pointer-lock', + 'allow-popups', + 'allow-popups-to-escape-sandbox', + 'allow-presentation', + 'allow-same-origin', + 'allow-scripts', + 'allow-storage-access-by-user-activation', + 'allow-top-navigation', + 'allow-top-navigation-by-user-activation', +] as const; + +export default class Microfrontend 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 hidden = true; + + public static readonly flags = { + name: Flags.string({ + char: 'n', + summary: messages.getMessage('flags.name.summary'), + description: messages.getMessage('flags.name.description'), + required: true, + }), + src: Flags.string({ + char: 's', + summary: messages.getMessage('flags.src.summary'), + description: messages.getMessage('flags.src.description'), + required: true, + }), + sandbox: Flags.option({ + summary: messages.getMessage('flags.sandbox.summary'), + description: messages.getMessage('flags.sandbox.description'), + options: SANDBOX_TOKENS, + multiple: true, + required: true, + })(), + 'shell-title': Flags.string({ + summary: messages.getMessage('flags.shell-title.summary'), + description: messages.getMessage('flags.shell-title.description'), + }), + 'output-dir': outputDirFlagLightning, + 'api-version': orgApiVersionFlagWithDeprecations, + internal: internalFlag, + loglevel, + }; + + public async run(): Promise { + const { flags } = await this.parse(Microfrontend); + + const flagsAsOptions: MicrofrontendOptions = { + componentname: flags.name, + src: flags.src, + sandbox: flags.sandbox.join(' '), + shellTitle: flags['shell-title'] ?? flags.name, + outputdir: flags['output-dir'], + apiversion: flags['api-version'], + internal: flags.internal, + }; + + return runGenerator({ + templateType: TemplateType.Microfrontend, + opts: flagsAsOptions, + ux: new Ux({ jsonEnabled: this.jsonEnabled() }), + templates: getCustomTemplates(this.configAggregator), + }); + } +} From d72b7f5c5f2d7381f7eeefa0a29b6e43f7ba3a58 Mon Sep 17 00:00:00 2001 From: AMAN SINGH <36499868+aman06it@users.noreply.github.com> Date: Wed, 27 May 2026 21:45:41 +0530 Subject: [PATCH 2/6] refactor: rename microfrontend to lightning embedding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per team alignment in #plugin-design-templates, rename the command to align with the base component name. - Command: sf template generate microfrontend → sf template generate lightning embedding - Move src/commands/template/generate/microfrontend.ts under lightning/ as a peer of app, component, event, interface, test - messages/microfrontend.md → messages/lightningEmbedding.md - Class Microfrontend → LightningEmbedding - Imports updated: MicrofrontendOptions → LightningEmbeddingOptions, TemplateType.Microfrontend → TemplateType.LightningEmbedding --- command-snapshot.json | 38 +++++++++---------- ...microfrontend.md => lightningEmbedding.md} | 18 ++++----- .../embedding.ts} | 18 ++++----- 3 files changed, 37 insertions(+), 37 deletions(-) rename messages/{microfrontend.md => lightningEmbedding.md} (59%) rename src/commands/template/generate/{microfrontend.ts => lightning/embedding.ts} (81%) diff --git a/command-snapshot.json b/command-snapshot.json index af08956c..fa7af857 100644 --- a/command-snapshot.json +++ b/command-snapshot.json @@ -79,6 +79,25 @@ "flags": ["api-version", "flags-dir", "internal", "json", "loglevel", "name", "output-dir", "template", "type"], "plugin": "@salesforce/plugin-templates" }, + { + "alias": [], + "command": "template:generate:lightning:embedding", + "flagAliases": ["apiversion", "outputdir"], + "flagChars": ["d", "i", "n", "s"], + "flags": [ + "api-version", + "flags-dir", + "internal", + "json", + "loglevel", + "name", + "output-dir", + "sandbox", + "shell-title", + "src" + ], + "plugin": "@salesforce/plugin-templates" + }, { "alias": ["force:lightning:event:create", "lightning:generate:event"], "command": "template:generate:lightning:event", @@ -103,25 +122,6 @@ "flags": ["api-version", "flags-dir", "internal", "json", "loglevel", "name", "output-dir", "template"], "plugin": "@salesforce/plugin-templates" }, - { - "alias": [], - "command": "template:generate:microfrontend", - "flagAliases": ["apiversion", "outputdir"], - "flagChars": ["d", "i", "n", "s"], - "flags": [ - "api-version", - "flags-dir", - "internal", - "json", - "loglevel", - "name", - "output-dir", - "sandbox", - "shell-title", - "src" - ], - "plugin": "@salesforce/plugin-templates" - }, { "alias": ["force:project:create", "project:generate"], "command": "template:generate:project", diff --git a/messages/microfrontend.md b/messages/lightningEmbedding.md similarity index 59% rename from messages/microfrontend.md rename to messages/lightningEmbedding.md index 6de05278..b2d3cbd7 100644 --- a/messages/microfrontend.md +++ b/messages/lightningEmbedding.md @@ -1,20 +1,20 @@ # examples -- Generate an MFE wrapper LWC in the current directory: +- Generate an embedding wrapper LWC in the current directory: - <%= config.bin %> <%= command.id %> --name MyMfeWrapper --src https://app.example.com --sandbox allow-forms --shell-title "Expense Report Widget" + <%= config.bin %> <%= command.id %> --name MyEmbeddingWrapper --src https://app.example.com --sandbox allow-forms --shell-title "Expense Report Widget" -- Generate an MFE wrapper LWC in the "force-app/main/default/lwc" directory with multiple sandbox tokens: +- Generate an embedding wrapper LWC in the "force-app/main/default/lwc" directory with multiple sandbox tokens: - <%= config.bin %> <%= command.id %> --name MyMfeWrapper --src https://app.example.com --sandbox allow-forms --sandbox allow-scripts --shell-title "Expense Report Widget" --output-dir force-app/main/default/lwc + <%= config.bin %> <%= command.id %> --name MyEmbeddingWrapper --src https://app.example.com --sandbox allow-forms --sandbox allow-scripts --shell-title "Expense Report Widget" --output-dir force-app/main/default/lwc # summary -Generate a Lightning Web Component that wraps the lightning-mfe-shell base component. +Generate a Lightning Web Component that wraps the lightning-embedding base component. # description -Generates a Lightning Web Component bundle that consumes the first-party component, pre-wired with the three required attributes: the widget URL (src), iframe sandbox tokens, and an accessible iframe title (shell-title). +Generates a Lightning Web Component bundle that consumes the first-party component, pre-wired with the three required attributes: the widget URL (src), iframe sandbox tokens, and an accessible iframe title (shell-title). The generated bundle contains four files (.html, .js, .js-meta.xml, .css) in a directory named with the camelCased component name. The bundle must live under a parent folder named "lwc". @@ -32,7 +32,7 @@ Absolute https URL the iframe will load. # flags.src.description -The URL is bound to the "src" attribute as a reactive property in the generated LWC. Must use https; plain http is only allowed for localhost or 127.0.0.1 (for local development). +The URL is bound to the "src" attribute as a reactive property in the generated LWC. Must use https; plain http is only allowed for localhost or 127.0.0.1 (for local development). # flags.sandbox.summary @@ -40,7 +40,7 @@ Iframe sandbox token (specify the flag multiple times to set more than one token # flags.sandbox.description -Each token is written into the space-separated "sandbox" attribute on . Only W3C-defined sandbox tokens are accepted. +Each token is written into the space-separated "sandbox" attribute on . Only W3C-defined sandbox tokens are accepted. # flags.shell-title.summary @@ -48,4 +48,4 @@ Accessible title for the embedded iframe. # flags.shell-title.description -Written to the "shell-title" attribute on and used as the iframe's accessible name (announced by screen readers). Must be a non-empty string. +Written to the "shell-title" attribute on and used as the iframe's accessible name (announced by screen readers). Must be a non-empty string. diff --git a/src/commands/template/generate/microfrontend.ts b/src/commands/template/generate/lightning/embedding.ts similarity index 81% rename from src/commands/template/generate/microfrontend.ts rename to src/commands/template/generate/lightning/embedding.ts index 0e5746e9..14495319 100644 --- a/src/commands/template/generate/microfrontend.ts +++ b/src/commands/template/generate/lightning/embedding.ts @@ -6,15 +6,15 @@ */ import { Flags, loglevel, orgApiVersionFlagWithDeprecations, SfCommand, Ux } from '@salesforce/sf-plugins-core'; -import { CreateOutput, MicrofrontendOptions, TemplateType } from '@salesforce/templates'; +import { CreateOutput, LightningEmbeddingOptions, TemplateType } from '@salesforce/templates'; import { Messages } from '@salesforce/core'; -import { getCustomTemplates, runGenerator } from '../../../utils/templateCommand.js'; -import { internalFlag, outputDirFlagLightning } from '../../../utils/flags.js'; +import { getCustomTemplates, runGenerator } from '../../../../utils/templateCommand.js'; +import { internalFlag, outputDirFlagLightning } from '../../../../utils/flags.js'; Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); -const messages = Messages.loadMessages('@salesforce/plugin-templates', 'microfrontend'); +const messages = Messages.loadMessages('@salesforce/plugin-templates', 'lightningEmbedding'); -// Keep in-sync with VALID_SANDBOX_TOKENS in salesforcedx-templates/src/generators/microfrontendGenerator.ts +// Keep in-sync with VALID_SANDBOX_TOKENS in salesforcedx-templates/src/generators/lightningEmbeddingGenerator.ts const SANDBOX_TOKENS = [ 'allow-forms', 'allow-modals', @@ -30,7 +30,7 @@ const SANDBOX_TOKENS = [ 'allow-top-navigation-by-user-activation', ] as const; -export default class Microfrontend extends SfCommand { +export default class LightningEmbedding extends SfCommand { public static readonly summary = messages.getMessage('summary'); public static readonly description = messages.getMessage('description'); public static readonly examples = messages.getMessages('examples'); @@ -67,9 +67,9 @@ export default class Microfrontend extends SfCommand { }; public async run(): Promise { - const { flags } = await this.parse(Microfrontend); + const { flags } = await this.parse(LightningEmbedding); - const flagsAsOptions: MicrofrontendOptions = { + const flagsAsOptions: LightningEmbeddingOptions = { componentname: flags.name, src: flags.src, sandbox: flags.sandbox.join(' '), @@ -80,7 +80,7 @@ export default class Microfrontend extends SfCommand { }; return runGenerator({ - templateType: TemplateType.Microfrontend, + templateType: TemplateType.LightningEmbedding, opts: flagsAsOptions, ux: new Ux({ jsonEnabled: this.jsonEnabled() }), templates: getCustomTemplates(this.configAggregator), From 6c7d94f1ef2cbf9874fd9cee447982f1421b53a9 Mon Sep 17 00:00:00 2001 From: AMAN SINGH <36499868+aman06it@users.noreply.github.com> Date: Thu, 28 May 2026 13:30:21 +0530 Subject: [PATCH 3/6] refactor: address review feedback on lightning embedding command Per Eric's review (#943): - Import sandbox token list and src URL validator from @salesforce/templates instead of duplicating them locally; single source of truth. - Validate --src at the CLI parse layer (custom parse method) for faster user feedback on bad URLs. - Make --shell-title required; drop the PascalCase fallback to --name since the iframe accessible name should be human-friendly, not a developer identifier. - Add `state = 'beta'` alongside `hidden = true` so the command shows the beta-warning banner once it's unhidden. --- messages/lightningEmbedding.md | 6 +++- .../template/generate/lightning/embedding.ts | 36 +++++++++---------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/messages/lightningEmbedding.md b/messages/lightningEmbedding.md index b2d3cbd7..9e2e9a6f 100644 --- a/messages/lightningEmbedding.md +++ b/messages/lightningEmbedding.md @@ -34,6 +34,10 @@ Absolute https URL the iframe will load. The URL is bound to the "src" attribute as a reactive property in the generated LWC. Must use https; plain http is only allowed for localhost or 127.0.0.1 (for local development). +# flags.src.error + +The --src flag must be an absolute https URL (e.g., https://app.example.com). Plain http is only allowed for localhost or 127.0.0.1. + # flags.sandbox.summary Iframe sandbox token (specify the flag multiple times to set more than one token). @@ -48,4 +52,4 @@ Accessible title for the embedded iframe. # flags.shell-title.description -Written to the "shell-title" attribute on and used as the iframe's accessible name (announced by screen readers). Must be a non-empty string. +Written to the "shell-title" attribute on and used as the iframe's accessible name (announced by screen readers). diff --git a/src/commands/template/generate/lightning/embedding.ts b/src/commands/template/generate/lightning/embedding.ts index 14495319..2a206395 100644 --- a/src/commands/template/generate/lightning/embedding.ts +++ b/src/commands/template/generate/lightning/embedding.ts @@ -6,7 +6,13 @@ */ import { Flags, loglevel, orgApiVersionFlagWithDeprecations, SfCommand, Ux } from '@salesforce/sf-plugins-core'; -import { CreateOutput, LightningEmbeddingOptions, TemplateType } from '@salesforce/templates'; +import { + CreateOutput, + isAllowedLightningEmbeddingSrcUrl, + LIGHTNING_EMBEDDING_SANDBOX_TOKENS, + LightningEmbeddingOptions, + TemplateType, +} from '@salesforce/templates'; import { Messages } from '@salesforce/core'; import { getCustomTemplates, runGenerator } from '../../../../utils/templateCommand.js'; import { internalFlag, outputDirFlagLightning } from '../../../../utils/flags.js'; @@ -14,26 +20,11 @@ import { internalFlag, outputDirFlagLightning } from '../../../../utils/flags.js Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); const messages = Messages.loadMessages('@salesforce/plugin-templates', 'lightningEmbedding'); -// Keep in-sync with VALID_SANDBOX_TOKENS in salesforcedx-templates/src/generators/lightningEmbeddingGenerator.ts -const SANDBOX_TOKENS = [ - 'allow-forms', - 'allow-modals', - 'allow-orientation-lock', - 'allow-pointer-lock', - 'allow-popups', - 'allow-popups-to-escape-sandbox', - 'allow-presentation', - 'allow-same-origin', - 'allow-scripts', - 'allow-storage-access-by-user-activation', - 'allow-top-navigation', - 'allow-top-navigation-by-user-activation', -] as const; - export default class LightningEmbedding 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 state = 'beta'; public static readonly hidden = true; public static readonly flags = { @@ -48,17 +39,24 @@ export default class LightningEmbedding extends SfCommand { summary: messages.getMessage('flags.src.summary'), description: messages.getMessage('flags.src.description'), required: true, + parse: (input: string) => { + if (!isAllowedLightningEmbeddingSrcUrl(input)) { + throw new Error(messages.getMessage('flags.src.error')); + } + return Promise.resolve(input); + }, }), sandbox: Flags.option({ summary: messages.getMessage('flags.sandbox.summary'), description: messages.getMessage('flags.sandbox.description'), - options: SANDBOX_TOKENS, + options: LIGHTNING_EMBEDDING_SANDBOX_TOKENS, multiple: true, required: true, })(), 'shell-title': Flags.string({ summary: messages.getMessage('flags.shell-title.summary'), description: messages.getMessage('flags.shell-title.description'), + required: true, }), 'output-dir': outputDirFlagLightning, 'api-version': orgApiVersionFlagWithDeprecations, @@ -73,7 +71,7 @@ export default class LightningEmbedding extends SfCommand { componentname: flags.name, src: flags.src, sandbox: flags.sandbox.join(' '), - shellTitle: flags['shell-title'] ?? flags.name, + shellTitle: flags['shell-title'], outputdir: flags['output-dir'], apiversion: flags['api-version'], internal: flags.internal, From e0ed7f3da209a23abd8016c7489c2faf769e8b3e Mon Sep 17 00:00:00 2001 From: AMAN SINGH <36499868+aman06it@users.noreply.github.com> Date: Thu, 28 May 2026 13:48:00 +0530 Subject: [PATCH 4/6] test: add nut tests for lightning embedding command Per Eric's review feedback on #943: cover the CLI surface end-to-end. Happy paths: - bundle scaffolding (4 files) - emitted in html - multiple --sandbox tokens joined into one space-separated attribute - --src URL bound into the generated js - http://localhost accepted for local development Failure paths: - missing --name / --src / --sandbox / --shell-title (no fallback to --name) - http src on non-localhost rejected at parse layer - non-http(s) protocols rejected - malformed URL rejected - unknown sandbox token rejected - output-dir without lwc/ parent rejected --- .../generate/lightning/embedding.nut.ts | 152 ++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 test/commands/template/generate/lightning/embedding.nut.ts diff --git a/test/commands/template/generate/lightning/embedding.nut.ts b/test/commands/template/generate/lightning/embedding.nut.ts new file mode 100644 index 00000000..45d9763e --- /dev/null +++ b/test/commands/template/generate/lightning/embedding.nut.ts @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2026, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +import path from 'node:path'; +import { expect, config } from 'chai'; +import { TestSession, execCmd } from '@salesforce/cli-plugins-testkit'; +import assert from 'yeoman-assert'; + +config.truncateThreshold = 0; + +describe('template generate lightning embedding:', () => { + let session: TestSession; + before(async () => { + session = await TestSession.create({ + project: {}, + devhubAuthStrategy: 'NONE', + }); + }); + after(async () => { + await session?.clean(); + }); + + const lwcDir = (): string => path.join(session.project.dir, 'lwc'); + const bundleFiles = (componentName: string): string[] => { + const camel = componentName.charAt(0).toLowerCase() + componentName.slice(1); + return ['.html', '.js', '.css', '.js-meta.xml'].map((suffix) => path.join(lwcDir(), camel, camel + suffix)); + }; + + describe('Check lightning embedding creation', () => { + const name = 'MyEmbedding'; + const src = 'https://app.example.com'; + const shellTitle = 'Demo Embedding'; + + it('should scaffold an embedding LWC bundle with all four files', () => { + execCmd( + `template generate lightning embedding --name ${name} --src ${src} --sandbox allow-forms --shell-title "${shellTitle}" --output-dir ${lwcDir()}`, + { ensureExitCode: 0 } + ); + assert.file(bundleFiles(name)); + }); + + it('should emit a element in the generated html', () => { + execCmd( + `template generate lightning embedding --name ${name} --src ${src} --sandbox allow-forms --shell-title "${shellTitle}" --output-dir ${lwcDir()}`, + { ensureExitCode: 0 } + ); + const camel = name.charAt(0).toLowerCase() + name.slice(1); + assert.fileContent(path.join(lwcDir(), camel, `${camel}.html`), ' { + execCmd( + `template generate lightning embedding --name MultiSandbox --src ${src} --sandbox allow-forms --sandbox allow-scripts --shell-title "${shellTitle}" --output-dir ${lwcDir()}`, + { ensureExitCode: 0 } + ); + assert.fileContent( + path.join(lwcDir(), 'multiSandbox', 'multiSandbox.html'), + 'sandbox="allow-forms allow-scripts"' + ); + }); + + it('should bind the src URL into the generated js as a reactive property', () => { + execCmd( + `template generate lightning embedding --name SrcBinding --src ${src} --sandbox allow-forms --shell-title "${shellTitle}" --output-dir ${lwcDir()}`, + { ensureExitCode: 0 } + ); + assert.fileContent(path.join(lwcDir(), 'srcBinding', 'srcBinding.js'), src); + }); + + it('should accept http URLs on localhost for local development', () => { + execCmd( + `template generate lightning embedding --name LocalDev --src http://localhost:3000 --sandbox allow-forms --shell-title "${shellTitle}" --output-dir ${lwcDir()}`, + { ensureExitCode: 0 } + ); + assert.fileContent(path.join(lwcDir(), 'localDev', 'localDev.js'), 'http://localhost:3000'); + }); + }); + + describe('lightning embedding failures', () => { + const baseFlags = '--name Foo --sandbox allow-forms --shell-title "Demo"'; + + it('should throw missing --name error', () => { + const stderr = execCmd( + 'template generate lightning embedding --src https://app.example.com --sandbox allow-forms --shell-title "Demo"' + ).shellOutput.stderr; + expect(stderr).to.contain('Missing required flag'); + }); + + it('should throw missing --src error', () => { + const stderr = execCmd( + 'template generate lightning embedding --name Foo --sandbox allow-forms --shell-title "Demo"' + ).shellOutput.stderr; + expect(stderr).to.contain('Missing required flag'); + }); + + it('should throw missing --sandbox error', () => { + const stderr = execCmd( + 'template generate lightning embedding --name Foo --src https://app.example.com --shell-title "Demo"' + ).shellOutput.stderr; + expect(stderr).to.contain('Missing required flag'); + }); + + it('should throw missing --shell-title error (no fallback to --name)', () => { + const stderr = execCmd( + 'template generate lightning embedding --name Foo --src https://app.example.com --sandbox allow-forms' + ).shellOutput.stderr; + expect(stderr).to.contain('Missing required flag'); + }); + + it('should reject http src on a non-localhost host', () => { + const stderr = execCmd( + `template generate lightning embedding --name Foo --src http://attacker.com --sandbox allow-forms --shell-title "Demo" --output-dir ${lwcDir()}` + ).shellOutput.stderr; + expect(stderr).to.contain('https URL'); + }); + + it('should reject non-http(s) protocols', () => { + const stderr = execCmd( + `template generate lightning embedding --name Foo --src ftp://example.com --sandbox allow-forms --shell-title "Demo" --output-dir ${lwcDir()}` + ).shellOutput.stderr; + expect(stderr).to.contain('https URL'); + }); + + it('should reject malformed --src input', () => { + const stderr = execCmd( + `template generate lightning embedding --name Foo --src not-a-url --sandbox allow-forms --shell-title "Demo" --output-dir ${lwcDir()}` + ).shellOutput.stderr; + expect(stderr).to.contain('https URL'); + }); + + it('should reject an unknown sandbox token', () => { + const stderr = execCmd( + `template generate lightning embedding ${baseFlags} --src https://app.example.com --sandbox allow-everything --output-dir ${lwcDir()}`, + { ensureExitCode: 'nonZero' } + ).shellOutput.stderr; + expect(stderr).to.contain('Expected --sandbox'); + }); + + it('should throw missing lwc parent folder error when output-dir is not under lwc/', () => { + const stderr = execCmd( + `template generate lightning embedding --name Foo --src https://app.example.com --sandbox allow-forms --shell-title "Demo" --output-dir ${path.join( + session.project.dir, + 'somewhere-else' + )}` + ).shellOutput.stderr; + expect(stderr).to.match(/lwc/i); + }); + }); +}); From 141cec524236115b99c22b0232fedeff0142a990 Mon Sep 17 00:00:00 2001 From: AMAN SINGH <36499868+aman06it@users.noreply.github.com> Date: Fri, 29 May 2026 11:35:31 +0530 Subject: [PATCH 5/6] docs: apply tech-writer suggestions on lightning embedding messages Apply all 8 suggestions from @jshackell-sfdc on #943: - Add LWC abbreviation in summary - Restructure description for clarity - Tighten flags.name.summary wording - Normalize "https" to "HTTPS" in user-facing strings - Replace "e.g." with "such as" in flags.src.error - Split flags.sandbox.summary into two sentences --- messages/lightningEmbedding.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/messages/lightningEmbedding.md b/messages/lightningEmbedding.md index 9e2e9a6f..e549668b 100644 --- a/messages/lightningEmbedding.md +++ b/messages/lightningEmbedding.md @@ -10,17 +10,17 @@ # summary -Generate a Lightning Web Component that wraps the lightning-embedding base component. +Generate a Lightning Web Component (LWC) bundle that wraps the lightning-embedding base component. # description -Generates a Lightning Web Component bundle that consumes the first-party component, pre-wired with the three required attributes: the widget URL (src), iframe sandbox tokens, and an accessible iframe title (shell-title). +The generated LWC bundle consumes the first-party component, which is pre-wired with the three required attributes: the widget URL (src), iframe sandbox tokens, and an accessible iframe title (shell-title). -The generated bundle contains four files (.html, .js, .js-meta.xml, .css) in a directory named with the camelCased component name. The bundle must live under a parent folder named "lwc". +The generated LWC bundle contains four files (.html, .js, .js-meta.xml, .css) in a directory named with the camelCased component name. The bundle must live under a parent folder named "lwc". # flags.name.summary -PascalCase name of the generated component. +Name of the generated component; must be in PascalCase format. # flags.name.description @@ -28,19 +28,19 @@ The component name is also used (camelCased) as the LWC folder name and file ste # flags.src.summary -Absolute https URL the iframe will load. +Absolute HTTPS URL that the iframe will load. # flags.src.description -The URL is bound to the "src" attribute as a reactive property in the generated LWC. Must use https; plain http is only allowed for localhost or 127.0.0.1 (for local development). +The URL is bound to the "src" attribute as a reactive property in the generated LWC. Must use HTTPS; plain HTTP is allowed only for localhost or 127.0.0.1 (for local development). # flags.src.error -The --src flag must be an absolute https URL (e.g., https://app.example.com). Plain http is only allowed for localhost or 127.0.0.1. +The --src flag must be an absolute HTTPS URL, such as https://app.example.com. Plain HTTP is allowed only for localhost or 127.0.0.1. # flags.sandbox.summary -Iframe sandbox token (specify the flag multiple times to set more than one token). +Iframe sandbox token. Specify this flag multiple times to set more than one token. # flags.sandbox.description From 62ffe615c10b337b67441524c23c1a94c6f699ae Mon Sep 17 00:00:00 2001 From: AMAN SINGH <36499868+aman06it@users.noreply.github.com> Date: Sat, 30 May 2026 13:24:40 +0530 Subject: [PATCH 6/6] chore(deps): bump @salesforce/templates to ^66.9.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The lightning embedding generator was merged in forcedotcom/salesforcedx-templates#821 and published as 66.9.0. Bump the dependency here so CI builds against the published library instead of the yarn-linked local copy. Also updates nut test assertions to match the user-facing wording landed by tech-writer review (https → HTTPS in --src error message). --- package.json | 2 +- .../generate/lightning/embedding.nut.ts | 6 +++--- yarn.lock | 18 +++++++++--------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 0358e834..458a5f9f 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "dependencies": { "@salesforce/core": "^8.28.4", "@salesforce/sf-plugins-core": "^12", - "@salesforce/templates": "^66.7.12" + "@salesforce/templates": "^66.9.0" }, "devDependencies": { "@oclif/plugin-command-snapshot": "^5.3.19", diff --git a/test/commands/template/generate/lightning/embedding.nut.ts b/test/commands/template/generate/lightning/embedding.nut.ts index 45d9763e..61935abf 100644 --- a/test/commands/template/generate/lightning/embedding.nut.ts +++ b/test/commands/template/generate/lightning/embedding.nut.ts @@ -114,21 +114,21 @@ describe('template generate lightning embedding:', () => { const stderr = execCmd( `template generate lightning embedding --name Foo --src http://attacker.com --sandbox allow-forms --shell-title "Demo" --output-dir ${lwcDir()}` ).shellOutput.stderr; - expect(stderr).to.contain('https URL'); + expect(stderr).to.contain('HTTPS URL'); }); it('should reject non-http(s) protocols', () => { const stderr = execCmd( `template generate lightning embedding --name Foo --src ftp://example.com --sandbox allow-forms --shell-title "Demo" --output-dir ${lwcDir()}` ).shellOutput.stderr; - expect(stderr).to.contain('https URL'); + expect(stderr).to.contain('HTTPS URL'); }); it('should reject malformed --src input', () => { const stderr = execCmd( `template generate lightning embedding --name Foo --src not-a-url --sandbox allow-forms --shell-title "Demo" --output-dir ${lwcDir()}` ).shellOutput.stderr; - expect(stderr).to.contain('https URL'); + expect(stderr).to.contain('HTTPS URL'); }); it('should reject an unknown sandbox token', () => { diff --git a/yarn.lock b/yarn.lock index 2c33b048..8a2d2bc2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1695,10 +1695,10 @@ cli-progress "^3.12.0" terminal-link "^3.0.0" -"@salesforce/templates@^66.7.12": - version "66.7.12" - resolved "https://registry.yarnpkg.com/@salesforce/templates/-/templates-66.7.12.tgz#51d5d8af53688c984ca5095f31c3c18ae90d6c7f" - integrity sha512-yfJrJBpanECeFvZ7E+/PA9Xz0GxtO6BxfQ3Vp3PKJiA7yTGpdHMv6EdCDwy7utHxdmA06kq0m5GMD3YGW/qM7A== +"@salesforce/templates@^66.9.0": + version "66.9.0" + resolved "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@salesforce/templates/-/templates-66.9.0.tgz#78755a9409e04f0263248b6d8c16705e4fc2e1a3" + integrity sha512-5eNZvN6GjELQ5KGx3AISH4MmTfit96sTdQYfqLpW0ZgAceS8os5TmHYp7BaZtvPeSsMw9AZMimtYhm0VBjSQtg== dependencies: "@salesforce/kit" "^3.2.6" ejs "^3.1.10" @@ -1706,7 +1706,7 @@ hpagent "^1.2.0" mime-types "^3.0.2" proxy-from-env "^1.1.0" - tar "^7.5.13" + tar "^7.5.15" tslib "^2.8.1" "@salesforce/ts-types@^2.0.11", "@salesforce/ts-types@^2.0.12": @@ -7473,10 +7473,10 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -tar@^7.5.13: - version "7.5.13" - resolved "https://registry.yarnpkg.com/tar/-/tar-7.5.13.tgz#0d214ed56781a26edc313581c0e2d929ceeb866d" - integrity sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng== +tar@^7.5.15: + version "7.5.15" + resolved "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/tar/-/tar-7.5.15.tgz#afe6d1316cddf614a566e3813e42fe01aed46fee" + integrity sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ== dependencies: "@isaacs/fs-minipass" "^4.0.0" chownr "^3.0.0"