diff --git a/CHANGELOG.md b/CHANGELOG.md index de7cdc0..f7e4da0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Change Log +## [2.4.0] - 2023-11-29 + +- Added support for SPFx v1.18.2 +- Added Teams Toolkit support +- Refactored help and feedback section to tree view adding new links to Teams Toolkit and ACE previewer +- Added ACE previewer checker to suggest this extension when ACE component is present in the project +- Modified CI/CD GitHub generate workflow action to present list of site level app catalogs +- Updated dependencies validation to check for latest version of yo + ## [2.3.0] - 2023-11-16 - Updated dependencies validation to check for yo@4.3.1 diff --git a/README.md b/README.md index 61a0264..317ed90 100644 --- a/README.md +++ b/README.md @@ -18,11 +18,11 @@

-

Viva Connections Toolkit is a Visual Studio Code extension that aims to boost your productivity in developing and managing SharePoint Framework solutions helping at every stage of your development flow, from setting up your development workspace to deploying a solution straight to your tenant without the need to leave VS Code.

+

Viva Connections Toolkit is a Visual Studio Code extension that aims to boost your productivity in developing and managing SharePoint Framework solutions helping at every stage of your development flow, from setting up your development workspace to deploying a solution straight to your tenant without the need to leave VS Code. With the SharePoint Framework, you can use modern web technologies and tools in your preferred development environment to build productive experiences and apps that are responsive and mobile-ready allowing you to create solutions to extend SharePoint, Microsoft Teams, Microsoft Viva Connections, Outlook and Microsoft365.com.

- Architecture | Capabilities | + Architecture | Wiki | Contributing | Support | @@ -33,12 +33,6 @@

![Sample Gallery](./assets/images/start.png) - -## ⚙️ Architecture - -Viva Connections Toolkit for Visual Studio Code is an abstraction layer on top of the [SPFx](https://aka.ms/spfx) Yeoman generator and [CLI for Microsoft 365](https://pnp.github.io/cli-microsoft365/). All operations and actions are performed on the command line level using these two tools with the environment settings on your computer. - -This means that the features and capabilities provided through this tool are available for any solution which has been built with SPFx. ## ⭐ Capabilities @@ -46,7 +40,7 @@ The extension provides the following capabilities: ### 1️⃣ Welcome experience -The extension automatically detects if you are in a SharePoint Framework project. If not, it will show helpful actions to allow you to create a new SPFx or ACE solution. +The extension automatically detects if have a SharePoint Framework project opened. If not, it will start with a welcome experience that will guide you through the process of creating a new project and validating your local environment. ![Welcome experience](./assets/images/welcome-experience.png) @@ -54,15 +48,15 @@ Directly from this view, you may create a new project from scratch or from an ex It is also possible to open an already existing project using Open folder button. -Last but not least it is possible to check your local workspace for the needed global dependencies to develop the SPFx project, like the correct Node version, gulp, yeoman etc., and install them if needed with a single click. +Last but not least it is possible to check your local workspace for the needed global dependencies to develop the SPFx solutions, like the correct Node version, gulp, yeoman etc., and install them if needed with a single click. ### 2️⃣ Set up your development environment -To ensure that you can develop SPFx solutions, in Viva Connections Toolkit you may use the **check dependencies** functionality. +To ensure that you can develop SPFx solutions, you may use the **check dependencies** functionality. ![Validate dependency](./assets/images/validate-dependency.png) -This action will check if you have the required dependencies to create a new Viva Connections app. +This action will check if you have the required dependencies such as: - Node version: 16 or 18 - NPM dependencies: @@ -70,13 +64,13 @@ This action will check if you have the required dependencies to create a new Viv - yo - @microsoft/generator-sharepoint -In case when you do not have all dependencies installed, you can use the **install dependencies** action to install them. +In case you do not have all dependencies installed, or some are in incorrect version, you can use the **install dependencies** action to install them. -> **Info**: This list is based on the [set up your development environment recommendations](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/set-up-your-development-environment) +> **Info**: The list of valid dependencies is based on [set up your development environment recommendations](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/set-up-your-development-environment) ### 3️⃣ Don't Start from scratch. Reuse an SPFx (web part or extension) or ACE sample -From one of the samples gallery, you can kick-start your development with a new project. Create a new SPFx web part Extension or ACE solution with a click of a button. The samples are coming from [PnP Samples repo](https://pnp.github.io/sp-dev-fx-webparts/samples/type/). +You may kick-start your development with a new project based on an existing ACE or SPFx web part or extension with a click of a button. All of the provided samples are powered by [PnP Samples repositories](https://pnp.github.io/sp-dev-fx-webparts/samples/type/). Check out how easy it is to create a new project based on a web part sample 👇. @@ -98,13 +92,13 @@ Check it out 👇. ![Create new project](./assets/images/scaffolding.gif) -It's possible to scaffolding any kind of SPFx project. +It's possible to scaffold any kind of SPFx project. ![Create new project](./assets/images/scaffolding.png) ### 6️⃣ Login to your tenant & retrieve environment details -The extension also allows you to log in to your Microsoft 365 tenant using CLI for Microsoft 365. +The extension also allows you to login to your Microsoft 365 tenant using CLI for Microsoft 365. ![login](./assets/images/login.png) @@ -118,7 +112,7 @@ Additionally, when an SPFx project is opened the extension will check serve.json ### 7️⃣ Gulp tasks -One of the sections of the extension shows all possible Gulp tasks one may run on an SPFx project. The tasks allow you to clean, bundle, package, serve the project with a single click. +The extension shows all possible Gulp tasks one may run on an SPFx project. The tasks allow you to clean, bundle, package, serve the project with a single click. ![Gulp Tasks](./assets/images/tasks.png) @@ -130,33 +124,33 @@ The actions section allows unique functionalities that may significantly boost p Currently the extension allows you to: -- **Upgrade project** - Uses CLI for Microsoft 365 to create a .md report with upgrade guidance to the latest supported SPFx version by the extension. [Check out our docs for more details](https://github.com/pnp/vscode-viva/wiki/4.6-Actions#upgrade-project) +- **Upgrade project** - Uses CLI for Microsoft 365 to create a .md report with upgrade guidance to the latest supported SPFx version by the extension. [Check out our docs for more details](https://github.com/pnp/vscode-viva/wiki/5.5-Actions#upgrade-project) ![Upgrade project](./assets/images/upgrade-project.png) -- **Validate current project** - Creates a validation .md report against the currently used SPFx version in the project. The action will automatically detect the SPFx version used and will validate if the project is properly set up. [Check out our docs for more details](https://github.com/pnp/vscode-viva/wiki/4.6-Actions#validate-current-project) +- **Validate current project** - Creates a validation .md report against the currently used SPFx version in the project. The action will automatically detect the SPFx version used and will validate if the project is properly set up. [Check out our docs for more details](https://github.com/pnp/vscode-viva/wiki/5.5-Actions#validate-current-project) -- **Rename current project** - Forget about manual work and let the extension rename your project and generate a new solution ID. [Check out our docs for more details](https://github.com/pnp/vscode-viva/wiki/4.6-Actions#rename-current-project) +- **Rename current project** - Forget about manual work and let the extension rename your project and generate a new solution ID. [Check out our docs for more details](https://github.com/pnp/vscode-viva/wiki/5.5-Actions#rename-current-project) ![Rename](./assets/images/rename.png) -- **Grant API permissions** - The action will Grant all API permissions specified in the package-solution.json of the current project. This is especially helpful if you just want to debug your SPFx solution using Workbench. No longer do you need to bundle, package, and deploy the project to then go to the SharePoint admin portal and consent to the permissions. All of that is now done with just a single click. [Check out our docs for more details](https://github.com/pnp/vscode-viva/wiki/4.6-Actions#grant-api-permissions) +- **Grant API permissions** - The action will Grant all API permissions specified in the package-solution.json of the current project. This is especially helpful if you just want to debug your SPFx solution using Workbench. No longer do you need to bundle, package, and deploy the project to then go to the SharePoint admin portal and consent to the permissions. All of that is now done with just a single click. [Check out our docs for more details](https://github.com/pnp/vscode-viva/wiki/5.5-Actions#grant-api-permissions) ![Grant permissions](./assets/images/grant-permissions.png) -- **Deploy project** - This action will only work when the user is logged in to tenant and the sppkg file is present. The action will deploy the project to the selected (tenant or site) app catalog. [Check out our docs for more details](https://github.com/pnp/vscode-viva/wiki/4.6-Actions#deploy-project) +- **Deploy project** - This action will only work when the user is logged in to tenant and the sppkg file is present. The action will deploy the project to the selected (tenant or site) app catalog. [Check out our docs for more details](https://github.com/pnp/vscode-viva/wiki/5.5-Actions#grant-api-permissions) ![Deploy](./assets/images/deploy.png) -- **Add new component** - Allows scaffolding a new SPFx project as a new component of the currently opened project. The action under the hood uses the same SharePoint Yeoman generator to scaffold a new project and this feature is an abstraction UI layer. [Check out our docs for more details](https://github.com/pnp/vscode-viva/wiki/4.6-Actions#add-new-component) +- **Add new component** - Allows scaffolding a new SPFx project as a new component of the currently opened project. The action under the hood uses the same SharePoint Yeoman generator to scaffold a new project and this feature is an abstraction UI layer. [Check out our docs for more details](https://github.com/pnp/vscode-viva/wiki/5.5-Actions#add-new-component) ![Add component](./assets/images/add-component.png) -- **CI/CD GitHub Workflow** - This action will allow you to generate yaml GitHub workflow that uses CLI for Microsoft 365 GitHub actions to bundle, package, and deploy your project to an app catalog on every code push. [Check out our docs for more details](https://github.com/pnp/vscode-viva/wiki/4.6-Actions#cicd-github-workflow) +- **CI/CD GitHub Workflow** - This action will allow you to generate yaml GitHub workflow that uses CLI for Microsoft 365 GitHub actions to bundle, package, and deploy your project to an app catalog on every code push. [Check out our docs for more details](https://github.com/pnp/vscode-viva/wiki/5.5-Actions#cicd-github-workflow) ![CI CD pipeline](./assets/images/CI_CD-pipeline-gif.gif) -- **Open sample/scenario galleries of the SPFx web part, extensions, or ACEs projects** - Viva Connections Toolkit supports a couple of sample galleries that may be used to scaffold a new SPFx project. [Check out our docs for more details](https://github.com/pnp/vscode-viva/wiki/4.6-Actions#open-samplescenario-galleries-of-the-spfx-web-part-extensions-or-aces-projects) +- **Open sample/scenario galleries of the SPFx web part, extensions, or ACEs projects** - Viva Connections Toolkit supports a couple of sample galleries that may be used to scaffold a new SPFx project. [Check out our docs for more details](https://github.com/pnp/vscode-viva/wiki/5.5-Actions#open-samplescenario-galleries-of-the-spfx-web-part-extensions-or-aces-projects) ### 9️⃣ Help and feedback section @@ -186,6 +180,12 @@ Check it out in action 👇 ![code snippets](./assets/images/code-snippets.gif) +## ⚙️ Architecture + +Viva Connections Toolkit for Visual Studio Code is an abstraction layer on top of the [SPFx](https://aka.ms/spfx) Yeoman generator and [CLI for Microsoft 365](https://pnp.github.io/cli-microsoft365/). + +This means that the features and capabilities provided through this tool are available for any solution which has been built with SPFx. + ## 📚 Wiki For more information on how to use the extension, please refer to the [wiki](https://github.com/pnp/vscode-viva/wiki). diff --git a/assets/images/actions.png b/assets/images/actions.png index c7da510..f1f5867 100644 Binary files a/assets/images/actions.png and b/assets/images/actions.png differ diff --git a/assets/images/create-base-on-spfx-ace.gif b/assets/images/create-base-on-spfx-ace.gif index 451701f..253c078 100644 Binary files a/assets/images/create-base-on-spfx-ace.gif and b/assets/images/create-base-on-spfx-ace.gif differ diff --git a/assets/images/create-base-on-spfx-webpart.gif b/assets/images/create-base-on-spfx-webpart.gif index 0e4b99b..9e8d9f6 100644 Binary files a/assets/images/create-base-on-spfx-webpart.gif and b/assets/images/create-base-on-spfx-webpart.gif differ diff --git a/assets/images/help-and-feedback.png b/assets/images/help-and-feedback.png index 5922018..a827f1d 100644 Binary files a/assets/images/help-and-feedback.png and b/assets/images/help-and-feedback.png differ diff --git a/assets/images/scaffolding.gif b/assets/images/scaffolding.gif index 8feee3e..4e4e49f 100644 Binary files a/assets/images/scaffolding.gif and b/assets/images/scaffolding.gif differ diff --git a/assets/images/start.png b/assets/images/start.png index 5535ee6..55b1ebf 100644 Binary files a/assets/images/start.png and b/assets/images/start.png differ diff --git a/assets/images/tasks.png b/assets/images/tasks.png index 462370c..9476fb8 100644 Binary files a/assets/images/tasks.png and b/assets/images/tasks.png differ diff --git a/assets/images/upgrade-project.png b/assets/images/upgrade-project.png index 42fb5f5..f543ed3 100644 Binary files a/assets/images/upgrade-project.png and b/assets/images/upgrade-project.png differ diff --git a/assets/images/welcome-experience.png b/assets/images/welcome-experience.png index 833e8f9..a1f5432 100644 Binary files a/assets/images/welcome-experience.png and b/assets/images/welcome-experience.png differ diff --git a/package-lock.json b/package-lock.json index ed8b370..6dd2db8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "viva-connections-toolkit", - "version": "2.3.0", + "version": "2.4.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "viva-connections-toolkit", - "version": "2.3.0", + "version": "2.4.0", "license": "MIT", "dependencies": { "@pnp/cli-microsoft365": "6.11.0" diff --git a/package.json b/package.json index b595468..9702d1a 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "viva-connections-toolkit", "displayName": "Viva Connections Toolkit", "description": "Viva Connections Toolkit aims to boost your productivity in developing and managing SharePoint Framework solutions helping at every stage of your development flow, from setting up your development workspace to deploying a solution straight to your tenant without the need to leave VS Code and now even create a CI/CD pipeline to introduce automate deployment of your app. This toolkit is provided by the community.", - "version": "2.3.0", + "version": "2.4.0", "publisher": "m365pnp", "preview": false, "homepage": "https://github.com/pnp/vscode-viva", @@ -24,11 +24,14 @@ }, "keywords": [ "SPFx", + "SharePoint Framework", "Viva", - "Viva-Connections", + "Viva Connections", "Microsoft 365", "SharePoint", - "Microsoft Teams" + "Microsoft Teams", + "Outlook", + "Office" ], "activationEvents": [ "workspaceContains:**/project.pnp", diff --git a/src/extension.ts b/src/extension.ts index c75fe2a..bf18ff2 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -5,7 +5,7 @@ import { PROJECT_FILE, Scaffolder } from './services/Scaffolder'; import { Extension } from './services/Extension'; import { Dependencies } from './services/Dependencies'; import { unlinkSync, readFileSync } from 'fs'; -import { Terminal } from './services/Terminal'; +import { TerminalCommandExecuter } from './services/TerminalCommandExecuter'; import { AuthProvider } from './providers/AuthProvider'; import { CliActions } from './services/CliActions'; import { ProjectFileContent } from './constants'; @@ -14,7 +14,7 @@ import { ProjectFileContent } from './constants'; export async function activate(context: ExtensionContext) { Extension.getInstance(context); - Terminal.register(); + TerminalCommandExecuter.register(); AuthProvider.register(); diff --git a/src/models/YoRc.ts b/src/models/YoRc.ts new file mode 100644 index 0000000..5d99c1c --- /dev/null +++ b/src/models/YoRc.ts @@ -0,0 +1,7 @@ +export interface YoRc { + '@microsoft/generator-sharepoint': GeneratorSharePoint; +} + +export interface GeneratorSharePoint { + aceTemplateType?: string; +} \ No newline at end of file diff --git a/src/models/index.ts b/src/models/index.ts index eb05825..7178e52 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -6,3 +6,4 @@ export * from './SiteAppCatalog'; export * from './solution-add-result'; export * from './subscription'; export * from './vscode-launch'; +export * from './YoRc'; diff --git a/src/panels/CommandPanel.ts b/src/panels/CommandPanel.ts index 2e88426..1eaa9bc 100644 --- a/src/panels/CommandPanel.ts +++ b/src/panels/CommandPanel.ts @@ -6,6 +6,8 @@ import { AuthProvider, M365AuthenticationSession } from '../providers/AuthProvid import { CliActions } from '../services/CliActions'; import { DebuggerCheck } from '../services/DebuggerCheck'; import { EnvironmentInformation } from '../services/EnvironmentInformation'; +import { TeamsToolkitIntegration } from '../services/TeamsToolkitIntegration'; +import { AdaptiveCardCheck } from '../services/AdaptiveCardCheck'; export class CommandPanel { @@ -19,7 +21,14 @@ export class CommandPanel { * @returns */ private static async init() { - const files = await workspace.findFiles('.yo-rc.json', '**/node_modules/**'); + let isTeamsToolkitProject = false; + let files = await workspace.findFiles('.yo-rc.json', '**/node_modules/**'); + + if (files.length <= 0) { + files = await workspace.findFiles('src/.yo-rc.json', '**/node_modules/**'); + isTeamsToolkitProject = true; + } + if (files.length <= 0) { CommandPanel.showWelcome(); return; @@ -41,14 +50,16 @@ export class CommandPanel { commands.executeCommand('setContext', ContextKeys.isSPFxProject, true); commands.executeCommand('setContext', ContextKeys.showWelcome, false); - CommandPanel.registerTreeview(); + TeamsToolkitIntegration.isTeamsToolkitProject = isTeamsToolkitProject; + + CommandPanel.registerTreeView(); AuthProvider.verify(); } /** * Register all the treeviews */ - private static registerTreeview() { + private static registerTreeView() { const authInstance = AuthProvider.getInstance(); if (authInstance) { authInstance.getAccount().then(account => CommandPanel.accountTreeView(account)); @@ -109,6 +120,8 @@ export class CommandPanel { const origin = url.origin; DebuggerCheck.validateUrl(origin); + AdaptiveCardCheck.validateACEComponent(); + environmentCommands.push( new ActionTreeItem('SharePoint', '', { name: 'sharepoint', custom: true }, undefined, undefined, undefined, undefined, [ new ActionTreeItem(origin, '', { name: 'globe', custom: false }, undefined, 'vscode.open', Uri.parse(origin), 'sp-url') @@ -127,7 +140,7 @@ export class CommandPanel { } } - window.registerTreeDataProvider('pnp-view-environment', new ActionTreeviewProvider(environmentCommands)); + window.createTreeView('pnp-view-environment', { treeDataProvider: new ActionTreeviewProvider(environmentCommands), showCollapseAll: true }); } /** @@ -173,81 +186,34 @@ export class CommandPanel { * Provide the actions for the help treeview */ private static helpTreeView() { - const links = [ - { - title: 'Overview of Viva Connections Extensibility', - url: 'https://learn.microsoft.com/en-us/sharepoint/dev/spfx/viva/overview-viva-connections', - image: { name: 'book', custom: false } - }, - { - title: 'Overview of the SharePoint Framework', - url: 'https://learn.microsoft.com/en-us/sharepoint/dev/spfx/sharepoint-framework-overview', - image: { name: 'book', custom: false } - }, - { - title: 'Overview of Microsoft Graph', - url: 'https://learn.microsoft.com/en-us/graph/overview?view=graph-rest-1.0', - image: { name: 'book', custom: false } - }, - { - title: 'Learning path: Extend Microsoft Viva Connections', - url: 'https://learn.microsoft.com/en-us/training/paths/m365-extend-viva-connections/', - image: { name: 'mortar-board', custom: false } - }, - { - title: 'Learning path: Extend Microsoft SharePoint - Associate', - url: 'https://learn.microsoft.com/en-us/training/paths/m365-sharepoint-associate/', - image: { name: 'mortar-board', custom: false } - }, - { - title: 'Learning path: Microsoft Graph Fundamentals', - url: 'https://learn.microsoft.com/en-us/training/paths/m365-msgraph-fundamentals/', - image: { name: 'mortar-board', custom: false } - }, - { - title: 'Sample Solution Gallery', - url: 'https://adoption.microsoft.com/en-us/sample-solution-gallery/', - image: { name: 'library', custom: false } - }, - { - title: 'Adaptive Card Designer', - url: 'https://adaptivecards.io/designer/', - image: { name: 'globe', custom: false } - }, - { - title: 'Microsoft Graph Explorer', - url: 'https://developer.microsoft.com/en-us/graph/graph-explorer', - image: { name: 'globe', custom: false } - }, - { - title: 'Join the Microsoft 365 Developer Program', - url: 'https://developer.microsoft.com/en-us/microsoft-365/dev-program', - image: { name: 'star-empty', custom: false } - }, - { - title: 'Microsoft 365 & Power Platform Community Home', - url: 'https://pnp.github.io/', - image: { name: 'organization', custom: false } - }, - { - title: 'Join the Microsoft 365 & Power Platform Community Discord Server', - url: 'https://aka.ms/community/discord', - image: { name: 'feedback', custom: false } - }, - { - title: 'Wiki', - url: 'https://github.com/pnp/vscode-viva/wiki', - image: { name: 'question', custom: false } - }, - { - title: 'Report an issue', - url: 'https://github.com/pnp/vscode-viva/issues/new/choose', - image: { name: 'github', custom: false } - } + const helpCommands: ActionTreeItem[] = [ + new ActionTreeItem('Docs & Learning', '', undefined, undefined, undefined, undefined, undefined, [ + new ActionTreeItem('Overview of the SharePoint Framework', '', { name: 'book', custom: false }, undefined, 'vscode.open', Uri.parse('https://learn.microsoft.com/en-us/sharepoint/dev/spfx/sharepoint-framework-overview')), + new ActionTreeItem('Overview of Viva Connections Extensibility', '', { name: 'book', custom: false }, undefined, 'vscode.open', Uri.parse('https://learn.microsoft.com/en-us/sharepoint/dev/spfx/viva/overview-viva-connections')), + new ActionTreeItem('Overview of Microsoft Graph', '', { name: 'book', custom: false }, undefined, 'vscode.open', Uri.parse('https://learn.microsoft.com/en-us/graph/overview?view=graph-rest-1.0')), + new ActionTreeItem('Learning path: Extend Microsoft SharePoint - Associate', '', { name: 'mortar-board', custom: false }, undefined, 'vscode.open', Uri.parse('https://learn.microsoft.com/en-us/training/paths/m365-sharepoint-associate/')), + new ActionTreeItem('Learning path: Extend Microsoft Viva Connections', '', { name: 'mortar-board', custom: false }, undefined, 'vscode.open', Uri.parse('https://learn.microsoft.com/en-us/training/paths/m365-extend-viva-connections/')), + new ActionTreeItem('Learning path: Microsoft Graph Fundamentals', '', { name: 'mortar-board', custom: false }, undefined, 'vscode.open', Uri.parse('https://learn.microsoft.com/en-us/training/paths/m365-msgraph-fundamentals/')) + ]), + new ActionTreeItem('Resources & Tooling', '', undefined, undefined, undefined, undefined, undefined, [ + new ActionTreeItem('Microsoft Graph Explorer', '', { name: 'globe', custom: false }, undefined, 'vscode.open', Uri.parse('https://developer.microsoft.com/en-us/graph/graph-explorer')), + new ActionTreeItem('Teams Toolkit', '', { name: 'tools', custom: false }, undefined, 'vscode.open', Uri.parse('https://marketplace.visualstudio.com/items?itemName=TeamsDevApp.ms-teams-vscode-extension')), + new ActionTreeItem('Adaptive Card Previewer', '', { name: 'tools', custom: false }, undefined, 'vscode.open', Uri.parse('https://marketplace.visualstudio.com/items?itemName=TeamsDevApp.vscode-adaptive-cards')), + new ActionTreeItem('Adaptive Card Designer', '', { name: 'globe', custom: false }, undefined, 'vscode.open', Uri.parse('https://adaptivecards.io/designer/')), + new ActionTreeItem('Join the Microsoft 365 Developer Program', '', { name: 'star-empty', custom: false }, undefined, 'vscode.open', Uri.parse('https://developer.microsoft.com/en-us/microsoft-365/dev-program')), + new ActionTreeItem('Sample Solution Gallery', '', { name: 'library', custom: false }, undefined, 'vscode.open', Uri.parse('https://adoption.microsoft.com/en-us/sample-solution-gallery/')) + ]), + new ActionTreeItem('Community', '', undefined, undefined, undefined, undefined, undefined, [ + new ActionTreeItem('Microsoft 365 & Power Platform Community Home', '', { name: 'organization', custom: false }, undefined, 'vscode.open', Uri.parse('https://pnp.github.io/')), + new ActionTreeItem('Join the Microsoft 365 & Power Platform Community Discord Server', '', { name: 'feedback', custom: false }, undefined, 'vscode.open', Uri.parse('https://aka.ms/community/discord')) + ]), + new ActionTreeItem('Support', '', undefined, undefined, undefined, undefined, undefined, [ + new ActionTreeItem('Wiki', '', { name: 'question', custom: false }, undefined, 'vscode.open', Uri.parse('https://github.com/pnp/vscode-viva/wiki')), + new ActionTreeItem('Report an issue', '', { name: 'github', custom: false }, undefined, 'vscode.open', Uri.parse('https://github.com/pnp/vscode-viva/issues/new/choose')) + ]) ]; - const helpCommands: ActionTreeItem[] = links.map(link => new ActionTreeItem(link.title, '', link.image, undefined, 'vscode.open', Uri.parse(link.url))); - window.registerTreeDataProvider('pnp-view-help', new ActionTreeviewProvider(helpCommands)); + window.createTreeView('pnp-view-help', { treeDataProvider: new ActionTreeviewProvider(helpCommands), showCollapseAll: true }); } /** diff --git a/src/providers/AuthProvider.ts b/src/providers/AuthProvider.ts index 4366bee..1999a0c 100644 --- a/src/providers/AuthProvider.ts +++ b/src/providers/AuthProvider.ts @@ -7,7 +7,7 @@ import { Extension } from './../services/Extension'; import { executeCommand } from '@pnp/cli-microsoft365'; import { exec } from 'child_process'; import { Folders } from '../services/Folders'; -import { Terminal } from '../services/Terminal'; +import { TerminalCommandExecuter } from '../services/TerminalCommandExecuter'; export class M365AuthenticationSession implements AuthenticationSession { @@ -147,7 +147,7 @@ export class AuthProvider implements AuthenticationProvider, Disposable { // Bring the editor to the front const wsFolder = await Folders.getWorkspaceFolder(); - exec('code .', { cwd: wsFolder?.uri.fsPath, shell: Terminal.shell }); + exec('code .', { cwd: wsFolder?.uri.fsPath, shell: TerminalCommandExecuter.shell }); this.onDidChangeEventEmit.fire({ added: [account as any], removed: [], changed: [] }); diff --git a/src/services/AdaptiveCardCheck.ts b/src/services/AdaptiveCardCheck.ts new file mode 100644 index 0000000..d79599a --- /dev/null +++ b/src/services/AdaptiveCardCheck.ts @@ -0,0 +1,50 @@ +import { extensions, workspace, env, Uri } from 'vscode'; +import { Logger } from './Logger'; +import { YoRc } from '../models'; +import { Notifications } from './Notifications'; + + +export class AdaptiveCardCheck { + /** + * Check if yo-rc has ACE component + */ + public static async validateACEComponent() { + try { + const vsCodeAdaptiveCardsExtension = extensions.getExtension('TeamsDevApp.vscode-adaptive-cards'); + + if (vsCodeAdaptiveCardsExtension) { + return; + } + + let yoRcFiles = await workspace.findFiles('.yo-rc.json', '**/node_modules/**'); + + if (!yoRcFiles || yoRcFiles.length <= 0) { + yoRcFiles = await workspace.findFiles('src/.yo-rc.json', '**/node_modules/**'); + } + + if (!yoRcFiles || yoRcFiles.length <= 0) { + return; + } + + for (const file of yoRcFiles) { + let content: YoRc | string = await workspace.openTextDocument(file.fsPath).then(doc => doc.getText()); + + if (!content) { + continue; + } + + content = typeof content === 'string' ? JSON.parse(content) as YoRc : content; + + if(content && content['@microsoft/generator-sharepoint'] && content['@microsoft/generator-sharepoint'].aceTemplateType) { + const answer = await Notifications.info('Consider installing Adaptive Card Previewer to boost your productivity with ACE projects. Would you like to install the extension?', 'yes', 'no'); + + if (answer === 'yes') { + env.openExternal(Uri.parse('https://marketplace.visualstudio.com/items?itemName=TeamsDevApp.vscode-adaptive-cards')); + } + } + } + } catch (e) { + Logger.error(`Error validating launch.json: ${e}`); + } + } +} \ No newline at end of file diff --git a/src/services/CliActions.ts b/src/services/CliActions.ts index 4a42390..67ae68c 100644 --- a/src/services/CliActions.ts +++ b/src/services/CliActions.ts @@ -11,6 +11,7 @@ import { basename, join } from 'path'; import { EnvironmentInformation } from './EnvironmentInformation'; import { AuthProvider } from '../providers/AuthProvider'; import { CommandOutput } from '@pnp/cli-microsoft365'; +import { TeamsToolkitIntegration } from './TeamsToolkitIntegration'; export class CliActions { @@ -69,7 +70,13 @@ export class CliActions { // Change the current working directory to the root of the Project const wsFolder = await Folders.getWorkspaceFolder(); if (wsFolder) { - process.chdir(wsFolder.uri.fsPath); + let path = wsFolder.uri.fsPath; + + if (TeamsToolkitIntegration.isTeamsToolkitProject) { + path = join(path, 'src'); + } + + process.chdir(path); } await window.withProgress({ @@ -83,7 +90,13 @@ export class CliActions { if (result.stdout) { // Create a file to allow the Markdown preview to correctly open the linked/referenced files - const filePath = join(wsFolder?.uri.fsPath || '', 'spfx.upgrade.md'); + let savePath = wsFolder?.uri.fsPath; + + if (savePath && TeamsToolkitIntegration.isTeamsToolkitProject) { + savePath = join(savePath, 'src'); + } + + const filePath = join(savePath || '', 'spfx.upgrade.md'); writeFileSync(filePath, result.stdout); await commands.executeCommand('markdown.showPreview', Uri.file(filePath)); } else if (result.stderr) { @@ -103,7 +116,13 @@ export class CliActions { // Change the current working directory to the root of the Project const wsFolder = await Folders.getWorkspaceFolder(); if (wsFolder) { - process.chdir(wsFolder.uri.fsPath); + let path = wsFolder.uri.fsPath; + + if (TeamsToolkitIntegration.isTeamsToolkitProject) { + path = join(path, 'src'); + } + + process.chdir(path); } const newName = await window.showInputBox({ @@ -139,7 +158,7 @@ export class CliActions { }, async (progress: Progress<{ message?: string; increment?: number }>) => { try { let result: CommandOutput; - if(shouldGenerateNewId) { + if (shouldGenerateNewId) { result = await CliExecuter.execute('spfx project rename', 'json', { newName: newName, generateNewId: shouldGenerateNewId }); } else { result = await CliExecuter.execute('spfx project rename', 'json', { newName: newName }); @@ -162,7 +181,13 @@ export class CliActions { // Change the current working directory to the root of the Project const wsFolder = await Folders.getWorkspaceFolder(); if (wsFolder) { - process.chdir(wsFolder.uri.fsPath); + let path = wsFolder.uri.fsPath; + + if (TeamsToolkitIntegration.isTeamsToolkitProject) { + path = join(path, 'src'); + } + + process.chdir(path); } await window.withProgress({ @@ -193,7 +218,13 @@ export class CliActions { // Change the current working directory to the root of the Project const wsFolder = await Folders.getWorkspaceFolder(); if (wsFolder) { - process.chdir(wsFolder.uri.fsPath); + let path = wsFolder.uri.fsPath; + + if (TeamsToolkitIntegration.isTeamsToolkitProject) { + path = join(path, 'src'); + } + + process.chdir(path); } const name = await window.showInputBox({ @@ -227,22 +258,31 @@ export class CliActions { }); let siteUrl: string | undefined; - if (scope === 'sitecollection'){ - siteUrl = await window.showInputBox({ - title: 'Specify the URL of the site collection where the solution package will be added', - ignoreFocusOut: true, - validateInput: async (value) => { - if (!value) { - return 'Site app catalog url is required'; - } + if (scope === 'sitecollection') { + if (EnvironmentInformation.appCatalogUrls && EnvironmentInformation.appCatalogUrls.length > 1) { + siteUrl = await window.showQuickPick(EnvironmentInformation.appCatalogUrls.map(url => url, { + placeHolder: 'Select the App Catalog', + ignoreFocusOut: true, + canPickMany: false, + title: 'Select the App Catalog' + })); + } else { + siteUrl = await window.showInputBox({ + title: 'Specify the URL of the site collection where the solution package will be added', + ignoreFocusOut: true, + validateInput: async (value) => { + if (!value) { + return 'Site app catalog url is required'; + } - if (value.toLowerCase().indexOf('https://') < 0 || value.toLowerCase().indexOf('appcatalog') < 0) { - return `${value} is not a valid SharePoint Online site app catalog URL`; - } + if (value.toLowerCase().indexOf('https://') < 0 || value.toLowerCase().indexOf('appcatalog') < 0) { + return `${value} is not a valid SharePoint Online site app catalog URL`; + } - return undefined; - } - }); + return undefined; + } + }); + } if (!siteUrl) { return; @@ -324,7 +364,13 @@ export class CliActions { // Change the current working directory to the root of the Project const wsFolder = await Folders.getWorkspaceFolder(); if (wsFolder) { - process.chdir(wsFolder.uri.fsPath); + let path = wsFolder.uri.fsPath; + + if (TeamsToolkitIntegration.isTeamsToolkitProject) { + path = join(path, 'src'); + } + + process.chdir(path); } await window.withProgress({ @@ -338,7 +384,13 @@ export class CliActions { if (result.stdout) { // Create a file to allow the Markdown preview to correctly open the linked/referenced files - const filePath = join(wsFolder?.uri.fsPath || '', 'spfx.validate.md'); + let savePath = wsFolder?.uri.fsPath; + + if (savePath && TeamsToolkitIntegration.isTeamsToolkitProject) { + savePath = join(savePath, 'src'); + } + + const filePath = join(savePath || '', 'spfx.validate.md'); writeFileSync(filePath, result.stdout); await commands.executeCommand('markdown.showPreview', Uri.file(filePath)); } else if (result.stderr) { diff --git a/src/services/DebuggerCheck.ts b/src/services/DebuggerCheck.ts index 32175b2..4fce999 100644 --- a/src/services/DebuggerCheck.ts +++ b/src/services/DebuggerCheck.ts @@ -34,7 +34,11 @@ export class DebuggerCheck { * @returns */ private static async validateLaunch(url: string) { - const launchFiles = await workspace.findFiles('.vscode/launch.json', '**/node_modules/**'); + let launchFiles = await workspace.findFiles('.vscode/launch.json', '**/node_modules/**'); + + if (!launchFiles || launchFiles.length <= 0) { + launchFiles = await workspace.findFiles('src/.vscode/launch.json', '**/node_modules/**'); + } if (!launchFiles || launchFiles.length <= 0) { return; @@ -75,7 +79,11 @@ export class DebuggerCheck { * @returns */ private static async validateServe(url: string) { - const serveFiles = await workspace.findFiles('config/serve.json', '**/node_modules/**'); + let serveFiles = await workspace.findFiles('config/serve.json', '**/node_modules/**'); + + if (!serveFiles || serveFiles.length <= 0) { + serveFiles = await workspace.findFiles('src/config/serve.json', '**/node_modules/**'); + } if (!serveFiles || serveFiles.length <= 0) { return; diff --git a/src/services/Dependencies.ts b/src/services/Dependencies.ts index 24043d9..805f26a 100644 --- a/src/services/Dependencies.ts +++ b/src/services/Dependencies.ts @@ -4,12 +4,12 @@ import { execSync } from 'child_process'; import { commands, ProgressLocation, ThemeIcon, window } from 'vscode'; import { Logger } from './Logger'; import { NpmLs, Subscription } from '../models'; -import { Terminal } from './Terminal'; +import { TerminalCommandExecuter } from './TerminalCommandExecuter'; import { Extension } from './Extension'; const SUPPORTED_VERSIONS = ['16.13', '18.17.1']; -const DEPENDENCIES = ['gulp-cli', 'yo@4.3.1', '@microsoft/generator-sharepoint']; +const DEPENDENCIES = ['gulp-cli', 'yo', '@microsoft/generator-sharepoint']; export class Dependencies { @@ -49,7 +49,7 @@ export class Dependencies { progress.report({ message: 'Checking npm dependencies...' }); const command = 'npm list -g --json --silent'; - const result = execSync(command, { shell: Terminal.shell, timeout: 15000 }); + const result = execSync(command, { shell: TerminalCommandExecuter.shell, timeout: 15000 }); if (!result) { Notifications.error('Failed checking dependencies'); @@ -109,12 +109,12 @@ export class Dependencies { */ private static isValidNodeJs() { try { - const output = execSync('node --version', { shell: Terminal.shell }); + const output = execSync('node --version', { shell: TerminalCommandExecuter.shell }); const match = /v(?\d+)\.(?\d+)\.(?\d+)/gm.exec(output.toString()); Logger.info(`Node.js version: ${output}`); - const npmOutput = execSync('npm --version', { shell: Terminal.shell }); + const npmOutput = execSync('npm --version', { shell: TerminalCommandExecuter.shell }); Logger.info(`npm version: ${npmOutput}`); if (!match) { diff --git a/src/services/Scaffolder.ts b/src/services/Scaffolder.ts index a9ca4f2..eb30361 100644 --- a/src/services/Scaffolder.ts +++ b/src/services/Scaffolder.ts @@ -14,8 +14,9 @@ import { Extension } from './Extension'; import download from 'github-directory-downloader/esm'; import { CliExecuter } from './CliCommandExecuter'; import { getPlatform } from '../utils'; -import { Terminal } from './Terminal'; +import { TerminalCommandExecuter } from './TerminalCommandExecuter'; import { execSync } from 'child_process'; +import { TeamsToolkitIntegration } from './TeamsToolkitIntegration'; export const PROJECT_FILE = 'project.pnp'; @@ -283,7 +284,13 @@ export class Scaffolder { try { if (!folderPath) { const wsFolder = await Folders.getWorkspaceFolder(); - folderPath = wsFolder?.uri.fsPath || ''; + let path = wsFolder?.uri.fsPath; + + if (path && TeamsToolkitIntegration.isTeamsToolkitProject) { + path = join(path, 'src'); + } + + folderPath = path || ''; } const result = await Executer.executeCommand(folderPath, yoCommand); @@ -310,7 +317,7 @@ export class Scaffolder { * @returns */ private static async aceComponent(): Promise<{ aceTemplateType: NameValue, componentName: string } | undefined> { - const output = execSync('node --version', { shell: Terminal.shell }); + const output = execSync('node --version', { shell: TerminalCommandExecuter.shell }); const match = /v(?\d+)\.(?\d+)\.(?\d+)/gm.exec(output.toString()); const nodeVersion = null === match ? '18' : match.groups?.major_version!; const adaptiveCardTypes = nodeVersion === '16' ? AdaptiveCardTypesNode16 : AdaptiveCardTypesNode18; diff --git a/src/services/TeamsToolkitIntegration.ts b/src/services/TeamsToolkitIntegration.ts new file mode 100644 index 0000000..f839215 --- /dev/null +++ b/src/services/TeamsToolkitIntegration.ts @@ -0,0 +1,15 @@ +export class TeamsToolkitIntegration { + private static _isTeamsToolkitProject: boolean | undefined = undefined; + + public static get isTeamsToolkitProject(): boolean | undefined { + return this._isTeamsToolkitProject; + } + + public static set isTeamsToolkitProject(value: boolean | undefined) { + this._isTeamsToolkitProject = value; + } + + public static reset() { + this._isTeamsToolkitProject = undefined; + } +} \ No newline at end of file diff --git a/src/services/Terminal.ts b/src/services/TerminalCommandExecuter.ts similarity index 63% rename from src/services/Terminal.ts rename to src/services/TerminalCommandExecuter.ts index 10a50dc..67c73f7 100644 --- a/src/services/Terminal.ts +++ b/src/services/TerminalCommandExecuter.ts @@ -1,35 +1,38 @@ -import { commands, ThemeIcon, workspace, window } from 'vscode'; +import { commands, ThemeIcon, workspace, window, Terminal } from 'vscode'; import { Commands } from '../constants'; import { Subscription } from '../models'; import { Extension } from './Extension'; import { getPlatform } from '../utils'; +import { TeamsToolkitIntegration } from './TeamsToolkitIntegration'; +import { Folders } from './Folders'; +import { join } from 'path'; interface ShellSetting { path: string; } -export class Terminal { +export class TerminalCommandExecuter { private static shellPath: string | undefined = undefined; public static register() { const subscriptions: Subscription[] = Extension.getInstance().subscriptions; - Terminal.registerCommands(subscriptions); + TerminalCommandExecuter.registerCommands(subscriptions); - Terminal.initShellPath(); + TerminalCommandExecuter.initShellPath(); } public static get shell() { - return Terminal.shellPath; + return TerminalCommandExecuter.shellPath; } private static initShellPath() { - const shell: string | { path: string } | undefined = Terminal.getShellPath(); + const shell: string | { path: string } | undefined = TerminalCommandExecuter.getShellPath(); if (typeof shell !== 'string' && !!shell) { - Terminal.shellPath = shell.path; + TerminalCommandExecuter.shellPath = shell.path; } else { - Terminal.shellPath = shell || undefined; + TerminalCommandExecuter.shellPath = shell || undefined; } } @@ -58,16 +61,11 @@ export class Terminal { private static registerCommands(subscriptions: Subscription[]) { subscriptions.push( - commands.registerCommand(Commands.executeTerminalCommand, Terminal.runCommand) + commands.registerCommand(Commands.executeTerminalCommand, TerminalCommandExecuter.runCommand) ); } - // eslint-disable-next-line no-unused-vars - public static async runCommand(command: string, args: string[]) { - Terminal.runInTerminal(command, 'Gulp task', 'tasks-list-configure'); - } - - public static async runInTerminal(command: string, name?: string, icon?: string) { + private static async createTerminal(name?: string, icon?: string): Promise { let terminal = window.terminals.find(t => t.name === name); if (!terminal) { @@ -83,9 +81,31 @@ export class Terminal { } } + return terminal; + } + + private static async runInTerminal(command: string, terminal?: Terminal | undefined) { if (terminal) { terminal.show(true); terminal.sendText(` ${command}`); } } + + // eslint-disable-next-line no-unused-vars + public static async runCommand(command: string, args: string[]) { + const terminal = await TerminalCommandExecuter.createTerminal('Gulp task', 'tasks-list-configure'); + + const wsFolder = await Folders.getWorkspaceFolder(); + if (wsFolder) { + let currentProjectPath = wsFolder.uri.fsPath; + + if (TeamsToolkitIntegration.isTeamsToolkitProject) { + currentProjectPath = join(currentProjectPath, 'src'); + } + + TerminalCommandExecuter.runInTerminal(`cd ${currentProjectPath}`, terminal); + } + + TerminalCommandExecuter.runInTerminal(command, terminal); + } } \ No newline at end of file