From 76a09e23ba6335d6b64a792d1be0ec70cd9916a3 Mon Sep 17 00:00:00 2001 From: Vilius Vystartas Date: Sat, 11 Jul 2026 21:53:58 +0100 Subject: [PATCH 1/8] feat: add usage examples to CLI help output Adds an abstract getExamples() method to SPFxActionBase that subclasses override to provide usage examples. The renderHelpText() method is overridden to append a formatted EXAMPLES section. Includes 4 examples for the 'spfx create' command covering: - Basic React web part scaffolding - Extension scaffolding with pnpm - Local template source usage - SPFx version targeting Partially addresses #238 --- apps/spfx-cli/src/cli/actions/CreateAction.ts | 9 ++++++ .../src/cli/actions/SPFxActionBase.ts | 32 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/apps/spfx-cli/src/cli/actions/CreateAction.ts b/apps/spfx-cli/src/cli/actions/CreateAction.ts index 8da46af7..e80ee4de 100644 --- a/apps/spfx-cli/src/cli/actions/CreateAction.ts +++ b/apps/spfx-cli/src/cli/actions/CreateAction.ts @@ -269,6 +269,15 @@ export class CreateAction extends SPFxActionBase { throw error; } } + + protected getExamples(): string[] { + return [ + 'Scaffold a new React web part in the current directory:\n spfx create --template webpart-react --component-name "HelloWorld" --library-name "helloworld-library" --solution-name "helloworld" --component-description "A Hello World web part"', + 'Scaffold an extension with pnpm (skipping npm install):\n spfx create --template extension-field --component-name "ColorField" --library-name "color-field-lib" --solution-name "color-field" --package-manager pnpm --skip-install', + 'Scaffold a web part without npm install, specifying a local template source:\n spfx create --template webpart-react --component-name "MyWebPart" --library-name "mywebpart" --solution-name "mywebpart" --skip-install --local-source ./my-templates', + 'Scaffold a web part using a specific SPFx version:\n spfx create --template webpart-react --component-name "MyWP" --component-description "Description" --spfx-version 1.22 --skip-install' + ]; + } } /** diff --git a/apps/spfx-cli/src/cli/actions/SPFxActionBase.ts b/apps/spfx-cli/src/cli/actions/SPFxActionBase.ts index d6ca0250..ca5096b0 100644 --- a/apps/spfx-cli/src/cli/actions/SPFxActionBase.ts +++ b/apps/spfx-cli/src/cli/actions/SPFxActionBase.ts @@ -166,4 +166,36 @@ export abstract class SPFxActionBase extends CommandLineAction { } } } + + /** + * Returns usage examples for this action. Each string is a self-contained example + * with the command invocation and a brief description of what it does. + * Override in subclasses to provide examples. The default returns an empty array. + */ + protected getExamples(): string[] { + return []; + } + + /** + * Overrides the base help text rendering to append a formatted EXAMPLES section + * when examples are defined. + */ + public renderHelpText(): string { + const base: string = super.renderHelpText(); + const examples: string[] = this.getExamples(); + if (examples.length === 0) { + return base; + } + + // Append examples after the last line of base help + const lines: string[] = base.split('\n'); + lines.push(''); + lines.push('EXAMPLES'); + for (const example of examples) { + lines.push(''); + lines.push(` ${example}`); + } + lines.push(''); + return lines.join('\n'); + } } From 601d0a678135d7a9750a5130d4041e75c89395b6 Mon Sep 17 00:00:00 2001 From: Vilius Vystartas Date: Sat, 11 Jul 2026 22:20:05 +0100 Subject: [PATCH 2/8] fix: add override keyword, fix invalid --skip-install parameter - Add override keyword to renderHelpText() (required by noImplicitOverride setting) - Add override keyword to CreateAction.getExamples() - Replace --skip-install examples with --package-manager pnpm - Remove unnecessary parameters from examples --- apps/spfx-cli/src/cli/actions/CreateAction.ts | 8 ++++---- apps/spfx-cli/src/cli/actions/SPFxActionBase.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/spfx-cli/src/cli/actions/CreateAction.ts b/apps/spfx-cli/src/cli/actions/CreateAction.ts index e80ee4de..9137a650 100644 --- a/apps/spfx-cli/src/cli/actions/CreateAction.ts +++ b/apps/spfx-cli/src/cli/actions/CreateAction.ts @@ -270,12 +270,12 @@ export class CreateAction extends SPFxActionBase { } } - protected getExamples(): string[] { + protected override getExamples(): string[] { return [ 'Scaffold a new React web part in the current directory:\n spfx create --template webpart-react --component-name "HelloWorld" --library-name "helloworld-library" --solution-name "helloworld" --component-description "A Hello World web part"', - 'Scaffold an extension with pnpm (skipping npm install):\n spfx create --template extension-field --component-name "ColorField" --library-name "color-field-lib" --solution-name "color-field" --package-manager pnpm --skip-install', - 'Scaffold a web part without npm install, specifying a local template source:\n spfx create --template webpart-react --component-name "MyWebPart" --library-name "mywebpart" --solution-name "mywebpart" --skip-install --local-source ./my-templates', - 'Scaffold a web part using a specific SPFx version:\n spfx create --template webpart-react --component-name "MyWP" --component-description "Description" --spfx-version 1.22 --skip-install' + 'Scaffold an extension with pnpm (skipping npm install):\n spfx create --template extension-field --component-name "ColorField" --package-manager pnpm', + 'Scaffold a web part without npm install, specifying a local template source:\n spfx create --template webpart-react --component-name "MyWebPart" --library-name "mywebpart" --solution-name "mywebpart" --local-source ./my-templates', + 'Scaffold a web part using a specific SPFx version:\n spfx create --template webpart-react --component-name "MyWP" --component-description "Description" --spfx-version 1.22' ]; } } diff --git a/apps/spfx-cli/src/cli/actions/SPFxActionBase.ts b/apps/spfx-cli/src/cli/actions/SPFxActionBase.ts index ca5096b0..95be93b8 100644 --- a/apps/spfx-cli/src/cli/actions/SPFxActionBase.ts +++ b/apps/spfx-cli/src/cli/actions/SPFxActionBase.ts @@ -180,7 +180,7 @@ export abstract class SPFxActionBase extends CommandLineAction { * Overrides the base help text rendering to append a formatted EXAMPLES section * when examples are defined. */ - public renderHelpText(): string { + public override renderHelpText(): string { const base: string = super.renderHelpText(); const examples: string[] = this.getExamples(); if (examples.length === 0) { From 4c71447666bd9f48fc2237f510a4bc4e9105aa9b Mon Sep 17 00:00:00 2001 From: Vilius Vystartas Date: Sat, 11 Jul 2026 22:54:30 +0100 Subject: [PATCH 3/8] fix: handle CRLF in renderHelpText, fix example wording --- apps/spfx-cli/src/cli/actions/CreateAction.ts | 2 +- apps/spfx-cli/src/cli/actions/SPFxActionBase.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/spfx-cli/src/cli/actions/CreateAction.ts b/apps/spfx-cli/src/cli/actions/CreateAction.ts index 9137a650..77e04513 100644 --- a/apps/spfx-cli/src/cli/actions/CreateAction.ts +++ b/apps/spfx-cli/src/cli/actions/CreateAction.ts @@ -272,7 +272,7 @@ export class CreateAction extends SPFxActionBase { protected override getExamples(): string[] { return [ - 'Scaffold a new React web part in the current directory:\n spfx create --template webpart-react --component-name "HelloWorld" --library-name "helloworld-library" --solution-name "helloworld" --component-description "A Hello World web part"', + 'Scaffold a new React web part in the current working directory:\n spfx create --template webpart-react --component-name "HelloWorld" --library-name "helloworld-library" --solution-name "helloworld" --component-description "A Hello World web part"', 'Scaffold an extension with pnpm (skipping npm install):\n spfx create --template extension-field --component-name "ColorField" --package-manager pnpm', 'Scaffold a web part without npm install, specifying a local template source:\n spfx create --template webpart-react --component-name "MyWebPart" --library-name "mywebpart" --solution-name "mywebpart" --local-source ./my-templates', 'Scaffold a web part using a specific SPFx version:\n spfx create --template webpart-react --component-name "MyWP" --component-description "Description" --spfx-version 1.22' diff --git a/apps/spfx-cli/src/cli/actions/SPFxActionBase.ts b/apps/spfx-cli/src/cli/actions/SPFxActionBase.ts index 95be93b8..3b3e4e75 100644 --- a/apps/spfx-cli/src/cli/actions/SPFxActionBase.ts +++ b/apps/spfx-cli/src/cli/actions/SPFxActionBase.ts @@ -188,7 +188,7 @@ export abstract class SPFxActionBase extends CommandLineAction { } // Append examples after the last line of base help - const lines: string[] = base.split('\n'); + const lines: string[] = base.replace(/\r$/, '').split('\n'); lines.push(''); lines.push('EXAMPLES'); for (const example of examples) { From 01eeccd95405fe269b22550d7cb92b4930fef23b Mon Sep 17 00:00:00 2001 From: Vilius Vystartas Date: Sun, 12 Jul 2026 07:53:05 +0100 Subject: [PATCH 4/8] fix: normalize all CRLF line endings, not just trailing The previous fix used /\r$/ which only strips a trailing CR from the last line. For CRLF content, every line except the last retains \r, producing malformed output. Use /\r\n/g to normalize all pairs. --- apps/spfx-cli/src/cli/actions/SPFxActionBase.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/spfx-cli/src/cli/actions/SPFxActionBase.ts b/apps/spfx-cli/src/cli/actions/SPFxActionBase.ts index 3b3e4e75..6fb9dddb 100644 --- a/apps/spfx-cli/src/cli/actions/SPFxActionBase.ts +++ b/apps/spfx-cli/src/cli/actions/SPFxActionBase.ts @@ -188,7 +188,8 @@ export abstract class SPFxActionBase extends CommandLineAction { } // Append examples after the last line of base help - const lines: string[] = base.replace(/\r$/, '').split('\n'); + // Normalize Windows CRLF to LF before splitting so every line is clean + const lines: string[] = base.replace(/\r\n/g, '\n').split('\n'); lines.push(''); lines.push('EXAMPLES'); for (const example of examples) { From e2055015c4596e902ae981f4d6740625076a1dbb Mon Sep 17 00:00:00 2001 From: Vilius Vystartas Date: Mon, 13 Jul 2026 15:33:45 +0100 Subject: [PATCH 5/8] chore: add change file for help examples --- .../@microsoft/spfx-cli/help-examples_2026-07-13.json | 10 ++++++++++ .../spfx-cli/library-name-optional_2026-07-13.json | 10 ++++++++++ 2 files changed, 20 insertions(+) create mode 100644 common/changes/@microsoft/spfx-cli/help-examples_2026-07-13.json create mode 100644 common/changes/@microsoft/spfx-cli/library-name-optional_2026-07-13.json diff --git a/common/changes/@microsoft/spfx-cli/help-examples_2026-07-13.json b/common/changes/@microsoft/spfx-cli/help-examples_2026-07-13.json new file mode 100644 index 00000000..a0a328ef --- /dev/null +++ b/common/changes/@microsoft/spfx-cli/help-examples_2026-07-13.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@microsoft/spfx-cli", + "comment": "", + "type": "none" + } + ], + "packageName": "@microsoft/spfx-cli" +} diff --git a/common/changes/@microsoft/spfx-cli/library-name-optional_2026-07-13.json b/common/changes/@microsoft/spfx-cli/library-name-optional_2026-07-13.json new file mode 100644 index 00000000..a0a328ef --- /dev/null +++ b/common/changes/@microsoft/spfx-cli/library-name-optional_2026-07-13.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@microsoft/spfx-cli", + "comment": "", + "type": "none" + } + ], + "packageName": "@microsoft/spfx-cli" +} From 203d387a0d25570db3f6ec57cb05c11ec7e95dda Mon Sep 17 00:00:00 2001 From: Vilius Vystartas Date: Mon, 13 Jul 2026 15:49:09 +0100 Subject: [PATCH 6/8] chore: remove change file that belongs to PR #260 --- .../spfx-cli/library-name-optional_2026-07-13.json | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 common/changes/@microsoft/spfx-cli/library-name-optional_2026-07-13.json diff --git a/common/changes/@microsoft/spfx-cli/library-name-optional_2026-07-13.json b/common/changes/@microsoft/spfx-cli/library-name-optional_2026-07-13.json deleted file mode 100644 index a0a328ef..00000000 --- a/common/changes/@microsoft/spfx-cli/library-name-optional_2026-07-13.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "changes": [ - { - "packageName": "@microsoft/spfx-cli", - "comment": "", - "type": "none" - } - ], - "packageName": "@microsoft/spfx-cli" -} From 9aa85cc918006f4338ed126db078127a91b82e1a Mon Sep 17 00:00:00 2001 From: Vilius Vystartas Date: Wed, 15 Jul 2026 22:37:30 +0100 Subject: [PATCH 7/8] test: update CLI help snapshot for create action examples The new EXAMPLES section in renderHelpText() causes the 'create' action help snapshot to diverge from the updated output. Regenerated with --updateSnapshot. --- .../__snapshots__/CommandLineHelp.test.ts.snap | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/apps/spfx-cli/src/cli/test/__snapshots__/CommandLineHelp.test.ts.snap b/apps/spfx-cli/src/cli/test/__snapshots__/CommandLineHelp.test.ts.snap index b7be4dfb..e6adc53c 100644 --- a/apps/spfx-cli/src/cli/test/__snapshots__/CommandLineHelp.test.ts.snap +++ b/apps/spfx-cli/src/cli/test/__snapshots__/CommandLineHelp.test.ts.snap @@ -56,6 +56,21 @@ Optional arguments: Package manager to use for dependency installation after scaffolding. Use \\"none\\" to skip installation. The default value is \\"none\\". + + +EXAMPLES + + Scaffold a new React web part in the current working directory: + spfx create --template webpart-react --component-name \\"HelloWorld\\" --library-name \\"helloworld-library\\" --solution-name \\"helloworld\\" --component-description \\"A Hello World web part\\" + + Scaffold an extension with pnpm (skipping npm install): + spfx create --template extension-field --component-name \\"ColorField\\" --package-manager pnpm + + Scaffold a web part without npm install, specifying a local template source: + spfx create --template webpart-react --component-name \\"MyWebPart\\" --library-name \\"mywebpart\\" --solution-name \\"mywebpart\\" --local-source ./my-templates + + Scaffold a web part using a specific SPFx version: + spfx create --template webpart-react --component-name \\"MyWP\\" --component-description \\"Description\\" --spfx-version 1.22 " `; From 66d7d61f3537562ba312e0dfc07003d13b52b1ac Mon Sep 17 00:00:00 2001 From: Vilius Vystartas Date: Thu, 16 Jul 2026 20:58:55 +0100 Subject: [PATCH 8/8] fix: revert --library-name regression, fix example wording and snapshot Fixes two issues: 1. Reverts --library-name back to optional (regression from merge conflict with already-merged PR #260 that made it optional on main) 2. Fixes example text to accurately describe CLI behavior: - Subfolder output, not CWD - Use --package-manager none (not pnpm) to skip install - Include required params in all examples now that --library-name is optional, examples show the minimal arguments needed Co-Authored-By: Claw --- apps/spfx-cli/src/cli/actions/CreateAction.ts | 16 ++++++++-------- .../__snapshots__/CommandLineHelp.test.ts.snap | 18 +++++++++--------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/apps/spfx-cli/src/cli/actions/CreateAction.ts b/apps/spfx-cli/src/cli/actions/CreateAction.ts index 77e04513..f25b79cf 100644 --- a/apps/spfx-cli/src/cli/actions/CreateAction.ts +++ b/apps/spfx-cli/src/cli/actions/CreateAction.ts @@ -48,7 +48,7 @@ const ScaffoldProfileSchema: z.ZodType = z.object({ export class CreateAction extends SPFxActionBase { private readonly _targetDirParameter: CommandLineStringParameter; private readonly _templateParameter: IRequiredCommandLineStringParameter; - private readonly _libraryNameParameter: IRequiredCommandLineStringParameter; + private readonly _libraryNameParameter: CommandLineStringParameter; private readonly _componentNameParameter: IRequiredCommandLineStringParameter; private readonly _componentAliasParameter: CommandLineStringParameter; private readonly _componentDescriptionParameter: CommandLineStringParameter; @@ -83,8 +83,8 @@ export class CreateAction extends SPFxActionBase { this._libraryNameParameter = this.defineStringParameter({ parameterLongName: '--library-name', argumentName: 'LIBRARY_NAME', - description: 'The library name for the component', - required: true + description: 'The library name for the component. Defaults to the solution name if not specified.', + required: false }); this._componentNameParameter = this.defineStringParameter({ @@ -204,7 +204,7 @@ export class CreateAction extends SPFxActionBase { const builtInContext: ISPFxBuiltInContext = buildBuiltInContext( { componentName, - libraryName: this._libraryNameParameter.value, + libraryName: this._libraryNameParameter.value || solutionName, spfxVersion: template.spfxVersion, solutionName: rawSolutionName || undefined, componentAlias: this._componentAliasParameter.value?.trim() || undefined, @@ -272,10 +272,10 @@ export class CreateAction extends SPFxActionBase { protected override getExamples(): string[] { return [ - 'Scaffold a new React web part in the current working directory:\n spfx create --template webpart-react --component-name "HelloWorld" --library-name "helloworld-library" --solution-name "helloworld" --component-description "A Hello World web part"', - 'Scaffold an extension with pnpm (skipping npm install):\n spfx create --template extension-field --component-name "ColorField" --package-manager pnpm', - 'Scaffold a web part without npm install, specifying a local template source:\n spfx create --template webpart-react --component-name "MyWebPart" --library-name "mywebpart" --solution-name "mywebpart" --local-source ./my-templates', - 'Scaffold a web part using a specific SPFx version:\n spfx create --template webpart-react --component-name "MyWP" --component-description "Description" --spfx-version 1.22' + 'Create a new React web part. Scaffolds into a subfolder named after the solution:\n spfx create --template webpart-react --component-name "HelloWorld" --solution-name "helloworld" --component-description "A Hello World web part"', + 'Create an extension. Skips package install with --package-manager none:\n spfx create --template extension-field --component-name "ColorField" --package-manager none', + 'Create a web part from a local template source:\n spfx create --template webpart-react --component-name "MyWebPart" --library-name "mywebpart" --solution-name "mywebpart" --local-source ./my-templates', + 'Create a web part with a specific SPFx version:\n spfx create --template webpart-react --component-name "MyWP" --component-description "Description" --spfx-version 1.22 --library-name "mywp" --solution-name "mywp"' ]; } } diff --git a/apps/spfx-cli/src/cli/test/__snapshots__/CommandLineHelp.test.ts.snap b/apps/spfx-cli/src/cli/test/__snapshots__/CommandLineHelp.test.ts.snap index e6adc53c..2bc50195 100644 --- a/apps/spfx-cli/src/cli/test/__snapshots__/CommandLineHelp.test.ts.snap +++ b/apps/spfx-cli/src/cli/test/__snapshots__/CommandLineHelp.test.ts.snap @@ -60,18 +60,18 @@ Optional arguments: EXAMPLES - Scaffold a new React web part in the current working directory: - spfx create --template webpart-react --component-name \\"HelloWorld\\" --library-name \\"helloworld-library\\" --solution-name \\"helloworld\\" --component-description \\"A Hello World web part\\" + Create a new React web part. Scaffolds into a subfolder named after the solution: + spfx create --template webpart-react --component-name \"HelloWorld\" --solution-name \"helloworld\" --component-description \"A Hello World web part\" - Scaffold an extension with pnpm (skipping npm install): - spfx create --template extension-field --component-name \\"ColorField\\" --package-manager pnpm + Create an extension. Skips package install with --package-manager none: + spfx create --template extension-field --component-name \"ColorField\" --package-manager none - Scaffold a web part without npm install, specifying a local template source: - spfx create --template webpart-react --component-name \\"MyWebPart\\" --library-name \\"mywebpart\\" --solution-name \\"mywebpart\\" --local-source ./my-templates + Create a web part from a local template source: + spfx create --template webpart-react --component-name \"MyWebPart\" --library-name \"mywebpart\" --solution-name \"mywebpart\" --local-source ./my-templates - Scaffold a web part using a specific SPFx version: - spfx create --template webpart-react --component-name \\"MyWP\\" --component-description \\"Description\\" --spfx-version 1.22 -" + Create a web part with a specific SPFx version: + spfx create --template webpart-react --component-name \"MyWP\" --component-description \"Description\" --spfx-version 1.22 --library-name \"mywp\" --solution-name \"mywp\" +\" `; exports[`CommandLineHelp prints the help: global help 1`] = `