├── .azuredevops └── pull_request_template.md ├── .eslintrc.js ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── pull_request_template.md ├── .gitignore ├── .vs ├── powerapps-project-template │ └── v16 │ │ └── .suo └── slnx.sqlite ├── .vscode ├── extensions.json ├── launch.json └── tasks.json ├── CONTRIBUTING.md ├── GitVersion.yml ├── LICENSE ├── README.md ├── __tests__ ├── app.js ├── helpers.js └── sample-with-solution │ ├── .gitattributes │ ├── .gitignore │ ├── .npmignore │ ├── .nuke │ ├── .vscode │ ├── extensions.json │ ├── launch.json │ ├── settings.json │ └── tasks.json │ ├── CONTRIBUTING.md │ ├── GitVersion.yml │ ├── NuGet.config │ ├── README.md │ ├── TestClient.TestPackage.sln │ ├── build.ps1 │ ├── build │ ├── .editorconfig │ ├── Build.cs │ ├── Configuration.cs │ ├── SolutionConfiguration.cs │ ├── SolutionType.cs │ ├── _build.csproj │ └── _build.csproj.DotSettings │ ├── deploy │ ├── PackageTemplate.cs │ ├── PkgFolder │ │ └── ImportConfig.xml │ ├── TestClient.TestPackage.Deployment.csproj │ ├── TestClient.TestPackage.Deployment.ruleset │ └── app.config │ ├── pipelines │ ├── azure-pipelines-pull-request.yml │ ├── azure-pipelines.yml │ ├── scripts │ │ └── Add-BuildTagForEachUpdatedSolution.ps1 │ └── templates │ │ ├── include-build-stage.yml │ │ └── include-solution-checker-stage.yml │ ├── src │ ├── common │ │ ├── TestClient.TestPackage.BusinessLogic │ │ │ ├── CrmPluginRegistrationAttribute.cs │ │ │ ├── Extensions │ │ │ │ └── InArgumentExtensions.cs │ │ │ ├── IRepositoryFactory.cs │ │ │ ├── Logging │ │ │ │ ├── ConsoleLogWriter.cs │ │ │ │ ├── ILogWriter.cs │ │ │ │ ├── Severity.cs │ │ │ │ └── TracingServiceLogWriter.cs │ │ │ ├── Plugin.cs │ │ │ ├── RepositoryFactory.cs │ │ │ ├── TestClient.TestPackage.BusinessLogic.projitems │ │ │ ├── TestClient.TestPackage.BusinessLogic.shproj │ │ │ └── WorkflowActivity.cs │ │ ├── TestClient.TestPackage.Container │ │ │ ├── TestClient.TestPackage.Container.csproj │ │ │ ├── TestClient.TestPackage.Container.ruleset │ │ │ └── TestClient.TestPackage.Container.snk │ │ ├── TestClient.TestPackage.Model │ │ │ ├── TestClient.TestPackage.Model.projitems │ │ │ ├── TestClient.TestPackage.Model.shproj │ │ │ └── spkl.json │ │ └── TestClient.TestPackage.Repositories │ │ │ ├── CrmRepository.cs │ │ │ ├── CrmRepository{TContext,TEntity}.cs │ │ │ ├── ICrmRepository.cs │ │ │ ├── ICrmRepository{TEntity}.cs │ │ │ ├── OrganizationServiceExtensions.cs │ │ │ ├── SandboxWebClient.cs │ │ │ ├── TestClient.TestPackage.Repositories.projitems │ │ │ └── TestClient.TestPackage.Repositories.shproj │ └── solutions │ │ └── test_TestPackage_TestSolution │ │ ├── ExtractMappingFile.xml │ │ ├── PackMappingFile.xml │ │ ├── solution.json │ │ ├── spkl.json │ │ └── test_TestPackage_TestSolution.cdsproj │ └── tests │ ├── TestClient.TestPackage.IntegrationTests │ ├── CommonDataServiceFixture.cs │ ├── GlobalSuppressions.cs │ ├── TestClient.TestPackage.IntegrationTests.csproj │ ├── TestClient.TestPackage.IntegrationTests.ruleset │ ├── app.config │ └── xunit.runner.json │ ├── TestClient.TestPackage.UiTests │ ├── Bindings │ │ └── keep │ ├── Data │ │ └── keep │ ├── Extensions │ │ └── StringExtensions.cs │ ├── TestClient.TestPackage.UiTests.csproj │ ├── TestClient.TestPackage.UiTests.ruleset │ ├── power-apps-bindings.yml │ └── specflow.json │ └── TestClient.TestPackage.UnitTests │ ├── GlobalSuppressions.cs │ ├── TestClient.TestPackage.UnitTests.csproj │ ├── TestClient.TestPackage.UnitTests.ruleset │ ├── WorkflowActivityTests.cs │ ├── app.config │ ├── unit.runsettings │ └── xunit.runner.json ├── package-lock.json ├── package.json ├── pipelines ├── azure-pipelines-pull-request.yml ├── azure-pipelines.yml └── templates │ └── build-and-test-job.yml ├── src ├── common │ ├── ISolution.ts │ ├── MappingFileTransformer.ts │ ├── PackageReader.ts │ └── utilities.ts └── generators │ ├── app │ ├── index.ts │ ├── templates │ │ └── source │ │ │ ├── .gitattributes │ │ │ ├── .gitignore │ │ │ ├── .npmignore │ │ │ ├── .nuke │ │ │ ├── .vscode │ │ │ ├── extensions.json │ │ │ ├── launch.json │ │ │ ├── settings.json │ │ │ └── tasks.json │ │ │ ├── CONTRIBUTING.md │ │ │ ├── Client.Package.sln │ │ │ ├── GitVersion.yml │ │ │ ├── NuGet.config │ │ │ ├── README.md │ │ │ ├── build.ps1 │ │ │ ├── build │ │ │ ├── .editorconfig │ │ │ ├── Build.cs │ │ │ ├── Configuration.cs │ │ │ ├── SolutionConfiguration.cs │ │ │ ├── SolutionType.cs │ │ │ ├── _build.csproj │ │ │ └── _build.csproj.DotSettings │ │ │ ├── deploy │ │ │ ├── Client.Package.Deployment.csproj │ │ │ ├── Client.Package.Deployment.ruleset │ │ │ ├── PackageTemplate.cs │ │ │ ├── PkgFolder │ │ │ │ └── ImportConfig.xml │ │ │ └── app.config │ │ │ ├── pipelines │ │ │ ├── azure-pipelines-pull-request.yml │ │ │ ├── azure-pipelines.yml │ │ │ ├── scripts │ │ │ │ └── Add-BuildTagForEachUpdatedSolution.ps1 │ │ │ └── templates │ │ │ │ ├── include-build-stage.yml │ │ │ │ └── include-solution-checker-stage.yml │ │ │ ├── src │ │ │ └── common │ │ │ │ ├── Client.Package.BusinessLogic │ │ │ │ ├── Client.Package.BusinessLogic.projitems │ │ │ │ ├── Client.Package.BusinessLogic.shproj │ │ │ │ ├── CrmPluginRegistrationAttribute.cs │ │ │ │ ├── Extensions │ │ │ │ │ └── InArgumentExtensions.cs │ │ │ │ ├── IRepositoryFactory.cs │ │ │ │ ├── Logging │ │ │ │ │ ├── ConsoleLogWriter.cs │ │ │ │ │ ├── ILogWriter.cs │ │ │ │ │ ├── Severity.cs │ │ │ │ │ └── TracingServiceLogWriter.cs │ │ │ │ ├── Plugin.cs │ │ │ │ ├── RepositoryFactory.cs │ │ │ │ └── WorkflowActivity.cs │ │ │ │ ├── Client.Package.Container │ │ │ │ ├── Client.Package.Container.csproj │ │ │ │ ├── Client.Package.Container.ruleset │ │ │ │ └── Client.Package.Container.snk │ │ │ │ ├── Client.Package.Model │ │ │ │ ├── Client.Package.Model.projitems │ │ │ │ ├── Client.Package.Model.shproj │ │ │ │ └── spkl.json │ │ │ │ └── Client.Package.Repositories │ │ │ │ ├── Client.Package.Repositories.projitems │ │ │ │ ├── Client.Package.Repositories.shproj │ │ │ │ ├── CrmRepository.cs │ │ │ │ ├── CrmRepository{TContext,TEntity}.cs │ │ │ │ ├── ICrmRepository.cs │ │ │ │ ├── ICrmRepository{TEntity}.cs │ │ │ │ ├── OrganizationServiceExtensions.cs │ │ │ │ └── SandboxWebClient.cs │ │ │ └── tests │ │ │ ├── Client.Package.IntegrationTests │ │ │ ├── Client.Package.IntegrationTests.csproj │ │ │ ├── Client.Package.IntegrationTests.ruleset │ │ │ ├── CommonDataServiceFixture.cs │ │ │ ├── GlobalSuppressions.cs │ │ │ ├── app.config │ │ │ └── xunit.runner.json │ │ │ ├── Client.Package.UiTests │ │ │ ├── Bindings │ │ │ │ └── keep │ │ │ ├── Client.Package.UiTests.csproj │ │ │ ├── Client.Package.UiTests.ruleset │ │ │ ├── Data │ │ │ │ └── keep │ │ │ ├── Extensions │ │ │ │ └── StringExtensions.cs │ │ │ ├── power-apps-bindings.yml │ │ │ └── specflow.json │ │ │ └── Client.Package.UnitTests │ │ │ ├── Client.Package.UnitTests.csproj │ │ │ ├── Client.Package.UnitTests.ruleset │ │ │ ├── GlobalSuppressions.cs │ │ │ ├── WorkflowActivityTests.cs │ │ │ ├── app.config │ │ │ ├── unit.runsettings │ │ │ └── xunit.runner.json │ └── types │ │ ├── BuildProcess.d.ts │ │ └── DeployPhase.d.ts │ ├── azuredevops │ ├── AzureDevOpsScaffolder.ts │ ├── IPackageDetails.ts │ ├── IScaffoldResult.ts │ ├── IScaffoldSettings.ts │ ├── definitions │ │ ├── build │ │ │ └── build.json │ │ ├── extensions.json │ │ ├── release │ │ │ └── release.json │ │ ├── serviceendpoints │ │ │ └── ci-environment.json │ │ └── variablegroups │ │ │ ├── ci-environment.json │ │ │ └── integration-tests.json │ ├── generator │ │ ├── BuildGenerator.ts │ │ ├── ExtensionGenerator.ts │ │ ├── IGenerator.ts │ │ ├── ReleaseGenerator.ts │ │ ├── RepoGenerator.ts │ │ ├── ServiceEndpointGenerator.ts │ │ └── VarGroupGenerator.ts │ └── index.ts │ ├── data │ ├── index.ts │ └── templates │ │ └── source │ │ └── data │ │ ├── {{Solution}}DataExport.json │ │ ├── {{Solution}}DataImport.json │ │ └── {{Solution}}DataSchema.xml │ ├── pluginassembly │ ├── index.ts │ └── templates │ │ ├── ExtractMappingFile.xml │ │ ├── PackMappingFile.xml │ │ ├── source │ │ └── {{Client}}.{{Package}}.{{Solution}} │ │ │ ├── .vscode │ │ │ └── tasks.json │ │ │ ├── app.config │ │ │ ├── {{Client}}.{{Package}}.{{Solution}}.csproj │ │ │ ├── {{Client}}.{{Package}}.{{Solution}}.ruleset │ │ │ └── {{Client}}.{{Package}}.{{Solution}}.snk │ │ └── spkl.json │ ├── powerbi │ ├── README.md │ ├── index.ts │ └── templates │ │ └── source │ │ └── {{Client}}.{{Package}}.PowerBI │ │ ├── reports │ │ └── readme.txt │ │ ├── scripts │ │ ├── PowerBI_Automation_Module.psm1 │ │ ├── UpdateDataSourceCredential.ps1 │ │ ├── UpdateParameters.ps1 │ │ └── UploadReport.ps1 │ │ └── {{Client}}.{{Package}}.PowerBI.csproj │ ├── scripts │ ├── index.ts │ └── templates │ │ ├── ExtractMappingFile.xml │ │ ├── PackMappingFile.xml │ │ ├── source │ │ └── webresources │ │ │ └── scripts │ │ │ ├── .npmrc │ │ │ ├── package.json │ │ │ ├── tsconfig.json │ │ │ └── tslint.json │ │ └── spkl.json │ └── solution │ ├── index.ts │ └── templates │ └── source │ └── {{solutionUniqueName}} │ ├── ExtractMappingFile.xml │ ├── PackMappingFile.xml │ ├── spkl.json │ └── {{solutionUniqueName}}.cdsproj ├── tsconfig.eslint.json ├── tsconfig.json └── typings └── renamer.d.ts /.azuredevops/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Thank you for your contribution to the _powerapps-project-generator_ repo. 2 | Before submitting this PR, please make sure: 3 | 4 | - [ ] Your code builds clean without any errors or warnings 5 | - [ ] You've updated the package version in `package.json` if appropriate (source code has change) 6 | - [ ] You've updated any changes of usage in `README.md` if appropriate 7 | 8 | Also, if you have learnt anything of value to others wishing to contribute, please add some relevant notes to `CONTRIBUTING.md`. 9 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | node: true, 4 | es2021: true, 5 | mocha: true, 6 | }, 7 | extends: [ 8 | 'airbnb-base', 9 | 'airbnb-typescript/base', 10 | ], 11 | parser: '@typescript-eslint/parser', 12 | parserOptions: { 13 | ecmaVersion: 12, 14 | sourceType: 'module', 15 | project: './tsconfig.eslint.json', 16 | tsconfigRootDir: __dirname, 17 | }, 18 | plugins: [ 19 | '@typescript-eslint', 20 | ], 21 | rules: { 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior, in case people don't have core.autocrlf set. 2 | * text=auto 3 | 4 | *.ts text eol=lf 5 | *.js text eol=lf 6 | 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Describe the bug 11 | _A clear and concise description of what the bug is._ 12 | 13 | ## To reproduce 14 | _Steps to reproduce the behaviour._ 15 | 16 | ## Expected behaviour 17 | _A clear and concise description of what you expected to happen._ 18 | 19 | ## Screenshots 20 | _If applicable, add screenshots to help explain your problem._ 21 | 22 | ## Environment (please complete the following information): 23 | - yo version 24 | - generator-powerapps-project version 25 | - Power Apps CLI version 26 | - Power Apps environment version 27 | 28 | ## Additional context 29 | _Add any other context about the problem here._ 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: 'enhancement' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Purpose 2 | _Describe the problem or feature in addition to a link to the issue(s)._ 3 | 4 | ## Approach 5 | _How does this change address the problem?_ 6 | 7 | ## TODOs 8 | - [ ] Documentation updated (if required) 9 | - [ ] Build and tests successful 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | 3 | /common 4 | /generators 5 | /debug*/ 6 | 7 | .vscode/settings.json 8 | 9 | # Tests output 10 | /.nyc_output 11 | /coverage 12 | 13 | # Visual Studio cache/options directory 14 | .vs/ -------------------------------------------------------------------------------- /.vs/powerapps-project-template/v16/.suo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Capgemini/powerapps-project-template/b7dbaf1e8945349d4c83f72bf0dc22c2f2d476d1/.vs/powerapps-project-template/v16/.suo -------------------------------------------------------------------------------- /.vs/slnx.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Capgemini/powerapps-project-template/b7dbaf1e8945349d4c83f72bf0dc22c2f2d476d1/.vs/slnx.sqlite -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["dbaeumer.vscode-eslint"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Main Generator", 11 | "runtimeArgs": ["${workspaceFolder}/node_modules/yo/lib/cli.js"], 12 | "program": "${workspaceFolder}/src/generators/app/index.ts", 13 | "cwd": "${workspaceFolder}/debug", 14 | "console": "integratedTerminal", 15 | "internalConsoleOptions": "neverOpen", 16 | "skipFiles": ["/**"], 17 | "preLaunchTask": "build and clean-debug" 18 | }, 19 | { 20 | "type": "node", 21 | "request": "launch", 22 | "name": "Azure DevOps Generator", 23 | "runtimeArgs": ["${workspaceFolder}/node_modules/yo/lib/cli.js"], 24 | "program": "${workspaceFolder}/src/generators/azuredevops/index.ts", 25 | "cwd": "${workspaceFolder}/debug", 26 | "console": "integratedTerminal", 27 | "internalConsoleOptions": "neverOpen", 28 | "skipFiles": ["/**"], 29 | "preLaunchTask": "npm: build" 30 | }, 31 | { 32 | "type": "node", 33 | "request": "launch", 34 | "name": "Solution Generator", 35 | "runtimeArgs": ["${workspaceFolder}/node_modules/yo/lib/cli.js"], 36 | "program": "${workspaceFolder}/src/generators/solution/index.ts", 37 | "cwd": "${workspaceFolder}/debug", 38 | "console": "integratedTerminal", 39 | "internalConsoleOptions": "neverOpen", 40 | "skipFiles": ["/**"], 41 | "preLaunchTask": "npm: build" 42 | }, 43 | { 44 | "type": "node", 45 | "request": "launch", 46 | "name": "Scripts Generator", 47 | "runtimeArgs": ["${workspaceFolder}/node_modules/yo/lib/cli.js"], 48 | "program": "${workspaceFolder}/src/generators/scripts/index.ts", 49 | "cwd": "${workspaceFolder}/debug", 50 | "console": "integratedTerminal", 51 | "internalConsoleOptions": "neverOpen", 52 | "skipFiles": ["/**"], 53 | "preLaunchTask": "npm: build" 54 | }, 55 | { 56 | "type": "node", 57 | "request": "launch", 58 | "name": "Plug-In Assembly Generator", 59 | "runtimeArgs": ["${workspaceFolder}/node_modules/yo/lib/cli.js"], 60 | "program": "${workspaceFolder}/src/generators/pluginassembly/index.ts", 61 | "cwd": "${workspaceFolder}/debug", 62 | "console": "integratedTerminal", 63 | "internalConsoleOptions": "neverOpen", 64 | "skipFiles": ["/**"], 65 | "preLaunchTask": "npm: build" 66 | }, 67 | { 68 | "type": "node", 69 | "request": "launch", 70 | "name": "Data Generator", 71 | "runtimeArgs": ["${workspaceFolder}/node_modules/yo/lib/cli.js"], 72 | "program": "${workspaceFolder}/src/generators/data/index.ts", 73 | "cwd": "${workspaceFolder}/debug", 74 | "console": "integratedTerminal", 75 | "internalConsoleOptions": "neverOpen", 76 | "skipFiles": ["/**"], 77 | "preLaunchTask": "npm: build" 78 | }, 79 | { 80 | "type": "node", 81 | "request": "launch", 82 | "name": "PowerBI Generator", 83 | "runtimeArgs": ["${workspaceFolder}/node_modules/yo/lib/cli.js"], 84 | "program": "${workspaceFolder}/src/generators/powerbi/index.ts", 85 | "cwd": "${workspaceFolder}/debug", 86 | "console": "integratedTerminal", 87 | "internalConsoleOptions": "neverOpen", 88 | "skipFiles": ["/**"], 89 | "preLaunchTask": "npm: build" 90 | } 91 | ] 92 | } 93 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "npm: build", 8 | "type": "npm", 9 | "script": "build", 10 | "group": { 11 | "kind": "build", 12 | "isDefault": true 13 | }, 14 | "problemMatcher": ["$tsc-watch"] 15 | }, 16 | { 17 | "label": "npm: clean-debug", 18 | "type": "npm", 19 | "script": "clean-debug", 20 | "problemMatcher": ["$tsc-watch"] 21 | }, 22 | { 23 | "label": "build and clean-debug", 24 | "dependsOn": ["npm: build", "npm: clean-debug"], 25 | "dependsOrder": "parallel", 26 | "group": "build", 27 | "problemMatcher": ["$tsc-watch"] 28 | }, 29 | { 30 | "type": "npm", 31 | "script": "lint", 32 | "problemMatcher": [ 33 | "$eslint-stylish" 34 | ], 35 | "label": "npm: lint", 36 | "detail": "eslint -c .eslintrc.js --ext .ts ./src" 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | This package is built using the generator framework provided by [Yeoman](https://yeoman.io/). Yeoman is a scaffolding tool which helps you to build generators to scaffold your projects. This generator is written in [TypeScript](https://www.typescriptlang.org/). 4 | 5 | ## Table of Contents 6 | 7 | - [Getting started](#getting-started?) 8 | - [Package anatomy](#package-anatomy) 9 | - [Updating the templates](#updating-the-templates) 10 | - [Updating the generation process](#updating-the-generation-process) 11 | - [Testing your changes](#testing-your-changes) 12 | 13 | ## Getting started 14 | 15 | It is highly recommended to familiarise yourself with [Yeoman](https://yeoman.io/) and their documentation on [creating an accelerator](https://yeoman.io/authoring/) before contributing to this repository. 16 | 17 | ## Repository anatomy 18 | 19 | The Yeoman generator source code is within the `src` folder. There are two folders within this: 20 | 21 | - A `common` folder for common classes used across the generators 22 | - A `generators` folder containing a folder for each generator 23 | 24 | This differs from the Yeoman documentation which has the `generators` folder at the root of the project. The reason for this is that the `src` folder contains TypeScript source files rather than the compiled JavaScript. 25 | 26 | Another difference to be aware of is the fact that the main generator does not only interact with the file system but also the Azure DevOps API. Some of the Azure DevOps configuration which is passed to the API is stored in the `definitions` folder within the `app` generator. 27 | 28 | ## Updating the templates 29 | 30 | Simple changes - involving the raw source code used in the template process - can most likely be updated without updating the generator code. 31 | 32 | You can find these files in the `templates` folder of the generator or sub-generator you wish to update. For example, changes to the code generated by the main generator can be done within the `src/generators/app/templates` folder. 33 | 34 | Refer to the Yeoman documentation if unsure on template syntax. 35 | 36 | ## Updating the generation process 37 | 38 | Updating the generation process can be done by modifying the source code within the `src` folder. The entry point for each generator is the `index.ts` file. For more information on writing generators, refer to the Yeoman documentation. 39 | 40 | You can compile your changes by running the TypeScript compiler. Run this in watch mode within Visual Studio Code by opening the command palette with `ctrl + shift + p`, selecting `Tasks: Run Task` and then `tsc: watch - tsconfig.json`. 41 | 42 | > Note that only compiling the TypeScript source will not update the template files in your build output. In order to do this, you must run the `npm: build` task. 43 | 44 | ## Testing your changes 45 | 46 | You can test your changes by running the generator locally. Note that you will most likely need to create a temporary Azure DevOps project for this purpose. 47 | 48 | To run the generators, first run the npm `build` task. This will build the generator source code and output it to the root of the project. The `.vscode/launch.json` file in this repository has been updated to include several debug configurations. This means you can easily debug the generators within Visual Studio Code. You will to first run the main generator before you can debug the others. The debug configurations will use the generators within the `debug` folder at the root of the project. -------------------------------------------------------------------------------- /GitVersion.yml: -------------------------------------------------------------------------------- 1 | assembly-versioning-scheme: MajorMinorPatch 2 | mode: Mainline 3 | 4 | major-version-bump-message: "(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\\([\\w\\s]*\\))?(!:|:.*\\n\\n.*\\n\\n.*BREAKING.*).*" 5 | minor-version-bump-message: "(feat)(\\([\\w\\s]*\\))?:" 6 | patch-version-bump-message: "(build|chore|ci|docs|fix|perf|refactor|revert|style|test)(\\([\\w\\s]*\\))?:(.*\\n\\n.*\\n\\n.*BREAKING.*){0}" 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Capgemini 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /__tests__/app.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const helpers = require('yeoman-test'); 5 | const { getAllFiles, assertFileLines } = require('./helpers'); 6 | 7 | const testGeneration = path.join(__dirname, 'sample-with-solution'); 8 | const testGenerationFiles = getAllFiles(testGeneration); 9 | 10 | describe('generator-powerapps-project:app', () => { 11 | let runResult; 12 | 13 | describe('existing solution = no', () => { 14 | before(async function beforeTestHook() { 15 | this.timeout(10000); 16 | 17 | runResult = await helpers 18 | .run(path.join(__dirname, '../generators/app')) 19 | .withPrompts({ 20 | client: 'TestClient', 21 | package: 'TestPackage', 22 | existingSolution: false, 23 | prefix: 'test', 24 | solution: 'TestSolution', 25 | pacProfile: 'test', 26 | environment: 'https://dev.com', 27 | hasStagingEnvironment: true, 28 | stagingEnvironment: 'https://staging.com', 29 | }); 30 | }); 31 | 32 | it('generates to correct files', () => { 33 | assert.deepEqual( 34 | getAllFiles(runResult.cwd), 35 | testGenerationFiles, 36 | 'File list does not match.', 37 | ); 38 | }); 39 | 40 | testGenerationFiles.forEach((file) => { 41 | it(`generates ${file} content`, () => { 42 | assertFileLines( 43 | runResult.fs.read(file, 'utf8'), 44 | fs.readFileSync(path.join(testGeneration, file), 'utf8'), 45 | `${file} does not match.`, 46 | ); 47 | }); 48 | }); 49 | }); 50 | 51 | describe('existing solution = yes', () => { 52 | before(async function beforeTestHook() { 53 | this.timeout(10000); 54 | 55 | runResult = await helpers 56 | .run(path.join(__dirname, '../generators/app')) 57 | .withPrompts({ 58 | client: 'TestClient', 59 | package: 'TestPackage', 60 | existingSolution: true, 61 | solutionUniqueName: 'test_TestPackage_TestSolution', 62 | pacProfile: 'test', 63 | environment: 'https://dev.com', 64 | hasStagingEnvironment: true, 65 | stagingEnvironment: 'https://staging.com', 66 | }); 67 | }); 68 | 69 | it('generates to correct files', () => { 70 | assert.deepEqual( 71 | getAllFiles(runResult.cwd), 72 | testGenerationFiles, 73 | 'File list does not match.', 74 | ); 75 | }); 76 | 77 | testGenerationFiles.forEach((file) => { 78 | it(`generates ${file} content`, () => { 79 | assertFileLines( 80 | runResult.fs.read(file, 'utf8'), 81 | fs.readFileSync(path.join(testGeneration, file), 'utf8'), 82 | `${file} does not match.`, 83 | ); 84 | }); 85 | }); 86 | }); 87 | }); 88 | -------------------------------------------------------------------------------- /__tests__/helpers.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | 5 | function getAllFiles(baseDir, dirPath = '', currentArrayOfFiles = []) { 6 | const items = fs.readdirSync(path.join(baseDir, dirPath)); 7 | let arrayOfFiles = currentArrayOfFiles; 8 | 9 | items.forEach((file) => { 10 | const fileStats = fs.statSync(path.join(baseDir, dirPath, file)); 11 | 12 | if (fileStats.isDirectory()) { 13 | arrayOfFiles = getAllFiles(baseDir, path.join(dirPath, file), arrayOfFiles); 14 | } else { 15 | arrayOfFiles.push(path.join(dirPath, file)); 16 | } 17 | }); 18 | 19 | return arrayOfFiles; 20 | } 21 | 22 | function assertFileLines(actualFileContent, expectedFileContent, message) { 23 | assert.equal( 24 | actualFileContent.replace(/\r?\n/g, '\n'), 25 | expectedFileContent.replace(/\r?\n/g, '\n'), 26 | message, 27 | ); 28 | } 29 | 30 | module.exports = { 31 | getAllFiles, 32 | assertFileLines, 33 | }; 34 | -------------------------------------------------------------------------------- /__tests__/sample-with-solution/.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /__tests__/sample-with-solution/.npmignore: -------------------------------------------------------------------------------- 1 | # This can be deleted once generated. If this doesn't exist, NPM will convert the .gitignore file into this file resulting in no .gitignore file for a generated project. -------------------------------------------------------------------------------- /__tests__/sample-with-solution/.nuke: -------------------------------------------------------------------------------- 1 | TestClient.TestPackage.sln -------------------------------------------------------------------------------- /__tests__/sample-with-solution/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. 3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 4 | 5 | // List of extensions which should be recommended for users of this workspace. 6 | "recommendations": [ 7 | "DotJoshJohnson.xml", 8 | "cake-build.cake-vscode", 9 | "esbenp.prettier-vscode", 10 | "ms-dotnettools.csharp", 11 | "eamodio.gitlens", 12 | "ms-vscode.vscode-typescript-tslint-plugin" 13 | ], 14 | // List of extensions recommended by VS Code that should not be recommended for users of this workspace. 15 | "unwantedRecommendations": [ 16 | 17 | ] 18 | } -------------------------------------------------------------------------------- /__tests__/sample-with-solution/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/build/bin/Debug/_build.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}/build", 16 | // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console 17 | "console": "internalConsole", 18 | "stopAtEntry": false 19 | }, 20 | { 21 | "name": ".NET Core Attach", 22 | "type": "coreclr", 23 | "request": "attach", 24 | "processId": "${command:pickProcess}" 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /__tests__/sample-with-solution/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "**/.git": true, 4 | "**/.svn": true, 5 | "**/.hg": true, 6 | "**/CVS": true, 7 | "**/.DS_Store": true, 8 | "**/npm-debug.log": true, 9 | "**/obj/": true, 10 | "**/.vs/": true 11 | }, 12 | "cake.taskRunner": { 13 | "autoDetect": false 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /__tests__/sample-with-solution/GitVersion.yml: -------------------------------------------------------------------------------- 1 | mode: mainline 2 | 3 | major-version-bump-message: "(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\\([\\w\\s]*\\))?(!:|:.*\\n\\n.*\\n\\n.*BREAKING.*).*" 4 | minor-version-bump-message: "(feat)(\\([\\w\\s]*\\))?:" 5 | patch-version-bump-message: "(build|chore|ci|docs|fix|perf|refactor|revert|style|test)(\\([\\w\\s]*\\))?:(.*\\n\\n.*\\n\\n.*BREAKING.*){0}" 6 | -------------------------------------------------------------------------------- /__tests__/sample-with-solution/NuGet.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /__tests__/sample-with-solution/README.md: -------------------------------------------------------------------------------- 1 | # TestPackage 2 | 3 | ## Introduction 4 | 5 | TestPackage package - generated using the [package generator](https://github.com/Capgemini/powerapps-project-template). 6 | 7 | ## Contributing 8 | 9 | Refer to the contributing [guide](./CONTRIBUTING.md). 10 | -------------------------------------------------------------------------------- /__tests__/sample-with-solution/build.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | Param( 3 | [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)] 4 | [string[]]$BuildArguments 5 | ) 6 | 7 | Write-Output "PowerShell $($PSVersionTable.PSEdition) version $($PSVersionTable.PSVersion)" 8 | 9 | Set-StrictMode -Version 2.0; $ErrorActionPreference = "Stop"; $ConfirmPreference = "None"; trap { Write-Error $_ -ErrorAction Continue; exit 1 } 10 | $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent 11 | 12 | ########################################################################### 13 | # CONFIGURATION 14 | ########################################################################### 15 | 16 | $BuildProjectFile = "$PSScriptRoot\build\_build.csproj" 17 | $TempDirectory = "$PSScriptRoot\\.tmp" 18 | 19 | $DotNetGlobalFile = "$PSScriptRoot\\global.json" 20 | $DotNetInstallUrl = "https://dot.net/v1/dotnet-install.ps1" 21 | $DotNetChannel = "Current" 22 | 23 | $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = 1 24 | $env:DOTNET_CLI_TELEMETRY_OPTOUT = 1 25 | $env:DOTNET_MULTILEVEL_LOOKUP = 0 26 | 27 | ########################################################################### 28 | # EXECUTION 29 | ########################################################################### 30 | 31 | function ExecSafe([scriptblock] $cmd) { 32 | & $cmd 33 | if ($LASTEXITCODE) { exit $LASTEXITCODE } 34 | } 35 | 36 | # If dotnet CLI is installed globally and it matches requested version, use for execution 37 | if ($null -ne (Get-Command "dotnet" -ErrorAction SilentlyContinue) -and ` 38 | $(dotnet --version) -and $LASTEXITCODE -eq 0) { 39 | $env:DOTNET_EXE = (Get-Command "dotnet").Path 40 | } 41 | else { 42 | # Download install script 43 | $DotNetInstallFile = "$TempDirectory\dotnet-install.ps1" 44 | New-Item -ItemType Directory -Path $TempDirectory -Force | Out-Null 45 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 46 | (New-Object System.Net.WebClient).DownloadFile($DotNetInstallUrl, $DotNetInstallFile) 47 | 48 | # If global.json exists, load expected version 49 | if (Test-Path $DotNetGlobalFile) { 50 | $DotNetGlobal = $(Get-Content $DotNetGlobalFile | Out-String | ConvertFrom-Json) 51 | if ($DotNetGlobal.PSObject.Properties["sdk"] -and $DotNetGlobal.sdk.PSObject.Properties["version"]) { 52 | $DotNetVersion = $DotNetGlobal.sdk.version 53 | } 54 | } 55 | 56 | # Install by channel or version 57 | $DotNetDirectory = "$TempDirectory\dotnet-win" 58 | if (!(Test-Path variable:DotNetVersion)) { 59 | ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Channel $DotNetChannel -NoPath } 60 | } else { 61 | ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Version $DotNetVersion -NoPath } 62 | } 63 | $env:DOTNET_EXE = "$DotNetDirectory\dotnet.exe" 64 | } 65 | 66 | Write-Output "Microsoft (R) .NET Core SDK version $(& $env:DOTNET_EXE --version)" 67 | 68 | ExecSafe { & $env:DOTNET_EXE build $BuildProjectFile /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet } 69 | ExecSafe { & $env:DOTNET_EXE run --project $BuildProjectFile --no-build -- $BuildArguments } 70 | -------------------------------------------------------------------------------- /__tests__/sample-with-solution/build/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | dotnet_style_qualification_for_field = false:warning 3 | dotnet_style_qualification_for_property = false:warning 4 | dotnet_style_qualification_for_method = false:warning 5 | dotnet_style_qualification_for_event = false:warning 6 | dotnet_style_require_accessibility_modifiers = never:warning 7 | 8 | csharp_style_expression_bodied_methods = true:silent 9 | csharp_style_expression_bodied_properties = true:warning 10 | csharp_style_expression_bodied_indexers = true:warning 11 | csharp_style_expression_bodied_accessors = true:warning 12 | -------------------------------------------------------------------------------- /__tests__/sample-with-solution/build/Configuration.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using Nuke.Common.Tooling; 3 | 4 | [TypeConverter(typeof(TypeConverter))] 5 | public class Configuration : Enumeration 6 | { 7 | public static Configuration Debug = new Configuration { Value = nameof(Debug) }; 8 | public static Configuration Release = new Configuration { Value = nameof(Release) }; 9 | public static Configuration Test = new Configuration { Value = nameof(Test) }; 10 | 11 | public static implicit operator string(Configuration configuration) 12 | { 13 | return configuration.Value; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /__tests__/sample-with-solution/build/SolutionConfiguration.cs: -------------------------------------------------------------------------------- 1 | /// 2 | /// Configuration for a Dataverse solution. 3 | /// 4 | public class SolutionConfiguration 5 | { 6 | private string masterProfile; 7 | 8 | /// 9 | /// The PAC profile to use for the development environment 10 | /// 11 | public string DevelopmentProfile { get; set; } 12 | 13 | /// 14 | /// The PAC profile to use for the master environment 15 | /// 16 | public string MasterProfile 17 | { 18 | get 19 | { 20 | return string.IsNullOrEmpty(this.masterProfile) ? this.DevelopmentProfile : this.masterProfile; 21 | } 22 | set 23 | { 24 | this.masterProfile = value; 25 | } 26 | } 27 | 28 | /// 29 | /// Dependencies configuration 30 | /// 31 | public SolutionDependencyConfiguration Dependencies { get; set; } 32 | 33 | /// 34 | /// Configuration relevant to a solution's dependencies. 35 | /// 36 | public class SolutionDependencyConfiguration 37 | { 38 | /// 39 | /// Solutions to not attempt to resolve from local solution projects when building (usually out-of-the-box solutions). 40 | /// 41 | public string[] NoResolve { get; set; } 42 | } 43 | } -------------------------------------------------------------------------------- /__tests__/sample-with-solution/build/SolutionType.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using Nuke.Common.Tooling; 3 | 4 | [TypeConverter(typeof(TypeConverter))] 5 | public class SolutionType : Enumeration 6 | { 7 | public static SolutionType Unmanaged = new SolutionType { Value = nameof(Unmanaged) }; 8 | public static SolutionType Managed = new SolutionType { Value = nameof(Managed) }; 9 | 10 | public static implicit operator string(SolutionType solutionType) 11 | { 12 | return solutionType.Value; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /__tests__/sample-with-solution/build/_build.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net7.0 6 | 7 | CS0649;CS0169 8 | .. 9 | .. 10 | true 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /__tests__/sample-with-solution/deploy/PackageTemplate.cs: -------------------------------------------------------------------------------- 1 | namespace TestClient.TestPackage.Deployment 2 | { 3 | using System.ComponentModel.Composition; 4 | using Capgemini.PowerApps.PackageDeployerTemplate; 5 | using Microsoft.Xrm.Tooling.PackageDeployment.CrmPackageExtentionBase; 6 | 7 | /// 8 | /// Import package starter frame. 9 | /// 10 | [Export(typeof(IImportExtensions))] 11 | public class PackageTemplate : PackageTemplateBase 12 | { 13 | /// 14 | public override string GetImportPackageDataFolderName => "PkgFolder"; 15 | 16 | /// 17 | public override string GetImportPackageDescriptionText => "TestPackage"; 18 | 19 | /// 20 | public override string GetLongNameOfImport => "TestPackage"; 21 | 22 | /// 23 | public override string GetNameOfImport(bool plural) => "TestPackage"; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /__tests__/sample-with-solution/deploy/PkgFolder/ImportConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /__tests__/sample-with-solution/deploy/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /__tests__/sample-with-solution/pipelines/azure-pipelines-pull-request.yml: -------------------------------------------------------------------------------- 1 | name: $(GITVERSION_FullSemVer) 2 | pool: 3 | vmImage: 'windows-latest' 4 | trigger: none 5 | stages: 6 | - template: templates/include-build-stage.yml 7 | - template: templates/include-solution-checker-stage.yml 8 | parameters: 9 | serviceConnection: 'CI Environment - TestPackage' -------------------------------------------------------------------------------- /__tests__/sample-with-solution/pipelines/azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | name: $(GITVERSION_FullSemVer) 2 | pool: 3 | vmImage: 'windows-latest' 4 | trigger: 5 | branches: 6 | include: 7 | - master 8 | stages: 9 | - template: templates/include-build-stage.yml 10 | - template: templates/include-solution-checker-stage.yml 11 | parameters: 12 | serviceConnection: 'CI Environment - TestPackage' -------------------------------------------------------------------------------- /__tests__/sample-with-solution/pipelines/scripts/Add-BuildTagForEachUpdatedSolution.ps1: -------------------------------------------------------------------------------- 1 | $resultArray = git show --name-only 2 | [System.Collections.ArrayList]$changedSolutions = @() 3 | 4 | foreach ($_ in $resultArray) { 5 | if ($_.StartsWith("src/solutions")) { 6 | $solutionName = $_.Split("/")[2] 7 | 8 | if (!$changedSolutions.Contains($solutionName)) { 9 | $changedSolutions.Add($solutionName) 10 | Write-Host "##vso[build.addbuildtag]$solutionName" 11 | } 12 | } 13 | } 14 | $output = $changedSolutions -Join ',' 15 | 16 | Write-Host "##vso[task.setvariable variable=solutionList]$output" -------------------------------------------------------------------------------- /__tests__/sample-with-solution/pipelines/templates/include-build-stage.yml: -------------------------------------------------------------------------------- 1 | stages: 2 | - stage: Build 3 | displayName: 'Build' 4 | pool: 5 | vmImage: 'windows-latest' 6 | jobs: 7 | - job: BuildJob 8 | displayName: 'Build' 9 | variables: 10 | - name: 'GitVersion.SemVer' 11 | value: '' 12 | steps: 13 | - task: PowerShell@2 14 | displayName: 'Add build tag for each updated solution' 15 | inputs: 16 | targetType: 'filePath' 17 | errorActionPreference: 'stop' 18 | filePath: 'pipelines/scripts/Add-BuildTagForEachUpdatedSolution.ps1' 19 | - task: gitversion/setup@0 20 | displayName: 'Install GitVersion' 21 | inputs: 22 | versionSpec: '5.x' 23 | - task: gitversion/execute@0 24 | displayName: 'Execute GitVersion' 25 | inputs: 26 | useConfigFile: true 27 | configFilePath: '$(Build.SourcesDirectory)\GitVersion.yml' 28 | - pwsh: Write-Host "##vso[task.setvariable variable=SemVer;isOutput=true]$(GitVersion.SemVer)" 29 | displayName: 'Output SemVer variable' 30 | name: OutputSemVerTask 31 | - task: PowerShell@2 32 | displayName: 'Build package' 33 | inputs: 34 | targetType: 'filePath' 35 | filePath: './build.ps1' 36 | arguments: '--target compile --solution-type managed' 37 | - task: PowerShell@2 38 | displayName: 'Build tests' 39 | inputs: 40 | targetType: 'filePath' 41 | filePath: './build.ps1' 42 | arguments: '--target compile-tests' 43 | - task: VSTest@2 44 | displayName: Run unit tests 45 | inputs: 46 | runInParallel: true 47 | codeCoverageEnabled: true 48 | runSettingsFile: tests/TestClient.TestPackage.UnitTests/unit.runsettings 49 | testAssemblyVer2: | 50 | **\*.UnitTests.dll 51 | !**\*TestAdapter.dll 52 | !**\obj\** 53 | searchFolder: tests 54 | - task: CopyFiles@2 55 | displayName: 'Copy package to artifact staging directory' 56 | inputs: 57 | SourceFolder: 'deploy/bin/Release/net462' 58 | TargetFolder: '$(Build.ArtifactStagingDirectory)/package' 59 | - task: CopyFiles@2 60 | displayName: 'Copy integration tests to artifact staging directory' 61 | inputs: 62 | SourceFolder: 'tests\TestClient.TestPackage.IntegrationTests\bin\Debug\net48' 63 | TargetFolder: '$(Build.ArtifactStagingDirectory)/tests/integration' 64 | - task: CopyFiles@2 65 | displayName: 'Copy UI tests to artifact staging directory' 66 | inputs: 67 | SourceFolder: 'tests\TestClient.TestPackage.UiTests\bin\Debug\net48' 68 | TargetFolder: '$(Build.ArtifactStagingDirectory)/tests/ui' 69 | - task: PublishBuildArtifacts@1 70 | displayName: 'Publish package artifact' 71 | inputs: 72 | pathtoPublish: '$(Build.ArtifactStagingDirectory)/package' 73 | artifactName: 'Package' 74 | - task: PublishBuildArtifacts@1 75 | displayName: 'Publish tests artifact' 76 | inputs: 77 | pathtoPublish: '$(Build.ArtifactStagingDirectory)/tests' 78 | artifactName: 'Tests' 79 | -------------------------------------------------------------------------------- /__tests__/sample-with-solution/pipelines/templates/include-solution-checker-stage.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: serviceConnection 3 | type: string 4 | displayName: 'The service connection' 5 | stages: 6 | - stage: SolutionChecker 7 | displayName: 'Solution checker' 8 | jobs: 9 | - job: SolutionCheckerJob 10 | displayName: Solution checker 11 | steps: 12 | - checkout: none 13 | - download: current 14 | artifact: 'Package' 15 | displayName: 'Download package' 16 | - task: PowerPlatformToolInstaller@0 17 | displayName: 'Install Power Platform Build Tools' 18 | inputs: 19 | DefaultVersion: true 20 | - task: PowerPlatformChecker@0 21 | displayName: 'Run Solution Checker' 22 | inputs: 23 | PowerPlatformSPN: '${{ parameters.serviceConnection }}' 24 | FilesToAnalyze: '$(Pipeline.Workspace)/Package/**/*.zip' 25 | RuleSet: '0ad12346-e108-40b8-a956-9a8f95ea18c9' -------------------------------------------------------------------------------- /__tests__/sample-with-solution/src/common/TestClient.TestPackage.BusinessLogic/Extensions/InArgumentExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace TestClient.TestPackage.BusinessLogic.Extensions 2 | { 3 | using System; 4 | using System.Activities; 5 | 6 | /// 7 | /// Extensions to the class. 8 | /// 9 | public static class InArgumentExtensions 10 | { 11 | /// 12 | /// Throws an exception if the value is null. 13 | /// 14 | /// The argument. 15 | /// The context. 16 | /// The name of the argument. 17 | /// The argument type. 18 | /// The argument value. 19 | public static T GetRequired(this InArgument inArgument, ActivityContext context, string argumentName) 20 | { 21 | if (context == null) 22 | { 23 | throw new ArgumentNullException(nameof(context)); 24 | } 25 | 26 | var value = inArgument.Get(context); 27 | 28 | if ((value is string && string.IsNullOrEmpty(value as string)) || value == null) 29 | { 30 | throw new ArgumentNullException(argumentName); 31 | } 32 | 33 | return value; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /__tests__/sample-with-solution/src/common/TestClient.TestPackage.BusinessLogic/IRepositoryFactory.cs: -------------------------------------------------------------------------------- 1 | namespace TestClient.TestPackage.BusinessLogic 2 | { 3 | using TestClient.TestPackage.Repositories; 4 | using Microsoft.Xrm.Sdk; 5 | using Microsoft.Xrm.Sdk.Client; 6 | 7 | /// 8 | /// Factory for repositories. 9 | /// 10 | public interface IRepositoryFactory 11 | { 12 | /// 13 | /// Gets the organization service. 14 | /// 15 | IOrganizationService OrganizationService { get; } 16 | 17 | /// 18 | /// Get a repository for the given entity. 19 | /// 20 | /// The context. 21 | /// The entity. 22 | /// A repository for the given entity. 23 | ICrmRepository GetRepository() 24 | where TEntity : Entity, new() 25 | where TContext : OrganizationServiceContext; 26 | } 27 | } -------------------------------------------------------------------------------- /__tests__/sample-with-solution/src/common/TestClient.TestPackage.BusinessLogic/Logging/ConsoleLogWriter.cs: -------------------------------------------------------------------------------- 1 | namespace TestClient.TestPackage.BusinessLogic.Logging 2 | { 3 | using System; 4 | 5 | /// 6 | /// Console log writer. 7 | /// 8 | public class ConsoleLogWriter : ILogWriter 9 | { 10 | /// 11 | public void Log(Severity severity, string tag, string message) 12 | { 13 | Console.WriteLine($"{tag}: {severity}: {message}"); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /__tests__/sample-with-solution/src/common/TestClient.TestPackage.BusinessLogic/Logging/ILogWriter.cs: -------------------------------------------------------------------------------- 1 | namespace TestClient.TestPackage.BusinessLogic.Logging 2 | { 3 | /// 4 | /// Interface for a log writer. 5 | /// 6 | public interface ILogWriter 7 | { 8 | /// 9 | /// Write to the log. 10 | /// 11 | /// The severity of the message. 12 | /// A tag to prepend onto the message (generally the name of the calling class). 13 | /// The message to log. 14 | void Log(Severity severity, string tag, string message); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /__tests__/sample-with-solution/src/common/TestClient.TestPackage.BusinessLogic/Logging/Severity.cs: -------------------------------------------------------------------------------- 1 | namespace TestClient.TestPackage.BusinessLogic.Logging 2 | { 3 | /// 4 | /// Log severity. 5 | /// 6 | public enum Severity 7 | { 8 | /// 9 | /// Verbose log entry. 10 | /// 11 | Verbose, 12 | 13 | /// 14 | /// Information log entry. 15 | /// 16 | Info, 17 | 18 | /// 19 | /// Warning log entry. 20 | /// 21 | Warning, 22 | 23 | /// 24 | /// Error log entry. 25 | /// 26 | Error, 27 | } 28 | } -------------------------------------------------------------------------------- /__tests__/sample-with-solution/src/common/TestClient.TestPackage.BusinessLogic/Logging/TracingServiceLogWriter.cs: -------------------------------------------------------------------------------- 1 | namespace TestClient.TestPackage.BusinessLogic.Logging 2 | { 3 | using System; 4 | using System.Globalization; 5 | using Microsoft.Xrm.Sdk; 6 | 7 | /// 8 | /// Logs messages using the tracing service. 9 | /// 10 | public class TracingServiceLogWriter : ILogWriter 11 | { 12 | private readonly ITracingService tracingService; 13 | private readonly bool addTimestamps; 14 | 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | /// The tracing service. 19 | /// Whether or not to timestamp messages. 20 | public TracingServiceLogWriter(ITracingService tracingService, bool timestamp = false) 21 | { 22 | this.tracingService = tracingService ?? throw new ArgumentNullException(nameof(tracingService)); 23 | this.addTimestamps = timestamp; 24 | } 25 | 26 | /// 27 | public void Log(Severity severity, string tag, string message) 28 | { 29 | this.tracingService.Trace(this.FormatMessage(severity, tag, message)); 30 | } 31 | 32 | private string FormatMessage(Severity severity, string tag, string message) 33 | { 34 | return $"{(this.addTimestamps ? $"{DateTime.UtcNow.ToString("o", CultureInfo.CurrentCulture)}: " : string.Empty)}{tag}: {severity}: {message}"; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /__tests__/sample-with-solution/src/common/TestClient.TestPackage.BusinessLogic/Plugin.cs: -------------------------------------------------------------------------------- 1 | namespace TestClient.TestPackage.BusinessLogic 2 | { 3 | using System; 4 | using TestClient.TestPackage.BusinessLogic.Logging; 5 | using Microsoft.Xrm.Sdk; 6 | 7 | /// 8 | /// Dynamics 365 plugin. 9 | /// 10 | public abstract class Plugin : IPlugin 11 | { 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | public Plugin() 16 | : base() 17 | { 18 | } 19 | 20 | /// 21 | /// Initializes a new instance of the class. 22 | /// 23 | /// The unsecure configuration. 24 | /// The secure configuration. 25 | public Plugin(string unsecureConfig, string secureConfig) 26 | : this() 27 | { 28 | this.UnsecureConfig = unsecureConfig; 29 | this.SecureConfig = secureConfig; 30 | } 31 | 32 | /// 33 | /// Gets the plugin step's unsecure configuration. 34 | /// 35 | protected string UnsecureConfig { get; private set; } 36 | 37 | /// 38 | /// Gets the plugin step's secure configuration. 39 | /// 40 | protected string SecureConfig { get; private set; } 41 | 42 | /// 43 | public void Execute(IServiceProvider serviceProvider) 44 | { 45 | var tracingSvc = (ITracingService)serviceProvider.GetService(typeof(ITracingService)); 46 | var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext)); 47 | var serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory)); 48 | var orgSvc = serviceFactory.CreateOrganizationService(Guid.Empty); 49 | var repositoryFactory = new RepositoryFactory(orgSvc); 50 | var logWriter = new TracingServiceLogWriter(tracingSvc, true); 51 | 52 | this.Execute(context, orgSvc, logWriter, repositoryFactory); 53 | } 54 | 55 | /// 56 | /// Execute the plugin. 57 | /// 58 | /// The plugin execution context. 59 | /// The organization service. 60 | /// The log writer. 61 | /// The repository factory. 62 | protected abstract void Execute(IPluginExecutionContext context, IOrganizationService orgSvc, TracingServiceLogWriter logWriter, RepositoryFactory repositoryFactory); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /__tests__/sample-with-solution/src/common/TestClient.TestPackage.BusinessLogic/RepositoryFactory.cs: -------------------------------------------------------------------------------- 1 | namespace TestClient.TestPackage.BusinessLogic 2 | { 3 | using TestClient.TestPackage.Repositories; 4 | using Microsoft.Xrm.Sdk; 5 | using Microsoft.Xrm.Sdk.Client; 6 | 7 | /// 8 | /// Concrete implementation of . 9 | /// 10 | public class RepositoryFactory : IRepositoryFactory 11 | { 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | /// Organization service. 16 | public RepositoryFactory(IOrganizationService orgSvc) 17 | { 18 | this.OrganizationService = orgSvc; 19 | } 20 | 21 | /// 22 | /// Gets the organization service. 23 | /// 24 | public IOrganizationService OrganizationService { get; private set; } 25 | 26 | /// 27 | public ICrmRepository GetRepository() 28 | where TEntity : Entity, new() 29 | where TContext : OrganizationServiceContext 30 | { 31 | return new CrmRepository(this.OrganizationService); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /__tests__/sample-with-solution/src/common/TestClient.TestPackage.BusinessLogic/TestClient.TestPackage.BusinessLogic.projitems: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath) 5 | true 6 | 2b1ef6de-63cc-4b7e-9bf6-5d9b4c0afded 7 | 8 | 9 | TestClient.TestPackage.BusinessLogic 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /__tests__/sample-with-solution/src/common/TestClient.TestPackage.BusinessLogic/TestClient.TestPackage.BusinessLogic.shproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 2b1ef6de-63cc-4b7e-9bf6-5d9b4c0afded 5 | 14.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /__tests__/sample-with-solution/src/common/TestClient.TestPackage.BusinessLogic/WorkflowActivity.cs: -------------------------------------------------------------------------------- 1 | namespace TestClient.TestPackage.BusinessLogic 2 | { 3 | using System.Activities; 4 | using TestClient.TestPackage.BusinessLogic.Logging; 5 | using Microsoft.Xrm.Sdk; 6 | using Microsoft.Xrm.Sdk.Workflow; 7 | 8 | /// 9 | /// Base class for Dynamics 365 workflow activities. 10 | /// 11 | public abstract class WorkflowActivity : CodeActivity 12 | { 13 | /// 14 | /// Execute the custom workflow activity. 15 | /// 16 | /// The context. 17 | /// The workflow context. 18 | /// Organization service. 19 | /// Log writer. 20 | /// Repository factory. 21 | protected abstract void ExecuteWorkflowActivity(CodeActivityContext context, IWorkflowContext workflowContext, IOrganizationService orgSvc, ILogWriter logWriter, IRepositoryFactory repoFactory); 22 | 23 | /// 24 | protected override void Execute(CodeActivityContext context) 25 | { 26 | var tracingSvc = context.GetExtension(); 27 | var workflowContext = context.GetExtension(); 28 | var serviceFactory = context.GetExtension(); 29 | var orgSvc = serviceFactory.CreateOrganizationService(workflowContext.UserId); 30 | var repositoryFactory = new RepositoryFactory(orgSvc); 31 | var logWriter = new TracingServiceLogWriter(tracingSvc, true); 32 | 33 | this.ExecuteWorkflowActivity(context, workflowContext, orgSvc, logWriter, repositoryFactory); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /__tests__/sample-with-solution/src/common/TestClient.TestPackage.Container/TestClient.TestPackage.Container.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net462 4 | TestClient.TestPackage.Container 5 | TestClient.TestPackage.Container.ruleset 6 | 7 | 8 | true 9 | 10 | 11 | TestClient.TestPackage.Container.snk 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | all 21 | 22 | 23 | all 24 | runtime; build; native; contentfiles; analyzers; buildtransitive 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /__tests__/sample-with-solution/src/common/TestClient.TestPackage.Container/TestClient.TestPackage.Container.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Capgemini/powerapps-project-template/b7dbaf1e8945349d4c83f72bf0dc22c2f2d476d1/__tests__/sample-with-solution/src/common/TestClient.TestPackage.Container/TestClient.TestPackage.Container.snk -------------------------------------------------------------------------------- /__tests__/sample-with-solution/src/common/TestClient.TestPackage.Model/TestClient.TestPackage.Model.projitems: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath) 5 | true 6 | 70363b14-5d32-4bd6-aa74-a1ca3373c087 7 | 8 | 9 | TestClient.TestPackage.Model 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /__tests__/sample-with-solution/src/common/TestClient.TestPackage.Model/TestClient.TestPackage.Model.shproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 70363b14-5d32-4bd6-aa74-a1ca3373c087 5 | 14.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /__tests__/sample-with-solution/src/common/TestClient.TestPackage.Model/spkl.json: -------------------------------------------------------------------------------- 1 | { 2 | "earlyboundtypes": [ 3 | { 4 | "entities": "", 5 | "actions": "", 6 | "generateOptionsetEnums": true, 7 | "generateStateEnums": true, 8 | "generateGlobalOptionsets": true, 9 | "filename": "TestPackageContext.cs", 10 | "classNamespace": "TestClient.TestPackage.Model", 11 | "serviceContextName": "TestPackageContext" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /__tests__/sample-with-solution/src/common/TestClient.TestPackage.Repositories/SandboxWebClient.cs: -------------------------------------------------------------------------------- 1 | namespace TestClient.TestPackage.Repositories 2 | { 3 | using System; 4 | using System.Net; 5 | 6 | /// 7 | /// Web client optimised for use in the Dynamics 365 sandbox. 8 | /// . 9 | /// 10 | public class SandboxWebClient : WebClient 11 | { 12 | /// 13 | protected override WebRequest GetWebRequest(Uri address) 14 | { 15 | var req = (HttpWebRequest)base.GetWebRequest(address); 16 | req.KeepAlive = false; 17 | 18 | return req; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /__tests__/sample-with-solution/src/common/TestClient.TestPackage.Repositories/TestClient.TestPackage.Repositories.projitems: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath) 5 | true 6 | 56d657e2-2db7-4489-ad26-ca2e147836c6 7 | 8 | 9 | TestClient.TestPackage.Repositories 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /__tests__/sample-with-solution/src/common/TestClient.TestPackage.Repositories/TestClient.TestPackage.Repositories.shproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 56d657e2-2db7-4489-ad26-ca2e147836c6 5 | 14.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /__tests__/sample-with-solution/src/solutions/test_TestPackage_TestSolution/ExtractMappingFile.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /__tests__/sample-with-solution/src/solutions/test_TestPackage_TestSolution/PackMappingFile.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /__tests__/sample-with-solution/src/solutions/test_TestPackage_TestSolution/solution.json: -------------------------------------------------------------------------------- 1 | { 2 | "DevelopmentProfile": "test", 3 | "environment": "https://dev.com", 4 | "stagingEnvironment": "https://staging.com" 5 | } 6 | -------------------------------------------------------------------------------- /__tests__/sample-with-solution/src/solutions/test_TestPackage_TestSolution/spkl.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [] 3 | } -------------------------------------------------------------------------------- /__tests__/sample-with-solution/src/solutions/test_TestPackage_TestSolution/test_TestPackage_TestSolution.cdsproj: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | $(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\PowerApps 6 | $(MSBuildThisFileDirectory)\PackMappingFile.xml 7 | 8 | 9 | 10 | 11 | 12 | 13 | 63d3663a-0ab3-43cc-b0f3-cbdd6d869e19 14 | v4.6.2 15 | 16 | net462 17 | PackageReference 18 | Extract 19 | 20 | 21 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | PreserveNewest 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /__tests__/sample-with-solution/tests/TestClient.TestPackage.IntegrationTests/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Standard convention for test methods", Scope = "namespace", Target = "TestClient.TestPackage.IntegrationTests")] 6 | -------------------------------------------------------------------------------- /__tests__/sample-with-solution/tests/TestClient.TestPackage.IntegrationTests/TestClient.TestPackage.IntegrationTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net48 4 | TestClient.TestPackage.IntegrationTests 5 | TestClient.TestPackage.IntegrationTests.ruleset 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | all 17 | 18 | 19 | all 20 | runtime; build; native; contentfiles; analyzers; buildtransitive 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | all 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | PreserveNewest 62 | 63 | 64 | -------------------------------------------------------------------------------- /__tests__/sample-with-solution/tests/TestClient.TestPackage.IntegrationTests/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /__tests__/sample-with-solution/tests/TestClient.TestPackage.IntegrationTests/xunit.runner.json: -------------------------------------------------------------------------------- 1 | { 2 | "methodDisplay": "method" 3 | } -------------------------------------------------------------------------------- /__tests__/sample-with-solution/tests/TestClient.TestPackage.UiTests/Bindings/keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Capgemini/powerapps-project-template/b7dbaf1e8945349d4c83f72bf0dc22c2f2d476d1/__tests__/sample-with-solution/tests/TestClient.TestPackage.UiTests/Bindings/keep -------------------------------------------------------------------------------- /__tests__/sample-with-solution/tests/TestClient.TestPackage.UiTests/Data/keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Capgemini/powerapps-project-template/b7dbaf1e8945349d4c83f72bf0dc22c2f2d476d1/__tests__/sample-with-solution/tests/TestClient.TestPackage.UiTests/Data/keep -------------------------------------------------------------------------------- /__tests__/sample-with-solution/tests/TestClient.TestPackage.UiTests/Extensions/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace TestClient.TestPackage.UiTests.Extensions 2 | { 3 | using System.Security; 4 | 5 | /// 6 | /// Extensions to . 7 | /// 8 | public static class StringExtensions 9 | { 10 | /// 11 | /// Converts a string to a . 12 | /// 13 | /// The input string. 14 | /// The secure string. 15 | public static SecureString ToSecureString(this string input) 16 | { 17 | if (string.IsNullOrWhiteSpace(input)) 18 | { 19 | return null; 20 | } 21 | else 22 | { 23 | var result = new SecureString(); 24 | 25 | foreach (char c in input.ToCharArray()) 26 | { 27 | result.AppendChar(c); 28 | } 29 | 30 | return result; 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /__tests__/sample-with-solution/tests/TestClient.TestPackage.UiTests/TestClient.TestPackage.UiTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net48 4 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 5 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 6 | TestClient.TestPackage.UiTests 7 | TestClient.TestPackage.UiTests.ruleset 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | all 18 | 19 | 20 | all 21 | runtime; build; native; contentfiles; analyzers; buildtransitive 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | PreserveNewest 40 | 41 | 42 | PreserveNewest 43 | 44 | 45 | -------------------------------------------------------------------------------- /__tests__/sample-with-solution/tests/TestClient.TestPackage.UiTests/power-apps-bindings.yml: -------------------------------------------------------------------------------- 1 | url: 2 | browserOptions: 3 | browserType: Chrome 4 | headless: true 5 | width: 1920 6 | height: 1080 7 | startMaximized: false 8 | users: 9 | - username: 10 | password: 11 | alias: -------------------------------------------------------------------------------- /__tests__/sample-with-solution/tests/TestClient.TestPackage.UiTests/specflow.json: -------------------------------------------------------------------------------- 1 | { 2 | "bindingCulture": { 3 | "language": "en-gb" 4 | }, 5 | "language": { 6 | "feature": "en-gb" 7 | }, 8 | "stepAssemblies": [ 9 | { "assembly": "Capgemini.PowerApps.SpecFlowBindings" } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /__tests__/sample-with-solution/tests/TestClient.TestPackage.UnitTests/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Standard convention for test methods", Scope = "namespace", Target = "TestClient.TestPackage.UnitTests")] 6 | -------------------------------------------------------------------------------- /__tests__/sample-with-solution/tests/TestClient.TestPackage.UnitTests/WorkflowActivityTests.cs: -------------------------------------------------------------------------------- 1 | namespace TestClient.TestPackage.UnitTests 2 | { 3 | using System; 4 | using System.Activities; 5 | using Microsoft.Xrm.Sdk; 6 | using Microsoft.Xrm.Sdk.Workflow; 7 | using Moq; 8 | 9 | /// 10 | /// Base class for workflow activity tests. 11 | /// 12 | /// The type of workflow activity. 13 | public abstract class WorkflowActivityTests 14 | where TCodeActivity : CodeActivity, new() 15 | { 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | public WorkflowActivityTests() 20 | { 21 | this.WorkflowActivity = new TCodeActivity(); 22 | this.WorkflowInvoker = new WorkflowInvoker(this.WorkflowActivity); 23 | 24 | this.TracingSvcMock = new Mock(); 25 | this.OrgSvcFactoryMock = new Mock(); 26 | this.OrgSvcMock = new Mock(); 27 | this.WorkflowContextMock = new Mock(); 28 | 29 | this.OrgSvcFactoryMock.SetReturnsDefault(this.OrgSvcMock.Object); 30 | this.WorkflowContextMock.Setup(workflowContext => workflowContext.UserId).Returns(Guid.NewGuid()); 31 | this.WorkflowContextMock.Setup(workflowContext => workflowContext.SharedVariables).Returns(new ParameterCollection()); 32 | 33 | this.WorkflowInvoker.Extensions.Add(this.WorkflowContextMock.Object); 34 | this.WorkflowInvoker.Extensions.Add(this.TracingSvcMock.Object); 35 | this.WorkflowInvoker.Extensions.Add(this.OrgSvcFactoryMock.Object); 36 | this.WorkflowInvoker.Extensions.Add(this.OrgSvcMock.Object); 37 | } 38 | 39 | /// 40 | /// Gets the workflow invoker. 41 | /// 42 | protected WorkflowInvoker WorkflowInvoker { get; } 43 | 44 | /// 45 | /// Gets the workflow activity under test. 46 | /// 47 | protected TCodeActivity WorkflowActivity { get; } 48 | 49 | /// 50 | /// Gets mock workflow context. 51 | /// 52 | protected Mock WorkflowContextMock { get; } 53 | 54 | /// 55 | /// Gets the mocked tracing service. 56 | /// 57 | protected Mock TracingSvcMock { get; } 58 | 59 | /// 60 | /// Gets the mocked organization service factory. 61 | /// 62 | protected Mock OrgSvcFactoryMock { get; } 63 | 64 | /// 65 | /// Gets the mocked organization service. 66 | /// 67 | protected Mock OrgSvcMock { get; } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /__tests__/sample-with-solution/tests/TestClient.TestPackage.UnitTests/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /__tests__/sample-with-solution/tests/TestClient.TestPackage.UnitTests/unit.runsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | .*UnitTests.dll 12 | .*testadapter.dll 13 | 14 | 15 | True 16 | True 17 | True 18 | False 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /__tests__/sample-with-solution/tests/TestClient.TestPackage.UnitTests/xunit.runner.json: -------------------------------------------------------------------------------- 1 | { 2 | "methodDisplay": "method" 3 | } -------------------------------------------------------------------------------- /pipelines/azure-pipelines-pull-request.yml: -------------------------------------------------------------------------------- 1 | name: $(GITVERSION_FullSemVer) 2 | trigger: none 3 | pr: 4 | - master 5 | pool: 6 | vmImage: 'windows-latest' 7 | stages: 8 | - stage: BuildAndTest 9 | displayName: Build and Test 10 | jobs: 11 | - template: templates/build-and-test-job.yml -------------------------------------------------------------------------------- /pipelines/azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | name: $(GITVERSION_FullSemVer) 2 | 3 | trigger: 4 | batch: true 5 | branches: 6 | include: 7 | - master 8 | 9 | pr: none 10 | 11 | pool: 12 | vmImage: "windows-latest" 13 | 14 | variables: 15 | - name: GitVersion.SemVer 16 | value: "" 17 | stages: 18 | - stage: BuildAndTest 19 | displayName: Build and Test 20 | jobs: 21 | - template: templates/build-and-test-job.yml 22 | - stage: Publish 23 | displayName: Publish 24 | jobs: 25 | - job: PublishJob 26 | displayName: Publish 27 | variables: 28 | SemVer: $[ stageDependencies.BuildAndTest.BuildAndTestJob.outputs['OutputSemVerTask.SemVer'] ] 29 | steps: 30 | - checkout: none 31 | - download: current 32 | displayName: Download NPM package artifact 33 | artifact: powerapps-project-generator 34 | 35 | - task: Npm@1 36 | inputs: 37 | command: "custom" 38 | customCommand: "publish $(Pipeline.Workspace)/powerapps-project-generator/capgeminiuk-generator-powerapps-project-$(SemVer).tgz --access public" 39 | customEndpoint: "NPM - Capgemini UK (tdashworth)" 40 | 41 | - task: GitHubRelease@1 42 | displayName: Create GitHub releaes 43 | condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master')) 44 | inputs: 45 | gitHubConnection: 'github.com_tdashworth' 46 | repositoryName: '$(Build.Repository.Name)' 47 | action: 'create' 48 | target: '$(Build.SourceVersion)' 49 | tagSource: 'userSpecifiedTag' 50 | tag: 'v$(SemVer)' 51 | releaseNotesSource: 'inline' 52 | assets: '$(Pipeline.Workspace)/powerapps-project-generator/*' 53 | changeLogCompareToRelease: 'lastNonDraftRelease' 54 | changeLogType: 'commitBased' 55 | -------------------------------------------------------------------------------- /pipelines/templates/build-and-test-job.yml: -------------------------------------------------------------------------------- 1 | jobs: 2 | - job: BuildAndTestJob 3 | variables: 4 | - name: GitVersion.SemVer 5 | value: "" 6 | displayName: Build and Test 7 | 8 | steps: 9 | - task: gitversion/setup@0 10 | displayName: Install GitVersion 11 | inputs: 12 | versionSpec: "5.x" 13 | 14 | - task: gitversion/execute@0 15 | displayName: Execute GitVersion 16 | inputs: 17 | useConfigFile: true 18 | configFilePath: '$(Build.SourcesDirectory)\GitVersion.yml' 19 | updateAssemblyInfo: true 20 | 21 | - pwsh: Write-Host '##vso[task.setvariable variable=SemVer;isOutput=true]$(GitVersion.SemVer)' 22 | name: OutputSemVerTask 23 | displayName: Output SemVer 24 | 25 | - task: Npm@1 26 | displayName: Increment Package Version 27 | inputs: 28 | command: custom 29 | customCommand: version $(GitVersion.SemVer) --git-tag-version false 30 | 31 | - task: Npm@1 32 | displayName: Install NPM Dependencies 33 | inputs: 34 | command: custom 35 | customCommand: ci 36 | verbose: true 37 | 38 | - task: Npm@1 39 | displayName: Lint Package 40 | inputs: 41 | command: custom 42 | customCommand: run lint 43 | 44 | - task: Npm@1 45 | displayName: Build Package 46 | inputs: 47 | command: custom 48 | customCommand: run build 49 | 50 | - task: Npm@1 51 | displayName: Test Package 52 | inputs: 53 | command: custom 54 | customCommand: run test 55 | 56 | # Version 20 has been deprecated and never completes while version 21 requires activation within the org. 57 | 58 | # - task: WhiteSource@21 59 | # inputs: 60 | # cwd: '$(System.DefaultWorkingDirectory)' 61 | 62 | - task: Npm@1 63 | displayName: Pack Package 64 | inputs: 65 | command: custom 66 | customCommand: pack 67 | 68 | - task: CopyFiles@2 69 | displayName: Copy package 70 | inputs: 71 | Contents: "capgeminiuk-generator-powerapps-project-*.tgz" 72 | TargetFolder: $(Build.ArtifactStagingDirectory)/out 73 | 74 | - publish: $(Build.ArtifactStagingDirectory)/out 75 | displayName: Publish NPM artifact 76 | artifact: powerapps-project-generator 77 | -------------------------------------------------------------------------------- /src/common/ISolution.ts: -------------------------------------------------------------------------------- 1 | export interface ISolution { 2 | prefix: string; 3 | package: string; 4 | solution: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/common/MappingFileTransformer.ts: -------------------------------------------------------------------------------- 1 | import { render } from 'ejs'; 2 | import { Builder, parseString } from 'xml2js'; 3 | import Generator from 'yeoman-generator'; 4 | 5 | export default class MappingFileTransformer { 6 | private readonly fs: Generator.MemFsEditor; 7 | 8 | constructor(fs: Generator.MemFsEditor) { 9 | this.fs = fs; 10 | } 11 | 12 | public async transform( 13 | templateMappingFilePath: string, 14 | data: any, targetMappingFilePath: string, 15 | ): Promise { 16 | const templateMappingFileParsed = this.getRenderedTemplate(templateMappingFilePath, data); 17 | 18 | parseString( 19 | templateMappingFileParsed, 20 | { trim: true, includeWhiteChars: false, renderOpts: { pretty: true } }, 21 | (tErr, tRes) => { 22 | if (tErr) { 23 | throw tErr; 24 | } 25 | 26 | parseString( 27 | this.fs.read(targetMappingFilePath), 28 | { trim: true, includeWhiteChars: false, renderOpts: { pretty: true } }, 29 | (err, res) => { 30 | if (err) { 31 | throw err; 32 | } 33 | 34 | const merge = MappingFileTransformer.mergeMappings(tRes, res); 35 | 36 | this.fs.write( 37 | targetMappingFilePath, 38 | new Builder({ 39 | explicitArray: true, 40 | includeWhiteChars: false, 41 | renderOpts: { pretty: true }, 42 | trim: true, 43 | }).buildObject(merge), 44 | ); 45 | }, 46 | ); 47 | }, 48 | ); 49 | } 50 | 51 | private static mergeMappings(source: any, target: any) { 52 | const result = { ...target }; 53 | 54 | if (typeof target.Mapping !== 'object') { 55 | result.Mapping = {}; 56 | } 57 | 58 | Object.keys(source.Mapping).forEach((mappingType) => { 59 | if (!Array.isArray(target.Mapping[mappingType])) { 60 | result.Mapping[mappingType] = []; 61 | } 62 | result.Mapping[mappingType].push(...source.Mapping[mappingType]); 63 | }); 64 | 65 | return result; 66 | } 67 | 68 | private getRenderedTemplate(templatePath: string, data: any) { 69 | return render(this.fs.read(templatePath), data); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/common/PackageReader.ts: -------------------------------------------------------------------------------- 1 | import { lstatSync, readdirSync } from 'fs'; 2 | import { GlobSync } from 'glob'; 3 | import { join } from 'path'; 4 | 5 | export default class PackageReader { 6 | private readonly destinationPath: string; 7 | 8 | constructor(destinationPath: string) { 9 | this.destinationPath = destinationPath; 10 | } 11 | 12 | public static getClient(): string { 13 | const solutionFile = new GlobSync('*.sln').found[0]; 14 | return solutionFile.split('.')[0]; 15 | } 16 | 17 | public getSolutions(): string[] { 18 | const solutionsPath = join(this.destinationPath, 'src', 'solutions'); 19 | 20 | return readdirSync(solutionsPath) 21 | .filter((path) => PackageReader.isDirectoryPath(join(solutionsPath, path))); 22 | } 23 | 24 | private static isDirectoryPath(path: string): boolean { 25 | return lstatSync(path).isDirectory(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/common/utilities.ts: -------------------------------------------------------------------------------- 1 | import childProcess from 'child_process'; 2 | 3 | export function validateUrl(input: string): boolean | string { 4 | return /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_+.~#?&//=]*)/.test( 5 | input, 6 | ) 7 | ? true 8 | : 'You must provide a valid URL.'; 9 | } 10 | 11 | export function validateEmail(input: string): boolean | string { 12 | // tslint:disable-next-line:max-line-length 13 | // eslint-disable-next-line no-useless-escape 14 | return /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test( 15 | input, 16 | ) 17 | ? true 18 | : 'You must provide a valid email.'; 19 | } 20 | 21 | export function validateNamespace(input: string): boolean | string { 22 | return /^[ a-zA-Z]+$/.test(input) 23 | ? true 24 | : 'Answer must not contain spaces, numeric characters or special characters.'; 25 | } 26 | 27 | export function validateGuid(input: string): boolean | string { 28 | return /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/i.test(input) 29 | ? true 30 | : 'Your answer doesn\'t match the shape of a GUID.'; 31 | } 32 | 33 | export function validatePacAuthProfile(input: string): boolean | string { 34 | const subprocess = childProcess.spawnSync('pac auth list', [], { stdio: 'pipe', shell: true }); 35 | 36 | if (subprocess.error) { 37 | return 'Failed to validate. Do you have PAC installed?'; 38 | } 39 | 40 | const output = subprocess.stdout.toString(); 41 | 42 | return output.match(new RegExp(`\\s(${input})\\s`, 'g')) 43 | ? true 44 | : 'PAC Auth profile not found.'; 45 | } 46 | 47 | export function sleep(milliseconds: number): Promise { 48 | return new Promise((resolve) => { 49 | setTimeout(resolve, milliseconds); 50 | }); 51 | } 52 | -------------------------------------------------------------------------------- /src/generators/app/index.ts: -------------------------------------------------------------------------------- 1 | import inquirer from 'inquirer'; 2 | import Generator from 'yeoman-generator'; 3 | import { validateNamespace } from '../../common/utilities'; 4 | 5 | class Main extends Generator { 6 | private answers!: inquirer.Answers; 7 | 8 | public async prompting(): Promise { 9 | this.answers = await this.prompt([ 10 | { 11 | message: 'Name of the client? (this will be used for naming various artifacts)', 12 | name: 'client', 13 | store: true, 14 | validate: validateNamespace, 15 | }, 16 | { 17 | message: 'Name of the package? (this will be used for naming various artifacts)', 18 | name: 'package', 19 | store: true, 20 | }, 21 | ]); 22 | this.composeWith(require.resolve('../solution'), { ...this.options, client: this.answers.client, package: this.answers.package }); 23 | } 24 | 25 | public writing(): void { 26 | this.log('Writing package from template...'); 27 | 28 | this.fs.copyTpl( 29 | this.templatePath('source'), 30 | this.destinationPath(), 31 | this.answers, 32 | {}, 33 | { 34 | globOptions: { dot: true }, 35 | processDestinationPath: 36 | (destinationPath: string) => destinationPath.replace(/Client.Package/g, `${this.answers.client}.${this.answers.package}`), 37 | }, 38 | ); 39 | } 40 | } 41 | 42 | module.exports = Main; 43 | -------------------------------------------------------------------------------- /src/generators/app/templates/source/.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /src/generators/app/templates/source/.npmignore: -------------------------------------------------------------------------------- 1 | # This can be deleted once generated. If this doesn't exist, NPM will convert the .gitignore file into this file resulting in no .gitignore file for a generated project. -------------------------------------------------------------------------------- /src/generators/app/templates/source/.nuke: -------------------------------------------------------------------------------- 1 | <%= client %>.<%= package %>.sln -------------------------------------------------------------------------------- /src/generators/app/templates/source/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. 3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 4 | 5 | // List of extensions which should be recommended for users of this workspace. 6 | "recommendations": [ 7 | "DotJoshJohnson.xml", 8 | "cake-build.cake-vscode", 9 | "esbenp.prettier-vscode", 10 | "ms-dotnettools.csharp", 11 | "eamodio.gitlens", 12 | "ms-vscode.vscode-typescript-tslint-plugin" 13 | ], 14 | // List of extensions recommended by VS Code that should not be recommended for users of this workspace. 15 | "unwantedRecommendations": [ 16 | 17 | ] 18 | } -------------------------------------------------------------------------------- /src/generators/app/templates/source/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/build/bin/Debug/_build.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}/build", 16 | // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console 17 | "console": "internalConsole", 18 | "stopAtEntry": false 19 | }, 20 | { 21 | "name": ".NET Core Attach", 22 | "type": "coreclr", 23 | "request": "attach", 24 | "processId": "${command:pickProcess}" 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /src/generators/app/templates/source/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "**/.git": true, 4 | "**/.svn": true, 5 | "**/.hg": true, 6 | "**/CVS": true, 7 | "**/.DS_Store": true, 8 | "**/npm-debug.log": true, 9 | "**/obj/": true, 10 | "**/.vs/": true 11 | }, 12 | "cake.taskRunner": { 13 | "autoDetect": false 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/generators/app/templates/source/GitVersion.yml: -------------------------------------------------------------------------------- 1 | mode: mainline 2 | 3 | major-version-bump-message: "(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\\([\\w\\s]*\\))?(!:|:.*\\n\\n.*\\n\\n.*BREAKING.*).*" 4 | minor-version-bump-message: "(feat)(\\([\\w\\s]*\\))?:" 5 | patch-version-bump-message: "(build|chore|ci|docs|fix|perf|refactor|revert|style|test)(\\([\\w\\s]*\\))?:(.*\\n\\n.*\\n\\n.*BREAKING.*){0}" 6 | -------------------------------------------------------------------------------- /src/generators/app/templates/source/NuGet.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/generators/app/templates/source/README.md: -------------------------------------------------------------------------------- 1 | # <%= package %> 2 | 3 | ## Introduction 4 | 5 | <%= package %> package - generated using the [package generator](https://github.com/Capgemini/powerapps-project-template). 6 | 7 | ## Contributing 8 | 9 | Refer to the contributing [guide](./CONTRIBUTING.md). 10 | -------------------------------------------------------------------------------- /src/generators/app/templates/source/build.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | Param( 3 | [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)] 4 | [string[]]$BuildArguments 5 | ) 6 | 7 | Write-Output "PowerShell $($PSVersionTable.PSEdition) version $($PSVersionTable.PSVersion)" 8 | 9 | Set-StrictMode -Version 2.0; $ErrorActionPreference = "Stop"; $ConfirmPreference = "None"; trap { Write-Error $_ -ErrorAction Continue; exit 1 } 10 | $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent 11 | 12 | ########################################################################### 13 | # CONFIGURATION 14 | ########################################################################### 15 | 16 | $BuildProjectFile = "$PSScriptRoot\build\_build.csproj" 17 | $TempDirectory = "$PSScriptRoot\\.tmp" 18 | 19 | $DotNetGlobalFile = "$PSScriptRoot\\global.json" 20 | $DotNetInstallUrl = "https://dot.net/v1/dotnet-install.ps1" 21 | $DotNetChannel = "Current" 22 | 23 | $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = 1 24 | $env:DOTNET_CLI_TELEMETRY_OPTOUT = 1 25 | $env:DOTNET_MULTILEVEL_LOOKUP = 0 26 | 27 | ########################################################################### 28 | # EXECUTION 29 | ########################################################################### 30 | 31 | function ExecSafe([scriptblock] $cmd) { 32 | & $cmd 33 | if ($LASTEXITCODE) { exit $LASTEXITCODE } 34 | } 35 | 36 | # If dotnet CLI is installed globally and it matches requested version, use for execution 37 | if ($null -ne (Get-Command "dotnet" -ErrorAction SilentlyContinue) -and ` 38 | $(dotnet --version) -and $LASTEXITCODE -eq 0) { 39 | $env:DOTNET_EXE = (Get-Command "dotnet").Path 40 | } 41 | else { 42 | # Download install script 43 | $DotNetInstallFile = "$TempDirectory\dotnet-install.ps1" 44 | New-Item -ItemType Directory -Path $TempDirectory -Force | Out-Null 45 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 46 | (New-Object System.Net.WebClient).DownloadFile($DotNetInstallUrl, $DotNetInstallFile) 47 | 48 | # If global.json exists, load expected version 49 | if (Test-Path $DotNetGlobalFile) { 50 | $DotNetGlobal = $(Get-Content $DotNetGlobalFile | Out-String | ConvertFrom-Json) 51 | if ($DotNetGlobal.PSObject.Properties["sdk"] -and $DotNetGlobal.sdk.PSObject.Properties["version"]) { 52 | $DotNetVersion = $DotNetGlobal.sdk.version 53 | } 54 | } 55 | 56 | # Install by channel or version 57 | $DotNetDirectory = "$TempDirectory\dotnet-win" 58 | if (!(Test-Path variable:DotNetVersion)) { 59 | ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Channel $DotNetChannel -NoPath } 60 | } else { 61 | ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Version $DotNetVersion -NoPath } 62 | } 63 | $env:DOTNET_EXE = "$DotNetDirectory\dotnet.exe" 64 | } 65 | 66 | Write-Output "Microsoft (R) .NET Core SDK version $(& $env:DOTNET_EXE --version)" 67 | 68 | ExecSafe { & $env:DOTNET_EXE build $BuildProjectFile /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet } 69 | ExecSafe { & $env:DOTNET_EXE run --project $BuildProjectFile --no-build -- $BuildArguments } 70 | -------------------------------------------------------------------------------- /src/generators/app/templates/source/build/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | dotnet_style_qualification_for_field = false:warning 3 | dotnet_style_qualification_for_property = false:warning 4 | dotnet_style_qualification_for_method = false:warning 5 | dotnet_style_qualification_for_event = false:warning 6 | dotnet_style_require_accessibility_modifiers = never:warning 7 | 8 | csharp_style_expression_bodied_methods = true:silent 9 | csharp_style_expression_bodied_properties = true:warning 10 | csharp_style_expression_bodied_indexers = true:warning 11 | csharp_style_expression_bodied_accessors = true:warning 12 | -------------------------------------------------------------------------------- /src/generators/app/templates/source/build/Configuration.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using Nuke.Common.Tooling; 3 | 4 | [TypeConverter(typeof(TypeConverter))] 5 | public class Configuration : Enumeration 6 | { 7 | public static Configuration Debug = new Configuration { Value = nameof(Debug) }; 8 | public static Configuration Release = new Configuration { Value = nameof(Release) }; 9 | public static Configuration Test = new Configuration { Value = nameof(Test) }; 10 | 11 | public static implicit operator string(Configuration configuration) 12 | { 13 | return configuration.Value; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/generators/app/templates/source/build/SolutionConfiguration.cs: -------------------------------------------------------------------------------- 1 | /// 2 | /// Configuration for a Dataverse solution. 3 | /// 4 | public class SolutionConfiguration 5 | { 6 | private string masterProfile; 7 | 8 | /// 9 | /// The PAC profile to use for the development environment 10 | /// 11 | public string DevelopmentProfile { get; set; } 12 | 13 | /// 14 | /// The PAC profile to use for the master environment 15 | /// 16 | public string MasterProfile 17 | { 18 | get 19 | { 20 | return string.IsNullOrEmpty(this.masterProfile) ? this.DevelopmentProfile : this.masterProfile; 21 | } 22 | set 23 | { 24 | this.masterProfile = value; 25 | } 26 | } 27 | 28 | /// 29 | /// Dependencies configuration 30 | /// 31 | public SolutionDependencyConfiguration Dependencies { get; set; } 32 | 33 | /// 34 | /// Configuration relevant to a solution's dependencies. 35 | /// 36 | public class SolutionDependencyConfiguration 37 | { 38 | /// 39 | /// Solutions to not attempt to resolve from local solution projects when building (usually out-of-the-box solutions). 40 | /// 41 | public string[] NoResolve { get; set; } 42 | } 43 | } -------------------------------------------------------------------------------- /src/generators/app/templates/source/build/SolutionType.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using Nuke.Common.Tooling; 3 | 4 | [TypeConverter(typeof(TypeConverter))] 5 | public class SolutionType : Enumeration 6 | { 7 | public static SolutionType Unmanaged = new SolutionType { Value = nameof(Unmanaged) }; 8 | public static SolutionType Managed = new SolutionType { Value = nameof(Managed) }; 9 | 10 | public static implicit operator string(SolutionType solutionType) 11 | { 12 | return solutionType.Value; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/generators/app/templates/source/build/_build.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net7.0 6 | 7 | CS0649;CS0169 8 | .. 9 | .. 10 | true 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/generators/app/templates/source/deploy/PackageTemplate.cs: -------------------------------------------------------------------------------- 1 | namespace <%= client %>.<%= package %>.Deployment 2 | { 3 | using System.ComponentModel.Composition; 4 | using Capgemini.PowerApps.PackageDeployerTemplate; 5 | using Microsoft.Xrm.Tooling.PackageDeployment.CrmPackageExtentionBase; 6 | 7 | /// 8 | /// Import package starter frame. 9 | /// 10 | [Export(typeof(IImportExtensions))] 11 | public class PackageTemplate : PackageTemplateBase 12 | { 13 | /// 14 | public override string GetImportPackageDataFolderName => "PkgFolder"; 15 | 16 | /// 17 | public override string GetImportPackageDescriptionText => "<%= package %>"; 18 | 19 | /// 20 | public override string GetLongNameOfImport => "<%= package %>"; 21 | 22 | /// 23 | public override string GetNameOfImport(bool plural) => "<%= package %>"; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/generators/app/templates/source/deploy/PkgFolder/ImportConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/generators/app/templates/source/deploy/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/generators/app/templates/source/pipelines/azure-pipelines-pull-request.yml: -------------------------------------------------------------------------------- 1 | name: $(GITVERSION_FullSemVer) 2 | pool: 3 | vmImage: 'windows-latest' 4 | trigger: none 5 | stages: 6 | - template: templates/include-build-stage.yml 7 | - template: templates/include-solution-checker-stage.yml 8 | parameters: 9 | serviceConnection: 'CI Environment - <%= package %>' -------------------------------------------------------------------------------- /src/generators/app/templates/source/pipelines/azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | name: $(GITVERSION_FullSemVer) 2 | pool: 3 | vmImage: 'windows-latest' 4 | trigger: 5 | branches: 6 | include: 7 | - master 8 | stages: 9 | - template: templates/include-build-stage.yml 10 | - template: templates/include-solution-checker-stage.yml 11 | parameters: 12 | serviceConnection: 'CI Environment - <%= package %>' -------------------------------------------------------------------------------- /src/generators/app/templates/source/pipelines/scripts/Add-BuildTagForEachUpdatedSolution.ps1: -------------------------------------------------------------------------------- 1 | $resultArray = git show --name-only 2 | [System.Collections.ArrayList]$changedSolutions = @() 3 | 4 | foreach ($_ in $resultArray) { 5 | if ($_.StartsWith("src/solutions")) { 6 | $solutionName = $_.Split("/")[2] 7 | 8 | if (!$changedSolutions.Contains($solutionName)) { 9 | $changedSolutions.Add($solutionName) 10 | Write-Host "##vso[build.addbuildtag]$solutionName" 11 | } 12 | } 13 | } 14 | $output = $changedSolutions -Join ',' 15 | 16 | Write-Host "##vso[task.setvariable variable=solutionList]$output" -------------------------------------------------------------------------------- /src/generators/app/templates/source/pipelines/templates/include-build-stage.yml: -------------------------------------------------------------------------------- 1 | stages: 2 | - stage: Build 3 | displayName: 'Build' 4 | pool: 5 | vmImage: 'windows-latest' 6 | jobs: 7 | - job: BuildJob 8 | displayName: 'Build' 9 | variables: 10 | - name: 'GitVersion.SemVer' 11 | value: '' 12 | steps: 13 | - task: PowerShell@2 14 | displayName: 'Add build tag for each updated solution' 15 | inputs: 16 | targetType: 'filePath' 17 | errorActionPreference: 'stop' 18 | filePath: 'pipelines/scripts/Add-BuildTagForEachUpdatedSolution.ps1' 19 | - task: gitversion/setup@0 20 | displayName: 'Install GitVersion' 21 | inputs: 22 | versionSpec: '5.x' 23 | - task: gitversion/execute@0 24 | displayName: 'Execute GitVersion' 25 | inputs: 26 | useConfigFile: true 27 | configFilePath: '$(Build.SourcesDirectory)\GitVersion.yml' 28 | - pwsh: Write-Host "##vso[task.setvariable variable=SemVer;isOutput=true]$(GitVersion.SemVer)" 29 | displayName: 'Output SemVer variable' 30 | name: OutputSemVerTask 31 | - task: PowerShell@2 32 | displayName: 'Build package' 33 | inputs: 34 | targetType: 'filePath' 35 | filePath: './build.ps1' 36 | arguments: '--target compile --solution-type managed' 37 | - task: PowerShell@2 38 | displayName: 'Build tests' 39 | inputs: 40 | targetType: 'filePath' 41 | filePath: './build.ps1' 42 | arguments: '--target compile-tests' 43 | - task: VSTest@2 44 | displayName: Run unit tests 45 | inputs: 46 | runInParallel: true 47 | codeCoverageEnabled: true 48 | runSettingsFile: tests/<%= client %>.<%= package %>.UnitTests/unit.runsettings 49 | testAssemblyVer2: | 50 | **\*.UnitTests.dll 51 | !**\*TestAdapter.dll 52 | !**\obj\** 53 | searchFolder: tests 54 | - task: CopyFiles@2 55 | displayName: 'Copy package to artifact staging directory' 56 | inputs: 57 | SourceFolder: 'deploy/bin/Release/net462' 58 | TargetFolder: '$(Build.ArtifactStagingDirectory)/package' 59 | - task: CopyFiles@2 60 | displayName: 'Copy integration tests to artifact staging directory' 61 | inputs: 62 | SourceFolder: 'tests\<%= client %>.<%= package %>.IntegrationTests\bin\Debug\net48' 63 | TargetFolder: '$(Build.ArtifactStagingDirectory)/tests/integration' 64 | - task: CopyFiles@2 65 | displayName: 'Copy UI tests to artifact staging directory' 66 | inputs: 67 | SourceFolder: 'tests\<%= client %>.<%= package %>.UiTests\bin\Debug\net48' 68 | TargetFolder: '$(Build.ArtifactStagingDirectory)/tests/ui' 69 | - task: PublishBuildArtifacts@1 70 | displayName: 'Publish package artifact' 71 | inputs: 72 | pathtoPublish: '$(Build.ArtifactStagingDirectory)/package' 73 | artifactName: 'Package' 74 | - task: PublishBuildArtifacts@1 75 | displayName: 'Publish tests artifact' 76 | inputs: 77 | pathtoPublish: '$(Build.ArtifactStagingDirectory)/tests' 78 | artifactName: 'Tests' 79 | -------------------------------------------------------------------------------- /src/generators/app/templates/source/pipelines/templates/include-solution-checker-stage.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: serviceConnection 3 | type: string 4 | displayName: 'The service connection' 5 | stages: 6 | - stage: SolutionChecker 7 | displayName: 'Solution checker' 8 | jobs: 9 | - job: SolutionCheckerJob 10 | displayName: Solution checker 11 | steps: 12 | - checkout: none 13 | - download: current 14 | artifact: 'Package' 15 | displayName: 'Download package' 16 | - task: PowerPlatformToolInstaller@0 17 | displayName: 'Install Power Platform Build Tools' 18 | inputs: 19 | DefaultVersion: true 20 | - task: PowerPlatformChecker@0 21 | displayName: 'Run Solution Checker' 22 | inputs: 23 | PowerPlatformSPN: '${{ parameters.serviceConnection }}' 24 | FilesToAnalyze: '$(Pipeline.Workspace)/Package/**/*.zip' 25 | RuleSet: '0ad12346-e108-40b8-a956-9a8f95ea18c9' -------------------------------------------------------------------------------- /src/generators/app/templates/source/src/common/Client.Package.BusinessLogic/Client.Package.BusinessLogic.projitems: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath) 5 | true 6 | 2b1ef6de-63cc-4b7e-9bf6-5d9b4c0afded 7 | 8 | 9 | <%= client %>.<%= package %>.BusinessLogic 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/generators/app/templates/source/src/common/Client.Package.BusinessLogic/Client.Package.BusinessLogic.shproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 2b1ef6de-63cc-4b7e-9bf6-5d9b4c0afded 5 | 14.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/generators/app/templates/source/src/common/Client.Package.BusinessLogic/Extensions/InArgumentExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace <%= client %>.<%= package %>.BusinessLogic.Extensions 2 | { 3 | using System; 4 | using System.Activities; 5 | 6 | /// 7 | /// Extensions to the class. 8 | /// 9 | public static class InArgumentExtensions 10 | { 11 | /// 12 | /// Throws an exception if the value is null. 13 | /// 14 | /// The argument. 15 | /// The context. 16 | /// The name of the argument. 17 | /// The argument type. 18 | /// The argument value. 19 | public static T GetRequired(this InArgument inArgument, ActivityContext context, string argumentName) 20 | { 21 | if (context == null) 22 | { 23 | throw new ArgumentNullException(nameof(context)); 24 | } 25 | 26 | var value = inArgument.Get(context); 27 | 28 | if ((value is string && string.IsNullOrEmpty(value as string)) || value == null) 29 | { 30 | throw new ArgumentNullException(argumentName); 31 | } 32 | 33 | return value; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/generators/app/templates/source/src/common/Client.Package.BusinessLogic/IRepositoryFactory.cs: -------------------------------------------------------------------------------- 1 | namespace <%= client %>.<%= package %>.BusinessLogic 2 | { 3 | using <%= client %>.<%= package %>.Repositories; 4 | using Microsoft.Xrm.Sdk; 5 | using Microsoft.Xrm.Sdk.Client; 6 | 7 | /// 8 | /// Factory for repositories. 9 | /// 10 | public interface IRepositoryFactory 11 | { 12 | /// 13 | /// Gets the organization service. 14 | /// 15 | IOrganizationService OrganizationService { get; } 16 | 17 | /// 18 | /// Get a repository for the given entity. 19 | /// 20 | /// The context. 21 | /// The entity. 22 | /// A repository for the given entity. 23 | ICrmRepository GetRepository() 24 | where TEntity : Entity, new() 25 | where TContext : OrganizationServiceContext; 26 | } 27 | } -------------------------------------------------------------------------------- /src/generators/app/templates/source/src/common/Client.Package.BusinessLogic/Logging/ConsoleLogWriter.cs: -------------------------------------------------------------------------------- 1 | namespace <%= client %>.<%= package %>.BusinessLogic.Logging 2 | { 3 | using System; 4 | 5 | /// 6 | /// Console log writer. 7 | /// 8 | public class ConsoleLogWriter : ILogWriter 9 | { 10 | /// 11 | public void Log(Severity severity, string tag, string message) 12 | { 13 | Console.WriteLine($"{tag}: {severity}: {message}"); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/generators/app/templates/source/src/common/Client.Package.BusinessLogic/Logging/ILogWriter.cs: -------------------------------------------------------------------------------- 1 | namespace <%= client %>.<%= package %>.BusinessLogic.Logging 2 | { 3 | /// 4 | /// Interface for a log writer. 5 | /// 6 | public interface ILogWriter 7 | { 8 | /// 9 | /// Write to the log. 10 | /// 11 | /// The severity of the message. 12 | /// A tag to prepend onto the message (generally the name of the calling class). 13 | /// The message to log. 14 | void Log(Severity severity, string tag, string message); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/generators/app/templates/source/src/common/Client.Package.BusinessLogic/Logging/Severity.cs: -------------------------------------------------------------------------------- 1 | namespace <%= client %>.<%= package %>.BusinessLogic.Logging 2 | { 3 | /// 4 | /// Log severity. 5 | /// 6 | public enum Severity 7 | { 8 | /// 9 | /// Verbose log entry. 10 | /// 11 | Verbose, 12 | 13 | /// 14 | /// Information log entry. 15 | /// 16 | Info, 17 | 18 | /// 19 | /// Warning log entry. 20 | /// 21 | Warning, 22 | 23 | /// 24 | /// Error log entry. 25 | /// 26 | Error, 27 | } 28 | } -------------------------------------------------------------------------------- /src/generators/app/templates/source/src/common/Client.Package.BusinessLogic/Logging/TracingServiceLogWriter.cs: -------------------------------------------------------------------------------- 1 | namespace <%= client %>.<%= package %>.BusinessLogic.Logging 2 | { 3 | using System; 4 | using System.Globalization; 5 | using Microsoft.Xrm.Sdk; 6 | 7 | /// 8 | /// Logs messages using the tracing service. 9 | /// 10 | public class TracingServiceLogWriter : ILogWriter 11 | { 12 | private readonly ITracingService tracingService; 13 | private readonly bool addTimestamps; 14 | 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | /// The tracing service. 19 | /// Whether or not to timestamp messages. 20 | public TracingServiceLogWriter(ITracingService tracingService, bool timestamp = false) 21 | { 22 | this.tracingService = tracingService ?? throw new ArgumentNullException(nameof(tracingService)); 23 | this.addTimestamps = timestamp; 24 | } 25 | 26 | /// 27 | public void Log(Severity severity, string tag, string message) 28 | { 29 | this.tracingService.Trace(this.FormatMessage(severity, tag, message)); 30 | } 31 | 32 | private string FormatMessage(Severity severity, string tag, string message) 33 | { 34 | return $"{(this.addTimestamps ? $"{DateTime.UtcNow.ToString("o", CultureInfo.CurrentCulture)}: " : string.Empty)}{tag}: {severity}: {message}"; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/generators/app/templates/source/src/common/Client.Package.BusinessLogic/Plugin.cs: -------------------------------------------------------------------------------- 1 | namespace <%= client %>.<%= package %>.BusinessLogic 2 | { 3 | using System; 4 | using <%= client %>.<%= package %>.BusinessLogic.Logging; 5 | using Microsoft.Xrm.Sdk; 6 | 7 | /// 8 | /// Dynamics 365 plugin. 9 | /// 10 | public abstract class Plugin : IPlugin 11 | { 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | public Plugin() 16 | : base() 17 | { 18 | } 19 | 20 | /// 21 | /// Initializes a new instance of the class. 22 | /// 23 | /// The unsecure configuration. 24 | /// The secure configuration. 25 | public Plugin(string unsecureConfig, string secureConfig) 26 | : this() 27 | { 28 | this.UnsecureConfig = unsecureConfig; 29 | this.SecureConfig = secureConfig; 30 | } 31 | 32 | /// 33 | /// Gets the plugin step's unsecure configuration. 34 | /// 35 | protected string UnsecureConfig { get; private set; } 36 | 37 | /// 38 | /// Gets the plugin step's secure configuration. 39 | /// 40 | protected string SecureConfig { get; private set; } 41 | 42 | /// 43 | public void Execute(IServiceProvider serviceProvider) 44 | { 45 | var tracingSvc = (ITracingService)serviceProvider.GetService(typeof(ITracingService)); 46 | var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext)); 47 | var serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory)); 48 | var orgSvc = serviceFactory.CreateOrganizationService(Guid.Empty); 49 | var repositoryFactory = new RepositoryFactory(orgSvc); 50 | var logWriter = new TracingServiceLogWriter(tracingSvc, true); 51 | 52 | this.Execute(context, orgSvc, logWriter, repositoryFactory); 53 | } 54 | 55 | /// 56 | /// Execute the plugin. 57 | /// 58 | /// The plugin execution context. 59 | /// The organization service. 60 | /// The log writer. 61 | /// The repository factory. 62 | protected abstract void Execute(IPluginExecutionContext context, IOrganizationService orgSvc, TracingServiceLogWriter logWriter, RepositoryFactory repositoryFactory); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/generators/app/templates/source/src/common/Client.Package.BusinessLogic/RepositoryFactory.cs: -------------------------------------------------------------------------------- 1 | namespace <%= client %>.<%= package %>.BusinessLogic 2 | { 3 | using <%= client %>.<%= package %>.Repositories; 4 | using Microsoft.Xrm.Sdk; 5 | using Microsoft.Xrm.Sdk.Client; 6 | 7 | /// 8 | /// Concrete implementation of . 9 | /// 10 | public class RepositoryFactory : IRepositoryFactory 11 | { 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | /// Organization service. 16 | public RepositoryFactory(IOrganizationService orgSvc) 17 | { 18 | this.OrganizationService = orgSvc; 19 | } 20 | 21 | /// 22 | /// Gets the organization service. 23 | /// 24 | public IOrganizationService OrganizationService { get; private set; } 25 | 26 | /// 27 | public ICrmRepository GetRepository() 28 | where TEntity : Entity, new() 29 | where TContext : OrganizationServiceContext 30 | { 31 | return new CrmRepository(this.OrganizationService); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/generators/app/templates/source/src/common/Client.Package.BusinessLogic/WorkflowActivity.cs: -------------------------------------------------------------------------------- 1 | namespace <%= client %>.<%= package %>.BusinessLogic 2 | { 3 | using System.Activities; 4 | using <%= client %>.<%= package %>.BusinessLogic.Logging; 5 | using Microsoft.Xrm.Sdk; 6 | using Microsoft.Xrm.Sdk.Workflow; 7 | 8 | /// 9 | /// Base class for Dynamics 365 workflow activities. 10 | /// 11 | public abstract class WorkflowActivity : CodeActivity 12 | { 13 | /// 14 | /// Execute the custom workflow activity. 15 | /// 16 | /// The context. 17 | /// The workflow context. 18 | /// Organization service. 19 | /// Log writer. 20 | /// Repository factory. 21 | protected abstract void ExecuteWorkflowActivity(CodeActivityContext context, IWorkflowContext workflowContext, IOrganizationService orgSvc, ILogWriter logWriter, IRepositoryFactory repoFactory); 22 | 23 | /// 24 | protected override void Execute(CodeActivityContext context) 25 | { 26 | var tracingSvc = context.GetExtension(); 27 | var workflowContext = context.GetExtension(); 28 | var serviceFactory = context.GetExtension(); 29 | var orgSvc = serviceFactory.CreateOrganizationService(workflowContext.UserId); 30 | var repositoryFactory = new RepositoryFactory(orgSvc); 31 | var logWriter = new TracingServiceLogWriter(tracingSvc, true); 32 | 33 | this.ExecuteWorkflowActivity(context, workflowContext, orgSvc, logWriter, repositoryFactory); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/generators/app/templates/source/src/common/Client.Package.Container/Client.Package.Container.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net462 4 | <%= client %>.<%= package %>.Container 5 | <%= client %>.<%= package %>.Container.ruleset 6 | 7 | 8 | true 9 | 10 | 11 | <%= client %>.<%= package %>.Container.snk 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | all 21 | 22 | 23 | all 24 | runtime; build; native; contentfiles; analyzers; buildtransitive 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/generators/app/templates/source/src/common/Client.Package.Container/Client.Package.Container.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Capgemini/powerapps-project-template/b7dbaf1e8945349d4c83f72bf0dc22c2f2d476d1/src/generators/app/templates/source/src/common/Client.Package.Container/Client.Package.Container.snk -------------------------------------------------------------------------------- /src/generators/app/templates/source/src/common/Client.Package.Model/Client.Package.Model.projitems: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath) 5 | true 6 | 70363b14-5d32-4bd6-aa74-a1ca3373c087 7 | 8 | 9 | <%= client %>.<%= package %>.Model 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/generators/app/templates/source/src/common/Client.Package.Model/Client.Package.Model.shproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 70363b14-5d32-4bd6-aa74-a1ca3373c087 5 | 14.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/generators/app/templates/source/src/common/Client.Package.Model/spkl.json: -------------------------------------------------------------------------------- 1 | { 2 | "earlyboundtypes": [ 3 | { 4 | "entities": "", 5 | "actions": "", 6 | "generateOptionsetEnums": true, 7 | "generateStateEnums": true, 8 | "generateGlobalOptionsets": true, 9 | "filename": "<%= package %>Context.cs", 10 | "classNamespace": "<%= client %>.<%= package %>.Model", 11 | "serviceContextName": "<%= package %>Context" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /src/generators/app/templates/source/src/common/Client.Package.Repositories/Client.Package.Repositories.projitems: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath) 5 | true 6 | 56d657e2-2db7-4489-ad26-ca2e147836c6 7 | 8 | 9 | <%= client %>.<%= package %>.Repositories 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/generators/app/templates/source/src/common/Client.Package.Repositories/Client.Package.Repositories.shproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 56d657e2-2db7-4489-ad26-ca2e147836c6 5 | 14.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/generators/app/templates/source/src/common/Client.Package.Repositories/SandboxWebClient.cs: -------------------------------------------------------------------------------- 1 | namespace <%= client %>.<%= package %>.Repositories 2 | { 3 | using System; 4 | using System.Net; 5 | 6 | /// 7 | /// Web client optimised for use in the Dynamics 365 sandbox. 8 | /// . 9 | /// 10 | public class SandboxWebClient : WebClient 11 | { 12 | /// 13 | protected override WebRequest GetWebRequest(Uri address) 14 | { 15 | var req = (HttpWebRequest)base.GetWebRequest(address); 16 | req.KeepAlive = false; 17 | 18 | return req; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/generators/app/templates/source/tests/Client.Package.IntegrationTests/Client.Package.IntegrationTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net48 4 | <%= client %>.<%= package %>.IntegrationTests 5 | <%= client %>.<%= package %>.IntegrationTests.ruleset 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | all 17 | 18 | 19 | all 20 | runtime; build; native; contentfiles; analyzers; buildtransitive 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | all 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | PreserveNewest 62 | 63 | 64 | -------------------------------------------------------------------------------- /src/generators/app/templates/source/tests/Client.Package.IntegrationTests/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Standard convention for test methods", Scope = "namespace", Target = "<%= client %>.<%= package %>.IntegrationTests")] 6 | -------------------------------------------------------------------------------- /src/generators/app/templates/source/tests/Client.Package.IntegrationTests/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/generators/app/templates/source/tests/Client.Package.IntegrationTests/xunit.runner.json: -------------------------------------------------------------------------------- 1 | { 2 | "methodDisplay": "method" 3 | } -------------------------------------------------------------------------------- /src/generators/app/templates/source/tests/Client.Package.UiTests/Bindings/keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Capgemini/powerapps-project-template/b7dbaf1e8945349d4c83f72bf0dc22c2f2d476d1/src/generators/app/templates/source/tests/Client.Package.UiTests/Bindings/keep -------------------------------------------------------------------------------- /src/generators/app/templates/source/tests/Client.Package.UiTests/Client.Package.UiTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net48 4 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 5 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 6 | <%= client %>.<%= package %>.UiTests 7 | <%= client %>.<%= package %>.UiTests.ruleset 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | all 18 | 19 | 20 | all 21 | runtime; build; native; contentfiles; analyzers; buildtransitive 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | PreserveNewest 40 | 41 | 42 | PreserveNewest 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/generators/app/templates/source/tests/Client.Package.UiTests/Data/keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Capgemini/powerapps-project-template/b7dbaf1e8945349d4c83f72bf0dc22c2f2d476d1/src/generators/app/templates/source/tests/Client.Package.UiTests/Data/keep -------------------------------------------------------------------------------- /src/generators/app/templates/source/tests/Client.Package.UiTests/Extensions/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace <%= client %>.<%= package %>.UiTests.Extensions 2 | { 3 | using System.Security; 4 | 5 | /// 6 | /// Extensions to . 7 | /// 8 | public static class StringExtensions 9 | { 10 | /// 11 | /// Converts a string to a . 12 | /// 13 | /// The input string. 14 | /// The secure string. 15 | public static SecureString ToSecureString(this string input) 16 | { 17 | if (string.IsNullOrWhiteSpace(input)) 18 | { 19 | return null; 20 | } 21 | else 22 | { 23 | var result = new SecureString(); 24 | 25 | foreach (char c in input.ToCharArray()) 26 | { 27 | result.AppendChar(c); 28 | } 29 | 30 | return result; 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/generators/app/templates/source/tests/Client.Package.UiTests/power-apps-bindings.yml: -------------------------------------------------------------------------------- 1 | url: 2 | browserOptions: 3 | browserType: Chrome 4 | headless: true 5 | width: 1920 6 | height: 1080 7 | startMaximized: false 8 | users: 9 | - username: 10 | password: 11 | alias: -------------------------------------------------------------------------------- /src/generators/app/templates/source/tests/Client.Package.UiTests/specflow.json: -------------------------------------------------------------------------------- 1 | { 2 | "bindingCulture": { 3 | "language": "en-gb" 4 | }, 5 | "language": { 6 | "feature": "en-gb" 7 | }, 8 | "stepAssemblies": [ 9 | { "assembly": "Capgemini.PowerApps.SpecFlowBindings" } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /src/generators/app/templates/source/tests/Client.Package.UnitTests/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Standard convention for test methods", Scope = "namespace", Target = "<%= client %>.<%= package %>.UnitTests")] 6 | -------------------------------------------------------------------------------- /src/generators/app/templates/source/tests/Client.Package.UnitTests/WorkflowActivityTests.cs: -------------------------------------------------------------------------------- 1 | namespace <%= client %>.<%= package %>.UnitTests 2 | { 3 | using System; 4 | using System.Activities; 5 | using Microsoft.Xrm.Sdk; 6 | using Microsoft.Xrm.Sdk.Workflow; 7 | using Moq; 8 | 9 | /// 10 | /// Base class for workflow activity tests. 11 | /// 12 | /// The type of workflow activity. 13 | public abstract class WorkflowActivityTests 14 | where TCodeActivity : CodeActivity, new() 15 | { 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | public WorkflowActivityTests() 20 | { 21 | this.WorkflowActivity = new TCodeActivity(); 22 | this.WorkflowInvoker = new WorkflowInvoker(this.WorkflowActivity); 23 | 24 | this.TracingSvcMock = new Mock(); 25 | this.OrgSvcFactoryMock = new Mock(); 26 | this.OrgSvcMock = new Mock(); 27 | this.WorkflowContextMock = new Mock(); 28 | 29 | this.OrgSvcFactoryMock.SetReturnsDefault(this.OrgSvcMock.Object); 30 | this.WorkflowContextMock.Setup(workflowContext => workflowContext.UserId).Returns(Guid.NewGuid()); 31 | this.WorkflowContextMock.Setup(workflowContext => workflowContext.SharedVariables).Returns(new ParameterCollection()); 32 | 33 | this.WorkflowInvoker.Extensions.Add(this.WorkflowContextMock.Object); 34 | this.WorkflowInvoker.Extensions.Add(this.TracingSvcMock.Object); 35 | this.WorkflowInvoker.Extensions.Add(this.OrgSvcFactoryMock.Object); 36 | this.WorkflowInvoker.Extensions.Add(this.OrgSvcMock.Object); 37 | } 38 | 39 | /// 40 | /// Gets the workflow invoker. 41 | /// 42 | protected WorkflowInvoker WorkflowInvoker { get; } 43 | 44 | /// 45 | /// Gets the workflow activity under test. 46 | /// 47 | protected TCodeActivity WorkflowActivity { get; } 48 | 49 | /// 50 | /// Gets mock workflow context. 51 | /// 52 | protected Mock WorkflowContextMock { get; } 53 | 54 | /// 55 | /// Gets the mocked tracing service. 56 | /// 57 | protected Mock TracingSvcMock { get; } 58 | 59 | /// 60 | /// Gets the mocked organization service factory. 61 | /// 62 | protected Mock OrgSvcFactoryMock { get; } 63 | 64 | /// 65 | /// Gets the mocked organization service. 66 | /// 67 | protected Mock OrgSvcMock { get; } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/generators/app/templates/source/tests/Client.Package.UnitTests/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/generators/app/templates/source/tests/Client.Package.UnitTests/unit.runsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | .*UnitTests.dll 12 | .*testadapter.dll 13 | 14 | 15 | True 16 | True 17 | True 18 | False 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/generators/app/templates/source/tests/Client.Package.UnitTests/xunit.runner.json: -------------------------------------------------------------------------------- 1 | { 2 | "methodDisplay": "method" 3 | } -------------------------------------------------------------------------------- /src/generators/app/types/BuildProcess.d.ts: -------------------------------------------------------------------------------- 1 | declare interface BuildProcess { 2 | type: number; 3 | yamlFilename: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/generators/app/types/DeployPhase.d.ts: -------------------------------------------------------------------------------- 1 | declare interface DeployPhase { 2 | deploymentInput?: { 3 | queueId?: number; 4 | }; 5 | } 6 | -------------------------------------------------------------------------------- /src/generators/azuredevops/IPackageDetails.ts: -------------------------------------------------------------------------------- 1 | export interface IPackageDetails { 2 | name: string; 3 | path: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/generators/azuredevops/IScaffoldResult.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BuildDefinition, 3 | VariableGroup, 4 | } from 'azure-devops-node-api/interfaces/BuildInterfaces'; 5 | import { ReleaseDefinition } from 'azure-devops-node-api/interfaces/ReleaseInterfaces'; 6 | import { ServiceEndpoint } from 'azure-devops-node-api/interfaces/TaskAgentInterfaces'; 7 | import { GitRepository } from 'azure-devops-node-api/interfaces/TfvcInterfaces'; 8 | 9 | export interface IScaffoldResult { 10 | buildDefinitions: BuildDefinition[]; 11 | releaseDefinition: ReleaseDefinition; 12 | repositories: GitRepository; 13 | variableGroups: VariableGroup[]; 14 | serviceEndpoint: ServiceEndpoint 15 | } 16 | -------------------------------------------------------------------------------- /src/generators/azuredevops/IScaffoldSettings.ts: -------------------------------------------------------------------------------- 1 | import { IPackageDetails } from './IPackageDetails'; 2 | 3 | export interface IScaffoldSettings { 4 | personalAccessToken: string; 5 | projectName: string; 6 | clientName: string; 7 | package: IPackageDetails; 8 | gitRepository: string; 9 | serviceAccountUsername: string; 10 | serviceAccountPassword: string; 11 | ciEnvironmentUrl: string; 12 | applicationId: string; 13 | tenantId: string; 14 | clientSecret: string; 15 | } 16 | -------------------------------------------------------------------------------- /src/generators/azuredevops/definitions/build/build.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "tags": [""], 4 | "buildNumberFormat": "$(date:yyyyMMdd)$(rev:.r)", 5 | "path": "", 6 | "process": { 7 | "type": 2, 8 | "yamlFilename": "" 9 | }, 10 | "triggers": [ 11 | { 12 | "branchFilters": [], 13 | "pathFilters": [], 14 | "settingsSourceType": 2, 15 | "batchChanges": true, 16 | "maxConcurrentBuildsPerBranch": 1, 17 | "triggerType": 2 18 | } 19 | ], 20 | "quality": 1, 21 | "queue": { "name": "Hosted VS2017" }, 22 | "queueStatus": 0, 23 | "repository": { 24 | "checkoutSubmodules": false, 25 | "clean": "true", 26 | "defaultBranch": "refs/heads/master", 27 | "id": "", 28 | "properties": { 29 | "checkoutNestedSubmodules": "false", 30 | "cleanOptions": "3", 31 | "fetchDepth": "0", 32 | "gitLfsSupport": "false", 33 | "labelSources": "0", 34 | "labelSourcesFormat": "$(build.buildNumber)", 35 | "reportBuildStatus": "true", 36 | "skipSyncSource": "false" 37 | }, 38 | "type": "TfsGit" 39 | }, 40 | "type": 2, 41 | "variableGroups": [{}], 42 | "variables": { 43 | "BuildConfiguration": { 44 | "allowOverride": true, 45 | "value": "release" 46 | }, 47 | "BuildPlatform": { 48 | "allowOverride": true, 49 | "value": "any cpu" 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/generators/azuredevops/definitions/extensions.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "publisher": "microsoft-IsvExpTools", 4 | "name": "PowerPlatform-BuildTools" 5 | }, 6 | { 7 | "publisher": "sariftools", 8 | "name": "scans" 9 | }, 10 | { 11 | "publisher": "gittools", 12 | "name": "gittools" 13 | } 14 | ] 15 | -------------------------------------------------------------------------------- /src/generators/azuredevops/definitions/serviceendpoints/ci-environment.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "[overridden by generator]", 3 | "type": "powerplatform-spn", 4 | "url": "[overridden by generator]", 5 | "authorization": { 6 | "parameters": { 7 | "tenantId": "[overridden by generator]", 8 | "applicationId": "[overridden by generator]", 9 | "clientSecret": "[overridden by generator]" 10 | }, 11 | "scheme": "None" 12 | }, 13 | "owner": "Library" 14 | } -------------------------------------------------------------------------------- /src/generators/azuredevops/definitions/variablegroups/ci-environment.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "[overridden by generator]", 3 | "variables": { 4 | "keep": { 5 | "value": "This can be deleted once another variable has been added." 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /src/generators/azuredevops/definitions/variablegroups/integration-tests.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "[overridden by generator]", 3 | "variables": { 4 | "CDS Test Admin Username": { 5 | "value": "[overridden by generator]" 6 | }, 7 | "CDS Test Admin Password": { 8 | "value": "[overridden by generator]", 9 | "isSecret": true 10 | }, 11 | "CDS Test CDS URL": { 12 | "value": "[overridden by generator]" 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /src/generators/azuredevops/generator/BuildGenerator.ts: -------------------------------------------------------------------------------- 1 | import { BuildApi } from 'azure-devops-node-api/BuildApi'; 2 | import { BuildDefinition } from 'azure-devops-node-api/interfaces/BuildInterfaces'; 3 | import glob from 'glob-promise'; 4 | import { parse } from 'path'; 5 | import buildDef from '../definitions/build/build.json'; 6 | import { IGenerator } from './IGenerator.js'; 7 | 8 | function getBuildDefinitionName(yamlPath: string) { 9 | const path = parse(yamlPath); 10 | 11 | return path.name === 'azure-pipelines' 12 | ? 'Package Build' 13 | : path.name 14 | .replace('azure-pipelines-', '') 15 | .replace(/-/g, ' ') 16 | .replace(/\w\S*/g, (txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()); 17 | } 18 | 19 | export default class BuildGenerator implements IGenerator { 20 | public readonly createdObjects: BuildDefinition[]; 21 | 22 | private readonly conn: BuildApi; 23 | 24 | private readonly log: (msg: string) => void; 25 | 26 | constructor(conn: BuildApi, log: (msg: string) => void) { 27 | this.conn = conn; 28 | this.log = log; 29 | this.createdObjects = []; 30 | } 31 | 32 | public async generate( 33 | packageDirectory: string, 34 | project: string, 35 | packageName: string, 36 | repoId: string, 37 | ): Promise { 38 | this.log('Generating builds definitions...'); 39 | const yamlDetails = await this.getYamlDetails(packageDirectory); 40 | const buildDefs = await this.createBuildDefinitions( 41 | project, 42 | yamlDetails.map((yaml) => this.generateBuildDefinition(yaml, packageName, repoId)), 43 | ); 44 | 45 | if (buildDefs === undefined) { 46 | throw new Error('An error occurred while creating build definitions.'); 47 | } 48 | 49 | this.createdObjects.push(...buildDefs); 50 | 51 | return buildDefs; 52 | } 53 | 54 | public async rollback(project: string): Promise { 55 | this.log(`Rolling back ${this.createdObjects.length} build definitions...`); 56 | 57 | await Promise.all( 58 | this.createdObjects.map((obj) => this.conn.deleteDefinition(obj.id!, project)), 59 | ); 60 | this.createdObjects.length = 0; 61 | } 62 | 63 | private async getYamlDetails(packageDirectory: string) { 64 | return glob('pipelines/*.yml', { cwd: `${packageDirectory}` }).then((files) => { 65 | this.log(`Found ${files.length} YAML builds.`); 66 | return files; 67 | }); 68 | } 69 | 70 | private async createBuildDefinitions( 71 | project: string, 72 | definitions: BuildDefinition[], 73 | ) { 74 | const createDefinitionPromises = definitions 75 | .map((def) => this.conn.createDefinition(def, project)); 76 | 77 | return Promise.all(createDefinitionPromises); 78 | } 79 | 80 | private generateBuildDefinition( 81 | yamlPath: string, 82 | packageName: string, 83 | repoId: string, 84 | ): BuildDefinition { 85 | this.log(`Creating ${yamlPath} build...`); 86 | const def: BuildDefinition = JSON.parse(JSON.stringify(buildDef)); 87 | const buildDefinitionName = getBuildDefinitionName(yamlPath); 88 | def.name = `${packageName} - ${buildDefinitionName}`; 89 | const process: BuildProcess = { 90 | type: 2, 91 | yamlFilename: yamlPath, 92 | }; 93 | def.process = process; 94 | def.repository!.id = repoId; 95 | def.path = packageName.replace(/\s/g, ''); 96 | def.variableGroups = []; 97 | 98 | return def; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/generators/azuredevops/generator/ExtensionGenerator.ts: -------------------------------------------------------------------------------- 1 | import { ExtensionManagementApi } from 'azure-devops-node-api/ExtensionManagementApi'; 2 | import { sleep } from '../../../common/utilities'; 3 | import extensions from '../definitions/extensions.json'; 4 | import { IGenerator } from './IGenerator'; 5 | 6 | export default class ExtensionGenerator implements IGenerator<{ publisher: string; name: string }> { 7 | public readonly createdObjects: Array<{ 8 | publisher: string; 9 | name: string; 10 | }>; 11 | 12 | private readonly conn: ExtensionManagementApi; 13 | 14 | private readonly log: (msg: string) => void; 15 | 16 | constructor(conn: ExtensionManagementApi, log: (msg: string) => void) { 17 | this.conn = conn; 18 | this.log = log; 19 | this.createdObjects = []; 20 | } 21 | 22 | public async generate() { 23 | const installedExtensions = await this.conn.getInstalledExtensions(); 24 | 25 | const uninstalledEntensions = extensions.filter((extension) => { 26 | const installed = installedExtensions 27 | // eslint-disable-next-line max-len 28 | .find((ext) => ext.extensionId === extension.name && ext.publisherId === extension.publisher) !== undefined; 29 | 30 | if (installed) { 31 | this.log(`Extension: ${extension.name} is already installed.`); 32 | } 33 | 34 | return !installed; 35 | }); 36 | 37 | const installPromises: Array | undefined> = uninstalledEntensions 38 | .map((extension) => this.installExtension(extension.publisher, extension.name)); 39 | 40 | await Promise.all(installPromises); 41 | 42 | if (installPromises.length > 0) { 43 | this.log('Waiting 1 minute for the extension(s) to fully install.'); 44 | const ONE_MINUTE = 1 * 60 * 1000; 45 | await sleep(ONE_MINUTE); 46 | } 47 | } 48 | 49 | public async rollback(): Promise { 50 | this.log(`Rolling back ${this.createdObjects.length} extensions...`); 51 | 52 | await Promise.all( 53 | this.createdObjects.map((obj) => this.conn.uninstallExtensionByName(obj.publisher, obj.name)), 54 | ); 55 | this.createdObjects.length = 0; 56 | } 57 | 58 | private async installExtension(publisher: string, name: string) { 59 | this.log( 60 | `Installing '${name}' from '${publisher}' extension...`, 61 | ); 62 | 63 | await this.conn.installExtensionByName(publisher, name); 64 | this.createdObjects.push({ publisher, name }); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/generators/azuredevops/generator/IGenerator.ts: -------------------------------------------------------------------------------- 1 | export interface IGenerator { 2 | createdObjects: TGeneratedType[]; 3 | rollback(project: string): Promise; 4 | } 5 | -------------------------------------------------------------------------------- /src/generators/azuredevops/generator/RepoGenerator.ts: -------------------------------------------------------------------------------- 1 | import { GitApi } from 'azure-devops-node-api/GitApi'; 2 | import { 3 | GitRepository, 4 | GitRepositoryCreateOptions, 5 | } from 'azure-devops-node-api/interfaces/GitInterfaces'; 6 | import * as fs from 'fs'; 7 | import Git from 'simple-git'; 8 | import { IGenerator } from './IGenerator'; 9 | 10 | export default class RepoGenerator implements IGenerator { 11 | public readonly createdObjects: GitRepository[]; 12 | 13 | private readonly conn: GitApi; 14 | 15 | private readonly log: (msg: string) => void; 16 | 17 | private repoLocation: string; 18 | 19 | constructor(conn: GitApi, log: (msg: string) => void) { 20 | this.conn = conn; 21 | this.log = log; 22 | this.createdObjects = []; 23 | this.repoLocation = ''; 24 | } 25 | 26 | public async generate( 27 | projectName: string, 28 | repoName: string, 29 | repoLocation: string, 30 | adoToken: string, 31 | ): Promise { 32 | this.log('Generating repository...'); 33 | this.repoLocation = repoLocation; 34 | 35 | const repo = await this.createRepo(projectName, { 36 | name: repoName, 37 | }); 38 | 39 | if (repo === undefined) { 40 | throw new Error('An error occurred while creating repository.'); 41 | } 42 | 43 | const orgName : string = repo.remoteUrl!.startsWith('https://dev.azure.com/') 44 | ? repo.remoteUrl!.split('/')[3] // for https://dev.azure.com/{orgname}/ 45 | : repo.remoteUrl!.split('/')[2].split('.')[0]; // for https://{orgname}.visualstudio.com/ 46 | const repoUrl = `https://${orgName}:${adoToken}@dev.azure.com/${orgName}/${encodeURIComponent(projectName)}/_git/${repoName}`; 47 | await this.pushRepo(repoLocation, repoUrl); 48 | 49 | return repo; 50 | } 51 | 52 | public async rollback(project: string): Promise { 53 | this.log(`Rolling back ${this.createdObjects.length} repositories...`); 54 | 55 | await Promise.all( 56 | this.createdObjects.map((obj) => this.conn.deleteRepository(obj.id!, project)), 57 | ); 58 | 59 | if (this.repoLocation && fs.existsSync(`${this.repoLocation}/.git`)) { 60 | this.log('Deleting the .git directory...'); 61 | // @ts-ignore The second argument is allowed but the type definition hasn't been updated. 62 | await fs.promises.rmdir(`${this.repoLocation}/.git`, { recursive: true }); 63 | } 64 | 65 | this.createdObjects.length = 0; 66 | } 67 | 68 | private async createRepo( 69 | project: string, 70 | repo: GitRepositoryCreateOptions, 71 | ): Promise { 72 | this.log(`Creating ${repo.name} repository...`); 73 | const repository = await this.conn.createRepository(repo, project); 74 | this.createdObjects.push(repository); 75 | 76 | return repository; 77 | } 78 | 79 | private async pushRepo(repoLocation: string, gitUrl: string) { 80 | this.log(`Pushing initial commit to ${gitUrl}`); 81 | 82 | const repo = Git(repoLocation); 83 | return repo 84 | .init(['--initial-branch', 'master']) 85 | .then(() => repo.add('.')) 86 | .then(() => repo.commit('Initial commit from template.')) 87 | .then(() => repo.remote(['add', 'origin', gitUrl])) 88 | .then(() => repo.push('origin', undefined, ['--set-upstream', '--all'])); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/generators/azuredevops/generator/ServiceEndpointGenerator.ts: -------------------------------------------------------------------------------- 1 | import { ServiceEndpoint } from 'azure-devops-node-api/interfaces/TaskAgentInterfaces'; 2 | import { ITaskAgentApi } from 'azure-devops-node-api/TaskAgentApi'; 3 | import ciEnvironment from '../definitions/serviceendpoints/ci-environment.json'; 4 | import { IGenerator } from './IGenerator.js'; 5 | 6 | export default class ServiceEndpointGenerator implements IGenerator { 7 | public readonly createdObjects: ServiceEndpoint[]; 8 | 9 | private readonly conn: ITaskAgentApi; 10 | 11 | private readonly log: (msg: string) => void; 12 | 13 | constructor(conn: ITaskAgentApi, log: (msg: string) => void) { 14 | this.conn = conn; 15 | this.log = log; 16 | this.createdObjects = []; 17 | } 18 | 19 | public async generate( 20 | project: string, 21 | packageName: string, 22 | ciUrl: string, 23 | tenantId: string, 24 | applicationId: string, 25 | clientSecret: string, 26 | ): Promise { 27 | this.log('Generating service connections...'); 28 | const serviceEndpoint = ServiceEndpointGenerator.generateServiceEndpoint( 29 | packageName, 30 | ciUrl, 31 | tenantId, 32 | applicationId, 33 | clientSecret, 34 | ); 35 | const def = await this.conn.createServiceEndpoint(serviceEndpoint, project); 36 | 37 | if (def === undefined) { 38 | throw new Error('An error occurred while creating service connections.'); 39 | } 40 | 41 | this.createdObjects.push(def); 42 | 43 | return def; 44 | } 45 | 46 | public async rollback(project: string): Promise { 47 | this.log( 48 | `Rolling back ${this.createdObjects.length} service connections...`, 49 | ); 50 | 51 | await Promise.all( 52 | this.createdObjects.map((obj) => this.conn.deleteServiceEndpoint(project, obj.id!)), 53 | ); 54 | this.createdObjects.length = 0; 55 | } 56 | 57 | private async createServiceEndpoint( 58 | project: string, 59 | endpoint: ServiceEndpoint, 60 | ): Promise { 61 | return this.conn.createServiceEndpoint(endpoint, project); 62 | } 63 | 64 | private static generateServiceEndpoint( 65 | packageName: string, 66 | ciUrl: string, 67 | tenantId: string, 68 | applicationId: string, 69 | clientSecret: string, 70 | ): ServiceEndpoint { 71 | const endpoint: ServiceEndpoint = JSON.parse(JSON.stringify(ciEnvironment)); 72 | 73 | endpoint.name = `CI Environment - ${packageName}`; 74 | endpoint.url = ciUrl; 75 | endpoint.authorization!.parameters!.tenantId = tenantId; 76 | endpoint.authorization!.parameters!.applicationId = applicationId; 77 | endpoint.authorization!.parameters!.clientSecret = clientSecret; 78 | 79 | return endpoint; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/generators/azuredevops/generator/VarGroupGenerator.ts: -------------------------------------------------------------------------------- 1 | import { VariableGroup } from 'azure-devops-node-api/interfaces/BuildInterfaces'; 2 | import { VariableGroupParameters } from 'azure-devops-node-api/interfaces/TaskAgentInterfaces'; 3 | import { ITaskAgentApi } from 'azure-devops-node-api/TaskAgentApi'; 4 | import ciEnvironment from '../definitions/variablegroups/ci-environment.json'; 5 | import integrationTests from '../definitions/variablegroups/integration-tests.json'; 6 | import { IGenerator } from './IGenerator.js'; 7 | 8 | export default class VarGroupGenerator implements IGenerator { 9 | public readonly createdObjects: VariableGroup[]; 10 | 11 | private readonly conn: ITaskAgentApi; 12 | 13 | private readonly log: (msg: string) => void; 14 | 15 | constructor(conn: ITaskAgentApi, log: (msg: string) => void) { 16 | this.conn = conn; 17 | this.log = log; 18 | this.createdObjects = []; 19 | } 20 | 21 | public async generate( 22 | project: string, 23 | packageName: string, 24 | ciEnvironmentUrl: string, 25 | serviceAccountUsername: string, 26 | serviceAccountPassword: string, 27 | ): Promise { 28 | this.log('Generating variable groups...'); 29 | 30 | const groupsToCreate = VarGroupGenerator.generateVariableGroups( 31 | packageName, 32 | ciEnvironmentUrl, 33 | serviceAccountUsername, 34 | serviceAccountPassword, 35 | ); 36 | 37 | const varGroups = await this.createVariableGroups(project, groupsToCreate); 38 | this.createdObjects.push(...varGroups); 39 | 40 | return varGroups; 41 | } 42 | 43 | public async rollback(project: string): Promise { 44 | this.log(`Rolling back ${this.createdObjects.length} variable groups...`); 45 | await Promise.all( 46 | this.createdObjects.map((obj) => this.conn.deleteVariableGroup(project, obj.id!)), 47 | ); 48 | this.createdObjects.length = 0; 49 | } 50 | 51 | private static generateVariableGroups( 52 | packageName: string, 53 | ciEnvironmentUrl: string, 54 | serviceAccountUsername: string, 55 | serviceAccountPassword: string, 56 | ): VariableGroup[] { 57 | const groups: VariableGroup[] = []; 58 | 59 | ciEnvironment.name = `CI Environment - ${packageName}`; 60 | groups.push(ciEnvironment); 61 | 62 | integrationTests.name = `Integration Tests - ${packageName}`; 63 | integrationTests.variables['CDS Test CDS URL'].value = ciEnvironmentUrl; 64 | integrationTests.variables['CDS Test Admin Username'].value = serviceAccountUsername; 65 | integrationTests.variables['CDS Test Admin Password'].value = serviceAccountPassword; 66 | groups.push(integrationTests); 67 | 68 | return groups; 69 | } 70 | 71 | private async createVariableGroups( 72 | project: string, 73 | groups: VariableGroupParameters[], 74 | ): Promise { 75 | return Promise.all( 76 | groups.map((group) => { 77 | this.log(`Creating ${group.name} variable group...`); 78 | return this.conn.addVariableGroup(group, project); 79 | }), 80 | ); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/generators/data/templates/source/data/{{Solution}}DataExport.json: -------------------------------------------------------------------------------- 1 | { 2 | "CrmMigrationToolSchemaPaths": ["<%= solution %>DataSchema.xml"], 3 | "CrmMigrationToolSchemaFilters": {}, 4 | "PageSize": 1000, 5 | "BatchSize": 1000, 6 | "TopCount": 10000, 7 | "OnlyActiveRecords": false, 8 | "JsonFolderPath": "Extract", 9 | "OneEntityPerBatch": true, 10 | "FilePrefix": "<%= prefix %>", 11 | "SeperateFilesPerEntity": true, 12 | "LookupMapping": { 13 | "teamroles": { 14 | "roleid": ["name", "businessunitid"], 15 | "teamid": ["name", "businessunitid"] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/generators/data/templates/source/data/{{Solution}}DataImport.json: -------------------------------------------------------------------------------- 1 | { 2 | "IgnoreStatuses": false, 3 | "IgnoreSystemFields": true, 4 | "MigrationConfig": { 5 | "ApplyAliasMapping": true, 6 | "SourceRootBUName": "", 7 | "Mappings": {} 8 | }, 9 | "SaveBatchSize": 200, 10 | "JsonFolderPath": "Extract", 11 | "DeactivateAllProcesses": false, 12 | "FilePrefix": "<%= prefix %>", 13 | "PassOneReferences": [ 14 | "businessunit", 15 | "subject", 16 | "uom", 17 | "uomschedule", 18 | "queue" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /src/generators/data/templates/source/data/{{Solution}}DataSchema.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/generators/pluginassembly/templates/ExtractMappingFile.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/generators/pluginassembly/templates/PackMappingFile.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/generators/pluginassembly/templates/source/{{Client}}.{{Package}}.{{Solution}}/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/<%= client %>.<%= package %>.<%= solution %>.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "watch", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "watch", 22 | "run", 23 | "${workspaceFolder}/<%= client %>.<%= package %>.<%= solution %>.csproj", 24 | "/property:GenerateFullPaths=true", 25 | "/consoleloggerparameters:NoSummary" 26 | ], 27 | "problemMatcher": "$msCompile" 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /src/generators/pluginassembly/templates/source/{{Client}}.{{Package}}.{{Solution}}/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/generators/pluginassembly/templates/source/{{Client}}.{{Package}}.{{Solution}}/{{Client}}.{{Package}}.{{Solution}}.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net462 4 | $(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\PowerApps 5 | true 6 | <%= client %>.<%= package %>.<%= solution %>.snk 7 | <%= client %>.<%= package %>.<%= solution %>.ruleset 8 | Debug;Release;Test 9 | 2.0.0 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | all 23 | 24 | 25 | all 26 | runtime; build; native; contentfiles; analyzers; buildtransitive 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/generators/pluginassembly/templates/source/{{Client}}.{{Package}}.{{Solution}}/{{Client}}.{{Package}}.{{Solution}}.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Capgemini/powerapps-project-template/b7dbaf1e8945349d4c83f72bf0dc22c2f2d476d1/src/generators/pluginassembly/templates/source/{{Client}}.{{Package}}.{{Solution}}/{{Client}}.{{Package}}.{{Solution}}.snk -------------------------------------------------------------------------------- /src/generators/pluginassembly/templates/spkl.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | { 4 | "assemblypath": "PluginAssemblies\\<%= client %>.<%= package %>.<%= solution %>\\bin\\Debug\\net462\\<%= client %>.<%= package %>.<%= solution %>.dll", 5 | "solution": "<%= prefix %>_<%= package %>_<%= solution %>" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /src/generators/powerbi/templates/source/{{Client}}.{{Package}}.PowerBI/reports/readme.txt: -------------------------------------------------------------------------------- 1 | Power BI pbix files should be added to this folder and marked to be copied to output. This will allow them to be included in the build artifacts under PowerBI folder. -------------------------------------------------------------------------------- /src/generators/powerbi/templates/source/{{Client}}.{{Package}}.PowerBI/scripts/UpdateDataSourceCredential.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | PowerBi_Tool 4 | 5 | .DESCRIPTION 6 | This script is used to call function defined in .PSM1 file 7 | 8 | .PARAMETERS 9 | $BuildSourcesDirectory: Path to PowerShell module which is responsible for the interactions with Power BI Rest APIs. Can be selected from artifacts folder 10 | $checkModule : List of Modules that should be imported to run the script successfully. In this case you only need “MicrosoftPowerBIMgmt” 11 | $ClientID : Application Id of the app registration created as part of pre-requisites 12 | $ClientSecret : Secret that was added to this app registration as part of pre-requisites 13 | $TenantId : Tenant Id of the app registration 14 | $WorkSpaceName : target Workspace name 15 | $Authority : App registration oauthv1 endpoint. This can be found under App Reg Endpoint in Azure Active Directory 16 | $Resource : Resource URL to get an access token for to update credentials of the datasource 17 | $Dataset_Input : Name of the report/dataset to update parameters for 18 | $Api_URL : always set this parameter to 'https://api.powerbi.com/v1.0/myorg' 19 | $DataSource_Type : Type of data set we are targeting. At the moment only Dataverse is supported so use “Extension”. This can be extended 20 | $Admin_User_PowerBI : name of the systemuser to use as powerbi admin which is a user in dataverse instance as well 21 | $Admin_Password_PowerBI : password of the systemuser to use as powerbi admin which is a user in resource dataverse instance as well 22 | #> 23 | 24 | [CmdletBinding()] 25 | param( 26 | [Parameter(Mandatory=$true)][string]$BuildSourcesDirectory, 27 | [parameter(Mandatory=$true)]$CheckModule, 28 | [Parameter(Mandatory=$true)]$ClientID, 29 | [Parameter(Mandatory=$true)]$ClientSecret, 30 | [Parameter(Mandatory=$true)]$TenantId, 31 | [Parameter(Mandatory=$true)]$WorkSpaceName, 32 | [Parameter(Mandatory=$true)]$Authority, 33 | [Parameter(Mandatory=$true)]$Resource, 34 | [Parameter(Mandatory=$true)]$Api_URL, 35 | [Parameter(Mandatory=$true)]$Dataset_Input, 36 | [Parameter(Mandatory=$true)]$Admin_User_PowerBI, 37 | [Parameter(Mandatory=$true)]$Admin_Password_PowerBI, 38 | [Parameter(Mandatory=$true)]$DataSource_Type 39 | ) 40 | 41 | Write-Host "Path of Release Directory :$BuildSourcesDirectory." 42 | # Importing UserCreationProcess.psm1 module. 43 | Import-Module "$BuildSourcesDirectory" 44 | Write-Host "Importing Module :$CheckModule." 45 | # Importing Module ie. Active Directory . 46 | ModuleToImport -checkModule $checkModule 47 | 48 | Update_DataSourceCredential -ClientID $ClientID -ClientSecret $ClientSecret -TenantId $TenantId -WorkSpaceName $WorkSpaceName -Authority $Authority -Resource $Resource -Admin_User_PowerBI $Admin_User_PowerBI -Admin_Password_PowerBI $Admin_Password_PowerBI -Dataset_Input $Dataset_Input -Api_URL $Api_URL -DataSource_Type $DataSource_Type 49 | -------------------------------------------------------------------------------- /src/generators/powerbi/templates/source/{{Client}}.{{Package}}.PowerBI/scripts/UpdateParameters.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | PowerBi_Tool 4 | 5 | .DESCRIPTION 6 | This script is used to call function defined in .PSM1 file 7 | 8 | .PARAMETERS 9 | $BuildSourcesDirectory: Path to PowerShell module which is responsible for the interactions with Power BI Rest APIs. Can be selected from artifacts folder 10 | $checkModule : List of Modules that should be imported to run the script successfully. In this case you only need “MicrosoftPowerBIMgmt” 11 | $ClientID : Application Id of the app registration created as part of pre-requisites 12 | $ClientSecret : Secret that was added to this app registration as part of pre-requisites 13 | $TenantId : Tenant Id of the app registration 14 | $WorkSpaceName : target Workspace name 15 | $Dataset_Input : Name of the report/dataset to update parameters for 16 | $Api_URL : always set this parameter to 'https://api.powerbi.com/v1.0/myorg' 17 | $ParameterCollection > json array of parameter names and values to be updated. i.e. '[{"ParamName": "Name of the parameter", "ParamValue": "new value of the parameter"}, {"ParamName": "Name of the parameter 2", "ParamValue": "new value of the parameter 2"}]' 18 | #> 19 | 20 | [CmdletBinding()] 21 | param( 22 | [Parameter(Mandatory=$true)][string]$BuildSourcesDirectory, 23 | [parameter(Mandatory=$true)]$checkModule, 24 | [Parameter(Mandatory=$true)]$ClientID, 25 | [Parameter(Mandatory=$true)]$ClientSecret, 26 | [Parameter(Mandatory=$true)]$TenantId, 27 | [Parameter(Mandatory=$true)]$WorkSpaceName, 28 | [Parameter(Mandatory=$true)]$Dataset_Input, 29 | [Parameter(Mandatory=$true)]$Api_URL, 30 | [Parameter(Mandatory=$true)]$ParameterCollection 31 | 32 | ) 33 | 34 | Write-Host "Path of Release Directory :$BuildSourcesDirectory." 35 | # Importing UserCreationProcess.psm1 module. 36 | Import-Module "$BuildSourcesDirectory" 37 | Write-Host "Importing Module :$checkModule." 38 | # Importing Module ie. Active Directory . 39 | ModuleToImport -checkModule $checkModule 40 | 41 | Update_Parameter -ClientID $ClientID -ClientSecret $ClientSecret -TenantId $TenantId -WorkSpaceName $WorkSpaceName -Dataset_Input $Dataset_Input -Api_URL $Api_URL -ParameterCollection $ParameterCollection -------------------------------------------------------------------------------- /src/generators/powerbi/templates/source/{{Client}}.{{Package}}.PowerBI/scripts/UploadReport.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | PowerBi_Tool 4 | 5 | .DESCRIPTION 6 | This script is used to call function defined in .PSM1 file 7 | 8 | .PARAMETERS 9 | $BuildSourcesDirectory: Path to PowerShell module which is responsible for the interactions with Power BI Rest APIs. Can be selected from artifacts folder 10 | $checkModule : List of Modules that should be imported to run the script successfully. In this case you only need “MicrosoftPowerBIMgmt” 11 | $ClientID : Application Id of the app registration created as part of pre-requisites 12 | $ClientSecret : Secret that was added to this app registration as part of pre-requisites 13 | $TenantId : Tenant Id of the app registration 14 | $WorkSpaceName : target Workspace name 15 | $Path : Path of the '.pbix' file to import or override 16 | $ConflictAction : Determines what to do if dataset with the same name already exists. Create or Overwrite , Create , Overwrite 17 | #> 18 | 19 | [CmdletBinding()] 20 | param( 21 | [Parameter(Mandatory=$true)][string]$BuildSourcesDirectory, 22 | [parameter(Mandatory=$true)]$checkModule, 23 | [Parameter(Mandatory=$true)]$ClientID, 24 | [Parameter(Mandatory=$true)]$ClientSecret, 25 | [Parameter(Mandatory=$true)]$TenantId, 26 | [Parameter(Mandatory=$true)]$WorkSpaceName, 27 | [Parameter(Mandatory=$true)]$Path, 28 | [Parameter(Mandatory=$true)]$ConflictAction 29 | ) 30 | 31 | Write-Host "Path of Release Directory :$BuildSourcesDirectory." 32 | # Importing UserCreationProcess.psm1 module. 33 | Import-Module "$BuildSourcesDirectory" 34 | Write-Host "Importing Module :$checkModule." 35 | # Importing Module ie. Active Directory . 36 | ModuleToImport -checkModule $checkModule 37 | 38 | #Upload PowerBI Report(pbix) to PowerBI workspace 39 | New_PBIReport -Path $path -ConflictAction $conflictaction 40 | 41 | -------------------------------------------------------------------------------- /src/generators/powerbi/templates/source/{{Client}}.{{Package}}.PowerBI/{{Client}}.{{Package}}.PowerBI.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net462 5 | Debug;Release;Test 6 | 2.0.0 7 | 8 | 9 | 10 | Always 11 | 12 | 13 | Always 14 | 15 | 16 | Always 17 | 18 | 19 | Always 20 | 21 | 22 | Always 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/generators/scripts/index.ts: -------------------------------------------------------------------------------- 1 | import inquirer from 'inquirer'; 2 | import Generator from 'yeoman-generator'; 3 | import MappingFileTransformer from '../../common/MappingFileTransformer'; 4 | import PackageReader from '../../common/PackageReader'; 5 | 6 | class Main extends Generator { 7 | private readonly packageReader: PackageReader; 8 | 9 | private readonly mappingFileTransformer: MappingFileTransformer; 10 | 11 | private answers!: inquirer.Answers; 12 | 13 | constructor(args: string | string[], opts: {}) { 14 | super(args, opts); 15 | 16 | this.packageReader = new PackageReader(this.destinationPath()); 17 | this.mappingFileTransformer = new MappingFileTransformer(this.fs); 18 | } 19 | 20 | public async prompting(): Promise { 21 | this.answers = await this.prompt([ 22 | { 23 | choices: () => this.packageReader.getSolutions(), 24 | message: 'Name of the solution?', 25 | name: 'sourceSolution', 26 | store: true, 27 | type: 'list', 28 | }, 29 | ]); 30 | } 31 | 32 | public writing(): void { 33 | this.writeSource(); 34 | this.updateMappingFile(); 35 | } 36 | 37 | private updateMappingFile = async (): Promise => { 38 | const packMappingFilePath = this.destinationPath('src', 'solutions', this.answers.sourceSolution, 'PackMappingFile.xml'); 39 | const templatePackMappingFilePath = this.templatePath('PackMappingFile.xml'); 40 | 41 | this.mappingFileTransformer.transform( 42 | templatePackMappingFilePath, 43 | this.answers, 44 | packMappingFilePath, 45 | ); 46 | 47 | const ExtractMappingFilePath = this.destinationPath('src', 'solutions', this.answers.sourceSolution, 'ExtractMappingFile.xml'); 48 | const templateExtractMappingFilePath = this.templatePath('ExtractMappingFile.xml'); 49 | 50 | this.mappingFileTransformer.transform( 51 | templateExtractMappingFilePath, 52 | this.answers, 53 | ExtractMappingFilePath, 54 | ); 55 | }; 56 | 57 | private writeSource = () => { 58 | this.log('Writing solution from template...'); 59 | this.fs.copyTpl( 60 | this.templatePath('source'), 61 | this.destinationPath('src', 'solutions', this.answers.sourceSolution), 62 | this.answers, 63 | {}, 64 | { globOptions: { dot: true } }, 65 | ); 66 | }; 67 | } 68 | 69 | module.exports = Main; 70 | -------------------------------------------------------------------------------- /src/generators/scripts/templates/ExtractMappingFile.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/generators/scripts/templates/PackMappingFile.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/generators/scripts/templates/source/webresources/scripts/.npmrc: -------------------------------------------------------------------------------- 1 | @capgemini:registry=https://capgeminiuk.pkgs.visualstudio.com/_packaging/CapgeminiIp/npm/registry/ 2 | @capgemini:always-auth=true -------------------------------------------------------------------------------- /src/generators/scripts/templates/source/webresources/scripts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "build": "tsc" 4 | }, 5 | "dependencies": {}, 6 | "devDependencies": { 7 | "tslint": "^6.1.2", 8 | "tslint-config-airbnb": "^5.11.2", 9 | "tslint-config-prettier": "^1.18.0", 10 | "typescript": "^3.9.5" 11 | }, 12 | "-vs-binding": { 13 | "BeforeBuild": [ 14 | "install" 15 | ], 16 | "AfterBuild": [ 17 | "build" 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/generators/scripts/templates/source/webresources/scripts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "none", 4 | "moduleResolution": "node", 5 | "target": "es5", 6 | "outDir": "dist/", 7 | "rootDir": "src/", 8 | "sourceMap": true, 9 | "lib": [ 10 | "dom", 11 | "es5", 12 | "es2015.promise", 13 | "es2015.iterable" 14 | ] 15 | }, 16 | "include": [ 17 | "src/" 18 | ], 19 | "references": [] 20 | } -------------------------------------------------------------------------------- /src/generators/scripts/templates/source/webresources/scripts/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": ["tslint-config-airbnb", "tslint-config-prettier"], 4 | "jsRules": {}, 5 | "rules": { 6 | "no-namespace": false 7 | }, 8 | "rulesDirectory": [] 9 | } 10 | -------------------------------------------------------------------------------- /src/generators/scripts/templates/spkl.json: -------------------------------------------------------------------------------- 1 | { 2 | "webresources": [] 3 | } 4 | -------------------------------------------------------------------------------- /src/generators/solution/templates/source/{{solutionUniqueName}}/ExtractMappingFile.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/generators/solution/templates/source/{{solutionUniqueName}}/PackMappingFile.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/generators/solution/templates/source/{{solutionUniqueName}}/spkl.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [] 3 | } -------------------------------------------------------------------------------- /src/generators/solution/templates/source/{{solutionUniqueName}}/{{solutionUniqueName}}.cdsproj: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | $(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\PowerApps 6 | $(MSBuildThisFileDirectory)\PackMappingFile.xml 7 | 8 | 9 | 10 | 11 | 12 | 13 | 63d3663a-0ab3-43cc-b0f3-cbdd6d869e19 14 | v4.6.2 15 | 16 | net462 17 | PackageReference 18 | Extract 19 | 20 | 21 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | PreserveNewest 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["./src", "./typings", "./__tests__", "./.eslintrc.js"] 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2018", 4 | "module": "commonjs", 5 | "outDir": "./", 6 | "rootDir": "./src", 7 | "strict": true, 8 | "esModuleInterop": true, 9 | "sourceMap": true, 10 | "resolveJsonModule": true, 11 | "types": ["node"], 12 | }, 13 | "include": ["./src", "./typings"], 14 | "exclude": ["node_modules"] 15 | } 16 | -------------------------------------------------------------------------------- /typings/renamer.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'renamer' { 2 | export interface RenameOptions { 3 | files: string[]; 4 | find: string; 5 | replace: string; 6 | dryRun: boolean; 7 | } 8 | export default class Renamer { 9 | public rename(options: RenameOptions): void; 10 | } 11 | } 12 | --------------------------------------------------------------------------------