diff --git a/.github/workflows/auto-pr.yml b/.github/workflows/auto-pr.yml index 80d0acd30f..ce25006bf3 100644 --- a/.github/workflows/auto-pr.yml +++ b/.github/workflows/auto-pr.yml @@ -26,7 +26,6 @@ jobs: branch: auto-merge/rel-10-0/${{github.run_number}} title: Merge branch rel-10.1 with rel-10.0 body: This PR generated automatically to merge rel-10.1 with rel-10.0. Please review the changed files before merging to prevent any errors that may occur. - reviewers: maliming draft: true token: ${{ github.token }} - name: Merge Pull Request diff --git a/Directory.Packages.props b/Directory.Packages.props index 85a81516b0..586b62ddbb 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -140,7 +140,7 @@ - + diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index cf5c56f6ed..0c88043465 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -347,6 +347,10 @@ { "text": "Working with ABP Suite", "path": "studio/working-with-suite.md" + }, + { + "text": "Custom Commands", + "path": "studio/custom-commands.md" } ] }, @@ -1276,7 +1280,7 @@ }, { "text": "LeptonX Lite", - "path": "ui-themes/lepton-x-lite/mvc.md" + "path": "ui-themes/lepton-x-lite/asp-net-core.md" }, { "text": "LeptonX", diff --git a/docs/en/get-started/images/abp-studio-background-tasks.png b/docs/en/get-started/images/abp-studio-background-tasks.png index cf63394600..c3d020a90f 100644 Binary files a/docs/en/get-started/images/abp-studio-background-tasks.png and b/docs/en/get-started/images/abp-studio-background-tasks.png differ diff --git a/docs/en/get-started/images/abp-studio-created-microservice-solution-explorer.png b/docs/en/get-started/images/abp-studio-created-microservice-solution-explorer.png index 97936d018e..398be32048 100644 Binary files a/docs/en/get-started/images/abp-studio-created-microservice-solution-explorer.png and b/docs/en/get-started/images/abp-studio-created-microservice-solution-explorer.png differ diff --git a/docs/en/get-started/images/abp-studio-created-new-microservice-solution.png b/docs/en/get-started/images/abp-studio-created-new-microservice-solution.png index 7b13736509..28fa473185 100644 Binary files a/docs/en/get-started/images/abp-studio-created-new-microservice-solution.png and b/docs/en/get-started/images/abp-studio-created-new-microservice-solution.png differ diff --git a/docs/en/get-started/images/abp-studio-microservice-kubernetes-build-docker-images.png b/docs/en/get-started/images/abp-studio-microservice-kubernetes-build-docker-images.png index b2c3115f4b..a4730d1f61 100644 Binary files a/docs/en/get-started/images/abp-studio-microservice-kubernetes-build-docker-images.png and b/docs/en/get-started/images/abp-studio-microservice-kubernetes-build-docker-images.png differ diff --git a/docs/en/get-started/images/abp-studio-microservice-kubernetes-install-helm-chart.png b/docs/en/get-started/images/abp-studio-microservice-kubernetes-install-helm-chart.png index c4e4f6787f..a4f65d9214 100644 Binary files a/docs/en/get-started/images/abp-studio-microservice-kubernetes-install-helm-chart.png and b/docs/en/get-started/images/abp-studio-microservice-kubernetes-install-helm-chart.png differ diff --git a/docs/en/get-started/images/abp-studio-microservice-kubernetes-tab.png b/docs/en/get-started/images/abp-studio-microservice-kubernetes-tab.png index 16ff718210..400a4d1789 100644 Binary files a/docs/en/get-started/images/abp-studio-microservice-kubernetes-tab.png and b/docs/en/get-started/images/abp-studio-microservice-kubernetes-tab.png differ diff --git a/docs/en/get-started/images/abp-studio-microservice-solution-runner-applications.png b/docs/en/get-started/images/abp-studio-microservice-solution-runner-applications.png index 2bd1a2fe40..497ad5d065 100644 Binary files a/docs/en/get-started/images/abp-studio-microservice-solution-runner-applications.png and b/docs/en/get-started/images/abp-studio-microservice-solution-runner-applications.png differ diff --git a/docs/en/get-started/images/abp-studio-microservice-solution-runner-browse-microservice.png b/docs/en/get-started/images/abp-studio-microservice-solution-runner-browse-microservice.png index 6b15ce18b3..49abf55666 100644 Binary files a/docs/en/get-started/images/abp-studio-microservice-solution-runner-browse-microservice.png and b/docs/en/get-started/images/abp-studio-microservice-solution-runner-browse-microservice.png differ diff --git a/docs/en/get-started/images/abp-studio-microservice-solution-runner-browse.png b/docs/en/get-started/images/abp-studio-microservice-solution-runner-browse.png index da82711582..b633385243 100644 Binary files a/docs/en/get-started/images/abp-studio-microservice-solution-runner-browse.png and b/docs/en/get-started/images/abp-studio-microservice-solution-runner-browse.png differ diff --git a/docs/en/get-started/images/abp-studio-microservice-solution-runner-enable-watch-1.png b/docs/en/get-started/images/abp-studio-microservice-solution-runner-enable-watch-1.png index 1d953a9552..aba03aedf4 100644 Binary files a/docs/en/get-started/images/abp-studio-microservice-solution-runner-enable-watch-1.png and b/docs/en/get-started/images/abp-studio-microservice-solution-runner-enable-watch-1.png differ diff --git a/docs/en/get-started/images/abp-studio-microservice-solution-runner-enable-watch-2.png b/docs/en/get-started/images/abp-studio-microservice-solution-runner-enable-watch-2.png index 53152f1243..d3275453ab 100644 Binary files a/docs/en/get-started/images/abp-studio-microservice-solution-runner-enable-watch-2.png and b/docs/en/get-started/images/abp-studio-microservice-solution-runner-enable-watch-2.png differ diff --git a/docs/en/get-started/images/abp-studio-microservice-solution-runner-external-service.png b/docs/en/get-started/images/abp-studio-microservice-solution-runner-external-service.png index 35fc51899e..d02ac37d39 100644 Binary files a/docs/en/get-started/images/abp-studio-microservice-solution-runner-external-service.png and b/docs/en/get-started/images/abp-studio-microservice-solution-runner-external-service.png differ diff --git a/docs/en/get-started/images/abp-studio-microservice-solution-runner-watch-enabled-icon.png b/docs/en/get-started/images/abp-studio-microservice-solution-runner-watch-enabled-icon.png index 796d183ddc..d3539efffb 100644 Binary files a/docs/en/get-started/images/abp-studio-microservice-solution-runner-watch-enabled-icon.png and b/docs/en/get-started/images/abp-studio-microservice-solution-runner-watch-enabled-icon.png differ diff --git a/docs/en/get-started/images/abp-studio-microservice-solution-runner.png b/docs/en/get-started/images/abp-studio-microservice-solution-runner.png index 3e06813a46..4a856a62e5 100644 Binary files a/docs/en/get-started/images/abp-studio-microservice-solution-runner.png and b/docs/en/get-started/images/abp-studio-microservice-solution-runner.png differ diff --git a/docs/en/get-started/images/abp-studio-new-microservice-helm-charts.png b/docs/en/get-started/images/abp-studio-new-microservice-helm-charts.png index 42009900b3..de4476a9f1 100644 Binary files a/docs/en/get-started/images/abp-studio-new-microservice-helm-charts.png and b/docs/en/get-started/images/abp-studio-new-microservice-helm-charts.png differ diff --git a/docs/en/get-started/images/abp-studio-new-microservice-solution-dialog-optional-modules.png b/docs/en/get-started/images/abp-studio-new-microservice-solution-dialog-optional-modules.png index 3b1a4abe68..0ecc98f3ee 100644 Binary files a/docs/en/get-started/images/abp-studio-new-microservice-solution-dialog-optional-modules.png and b/docs/en/get-started/images/abp-studio-new-microservice-solution-dialog-optional-modules.png differ diff --git a/docs/en/get-started/images/abp-studio-new-microservice-solution-dialog-properties.png b/docs/en/get-started/images/abp-studio-new-microservice-solution-dialog-properties.png index 5f2a9b7938..13d0e679a8 100644 Binary files a/docs/en/get-started/images/abp-studio-new-microservice-solution-dialog-properties.png and b/docs/en/get-started/images/abp-studio-new-microservice-solution-dialog-properties.png differ diff --git a/docs/en/get-started/images/abp-studio-new-solution-dialog-additional-options-microservice.png b/docs/en/get-started/images/abp-studio-new-solution-dialog-additional-options-microservice.png index cb85c3a2b8..ae89059631 100644 Binary files a/docs/en/get-started/images/abp-studio-new-solution-dialog-additional-options-microservice.png and b/docs/en/get-started/images/abp-studio-new-solution-dialog-additional-options-microservice.png differ diff --git a/docs/en/get-started/images/abp-studio-new-solution-dialog-additional-services.png b/docs/en/get-started/images/abp-studio-new-solution-dialog-additional-services.png index 6bbf3fb22a..40f2037ac5 100644 Binary files a/docs/en/get-started/images/abp-studio-new-solution-dialog-additional-services.png and b/docs/en/get-started/images/abp-studio-new-solution-dialog-additional-services.png differ diff --git a/docs/en/get-started/images/abp-studio-new-solution-dialog-admin-password.png b/docs/en/get-started/images/abp-studio-new-solution-dialog-admin-password.png new file mode 100644 index 0000000000..8040b491a3 Binary files /dev/null and b/docs/en/get-started/images/abp-studio-new-solution-dialog-admin-password.png differ diff --git a/docs/en/get-started/images/abp-studio-new-solution-dialog-aspire-configuration-microservice.png b/docs/en/get-started/images/abp-studio-new-solution-dialog-aspire-configuration-microservice.png index 74cd5066bb..507fdf0a7f 100644 Binary files a/docs/en/get-started/images/abp-studio-new-solution-dialog-aspire-configuration-microservice.png and b/docs/en/get-started/images/abp-studio-new-solution-dialog-aspire-configuration-microservice.png differ diff --git a/docs/en/get-started/images/abp-studio-new-solution-dialog-database-configurations-microservice.png b/docs/en/get-started/images/abp-studio-new-solution-dialog-database-configurations-microservice.png index e41580ad1e..12f0003333 100644 Binary files a/docs/en/get-started/images/abp-studio-new-solution-dialog-database-configurations-microservice.png and b/docs/en/get-started/images/abp-studio-new-solution-dialog-database-configurations-microservice.png differ diff --git a/docs/en/get-started/images/abp-studio-new-solution-dialog-database-provider-microservice.png b/docs/en/get-started/images/abp-studio-new-solution-dialog-database-provider-microservice.png index ed52bac62c..97255f2464 100644 Binary files a/docs/en/get-started/images/abp-studio-new-solution-dialog-database-provider-microservice.png and b/docs/en/get-started/images/abp-studio-new-solution-dialog-database-provider-microservice.png differ diff --git a/docs/en/get-started/images/abp-studio-new-solution-dialog-dynamic-localization.png b/docs/en/get-started/images/abp-studio-new-solution-dialog-dynamic-localization.png index 455a6bd574..3564685c5c 100644 Binary files a/docs/en/get-started/images/abp-studio-new-solution-dialog-dynamic-localization.png and b/docs/en/get-started/images/abp-studio-new-solution-dialog-dynamic-localization.png differ diff --git a/docs/en/get-started/images/abp-studio-new-solution-dialog-languages-microservice.png b/docs/en/get-started/images/abp-studio-new-solution-dialog-languages-microservice.png index 705fee9325..1797a4553c 100644 Binary files a/docs/en/get-started/images/abp-studio-new-solution-dialog-languages-microservice.png and b/docs/en/get-started/images/abp-studio-new-solution-dialog-languages-microservice.png differ diff --git a/docs/en/get-started/images/abp-studio-new-solution-dialog-microservice.png b/docs/en/get-started/images/abp-studio-new-solution-dialog-microservice.png index 9b9210b269..d1ec05e4c8 100644 Binary files a/docs/en/get-started/images/abp-studio-new-solution-dialog-microservice.png and b/docs/en/get-started/images/abp-studio-new-solution-dialog-microservice.png differ diff --git a/docs/en/get-started/images/abp-studio-new-solution-dialog-mobile-framework-microservice.png b/docs/en/get-started/images/abp-studio-new-solution-dialog-mobile-framework-microservice.png index 04e1af7ebe..004a473579 100644 Binary files a/docs/en/get-started/images/abp-studio-new-solution-dialog-mobile-framework-microservice.png and b/docs/en/get-started/images/abp-studio-new-solution-dialog-mobile-framework-microservice.png differ diff --git a/docs/en/get-started/images/abp-studio-new-solution-dialog-multi-tenancy.png b/docs/en/get-started/images/abp-studio-new-solution-dialog-multi-tenancy.png index 3c06ae001d..3311e76a76 100644 Binary files a/docs/en/get-started/images/abp-studio-new-solution-dialog-multi-tenancy.png and b/docs/en/get-started/images/abp-studio-new-solution-dialog-multi-tenancy.png differ diff --git a/docs/en/get-started/images/abp-studio-new-solution-dialog-public-web-site.png b/docs/en/get-started/images/abp-studio-new-solution-dialog-public-web-site.png index 40325a5358..edad8a63db 100644 Binary files a/docs/en/get-started/images/abp-studio-new-solution-dialog-public-web-site.png and b/docs/en/get-started/images/abp-studio-new-solution-dialog-public-web-site.png differ diff --git a/docs/en/get-started/images/abp-studio-new-solution-dialog-ui-framework-microservice.png b/docs/en/get-started/images/abp-studio-new-solution-dialog-ui-framework-microservice.png index 27e39f3776..08e30001e6 100644 Binary files a/docs/en/get-started/images/abp-studio-new-solution-dialog-ui-framework-microservice.png and b/docs/en/get-started/images/abp-studio-new-solution-dialog-ui-framework-microservice.png differ diff --git a/docs/en/get-started/images/abp-studio-new-solution-dialog-ui-theme-microservice.png b/docs/en/get-started/images/abp-studio-new-solution-dialog-ui-theme-microservice.png index 53692d03de..8755073d1d 100644 Binary files a/docs/en/get-started/images/abp-studio-new-solution-dialog-ui-theme-microservice.png and b/docs/en/get-started/images/abp-studio-new-solution-dialog-ui-theme-microservice.png differ diff --git a/docs/en/get-started/images/abp-studio-open-module-folder.png b/docs/en/get-started/images/abp-studio-open-module-folder.png index b32f046d94..d945377143 100644 Binary files a/docs/en/get-started/images/abp-studio-open-module-folder.png and b/docs/en/get-started/images/abp-studio-open-module-folder.png differ diff --git a/docs/en/get-started/images/abp-studio-welcome-screen.png b/docs/en/get-started/images/abp-studio-welcome-screen.png index 938be4a010..0607762677 100644 Binary files a/docs/en/get-started/images/abp-studio-welcome-screen.png and b/docs/en/get-started/images/abp-studio-welcome-screen.png differ diff --git a/docs/en/get-started/microservice.md b/docs/en/get-started/microservice.md index 56f275c258..d30e3926fc 100644 --- a/docs/en/get-started/microservice.md +++ b/docs/en/get-started/microservice.md @@ -126,6 +126,12 @@ Click the Next button to see *Additional Services* screen: On that screen, allows you to include extra microservices in your ABP solution during the creation process. This feature lets you extend your solution with business-specific services right from the start. +Click the Next button to see *Admin Password* screen: + +![abp-studio-new-solution-dialog-admin-password](images/abp-studio-new-solution-dialog-admin-password.png) + +Here, you can set the initial password for the `admin` user of your application. By default, it is set to `1q2w3E*`, but you can change it to a more secure password of your choice. + Now, we are ready to allow ABP Studio to create our solution. Just click the *Create* button and let the ABP Studio do the rest for you. After clicking the *Create* button, the dialog is closed and your solution is loaded into ABP Studio: ![abp-studio-created-new-microservice-solution](images/abp-studio-created-new-microservice-solution.png) diff --git a/docs/en/studio/custom-commands.md b/docs/en/studio/custom-commands.md new file mode 100644 index 0000000000..af56492f37 --- /dev/null +++ b/docs/en/studio/custom-commands.md @@ -0,0 +1,128 @@ +```json +//[doc-seo] +{ + "Description": "Learn how to create and manage custom commands in ABP Studio to automate build, deployment, and other workflows." +} +``` + +# Custom Commands + +````json +//[doc-nav] +{ + "Next": { + "Name": "Working with ABP Suite", + "Path": "studio/working-with-suite" + } +} +```` + +Custom commands allow you to define reusable terminal commands that appear in context menus throughout ABP Studio. You can use them to automate repetitive tasks such as building Docker images, installing Helm charts, running deployment scripts, or executing any custom workflow. + +> **Note:** This is an advanced feature primarily intended for teams working with Kubernetes deployments or complex build/deployment workflows. If you're developing a standard application without custom DevOps requirements, you may not need this feature. + +## Opening the Management Window + +To manage custom commands, right-click on the solution root in *Solution Explorer* and select *Manage Custom Commands*. + +![Custom Commands Management Window](images/custom-commands/management-window.png) + +The management window displays all defined commands with options to add, edit, or delete them. + +## Creating a New Command + +Click the *Add New Command* button to open the command editor dialog. + +![Create/Edit Custom Command](images/custom-commands/create-edit-command.png) + +## Command Properties + +| Property | Description | +|----------|-------------| +| **Command Name** | A unique identifier for the command (used internally) | +| **Display Name** | The text shown in context menus | +| **Terminal Command** | The PowerShell command to execute. Use `&&&` to chain multiple commands | +| **Working Directory** | Optional. The directory where the command runs (relative to solution path) | +| **Condition** | Optional. A [Scriban](https://github.com/scriban/scriban/blob/master/doc/language.md) expression that determines when the command is visible | +| **Require Confirmation** | When enabled, shows a confirmation dialog before execution | +| **Confirmation Text** | The message shown in the confirmation dialog | + +## Trigger Targets + +Trigger targets determine where your command appears in context menus. You can select multiple targets for a single command. + +| Target | Location | +|--------|----------| +| **Helm Charts Root** | *Kubernetes* panel > *Helm* tab > root node | +| **Helm Main Chart** | *Kubernetes* panel > *Helm* tab > main chart | +| **Helm Sub Chart** | *Kubernetes* panel > *Helm* tab > sub chart | +| **Kubernetes Service** | *Kubernetes* panel > *Kubernetes* tab > service | +| **Solution Runner Root** | *Solution Runner* panel > profile root | +| **Solution Runner Folder** | *Solution Runner* panel > folder | +| **Solution Runner Application** | *Solution Runner* panel > application | + +## Execution Targets + +Execution targets define where the command actually runs. This enables cascading execution: + +- When you trigger a command from a **root or parent item**, it can recursively execute on all matching children +- For example: trigger from *Helm Charts Root* with execution target *Helm Sub Chart* → the command runs on each sub chart + +## Template Variables + +Commands support [Scriban](https://github.com/scriban/scriban/blob/master/doc/language.md) template syntax for dynamic values. Use `{%{{{variable}}}%}` to insert context-specific data. + +### Available Variables by Context + +**Helm Charts:** + +| Variable | Description | +|----------|-------------| +| `profile.name` | Kubernetes profile name | +| `profile.namespace` | Kubernetes namespace | +| `chart.name` | Current chart name | +| `chart.path` | Chart directory path | +| `metadata.*` | Hierarchical metadata values (e.g., `metadata.imageName`) | +| `secrets.*` | Secret values (e.g., `secrets.registryPassword`) | + +**Kubernetes Service:** + +| Variable | Description | +|----------|-------------| +| `name` | Service name | +| `profile.name` | Kubernetes profile name | +| `profile.namespace` | Kubernetes namespace | +| `mainChart.name` | Parent main chart name | +| `chart.name` | Related sub chart name | +| `chart.metadata.*` | Chart-specific metadata | + +**Solution Runner (Root, Folder, Application):** + +| Variable | Description | +|----------|-------------| +| `profile.name` | Run profile name | +| `profile.path` | Profile file path | +| `application.name` | Application name (Application context only) | +| `application.baseUrl` | Application URL (Application context only) | +| `folder.name` | Folder name (Folder/Application context) | +| `metadata.*` | Profile metadata values | +| `secrets.*` | Profile secret values | + +## Example: Build Docker Image + +Here's an example command that builds a Docker image for Helm charts: + +**Command Properties:** +- **Command Name:** `buildDockerImage` +- **Display Name:** `Build Docker Image` +- **Terminal Command:** `./build-image.ps1 -ProjectPath {%{{{metadata.projectPath}}}%} -ImageName {%{{{metadata.imageName}}}%}` +- **Working Directory:** `etc/helm` +- **Trigger Targets:** Helm Charts Root, Helm Main Chart, Helm Sub Chart +- **Execution Targets:** Helm Main Chart, Helm Sub Chart +- **Condition:** `{%{{{metadata.projectPath}}}%}` + +This command: +1. Appears in the context menu of Helm charts root and all chart nodes +2. Executes on main charts and sub charts (cascading from root if triggered there) +3. Only shows for charts that have `projectPath` metadata defined +4. Runs the `build-image.ps1` script with dynamic parameters from metadata diff --git a/docs/en/studio/images/custom-commands/create-edit-command.png b/docs/en/studio/images/custom-commands/create-edit-command.png new file mode 100644 index 0000000000..7091516a8f Binary files /dev/null and b/docs/en/studio/images/custom-commands/create-edit-command.png differ diff --git a/docs/en/studio/images/custom-commands/management-window.png b/docs/en/studio/images/custom-commands/management-window.png new file mode 100644 index 0000000000..3a24ac8dc4 Binary files /dev/null and b/docs/en/studio/images/custom-commands/management-window.png differ diff --git a/docs/en/studio/kubernetes.md b/docs/en/studio/kubernetes.md index 758a7cf55c..eca8d83c5b 100644 --- a/docs/en/studio/kubernetes.md +++ b/docs/en/studio/kubernetes.md @@ -11,8 +11,8 @@ //[doc-nav] { "Next": { - "Name": "Working with ABP Suite", - "Path": "studio/working-with-suite" + "Name": "Custom Commands", + "Path": "studio/custom-commands" } } ```` @@ -210,46 +210,12 @@ When you connect to a Kubernetes cluster, it uses the selected profile for Kuber ## Advanced Topics -### Adding a Custom Command - -Custom commands can be added to both the *Helm* and *Kubernetes* tabs within the *Kubernetes* panel. For instance, when [redeploy](#redeploy-a-chart) a chart, it involves building the Docker image and reinstalling it. However, if you are working with a different Kubernetes cluster than Docker Desktop, you'll need to push the Docker image to the registry before the installation process. This can be achieved by incorporating a custom command into the *Kubernetes services*. Custom commands can be added to the *Chart Root*, *Main Chart*, and *Subchart* in the *Helm* tab, as well as to the *Service* in the *Kubernetes* tab. - -To do that, open the ABP Solution (*.abpsln*) file with *Visual Studio Code* it's a JSON file and you'll see the existing commands in the `commands` section. Before adding a new command, create a powershell script in the `abp-solution-path/etc/helm` folder. For example, we create a `push-image.ps1` script to push the docker image to the registry. Then, add the following command to the `commands` section. - -```JSON - "kubernetesRedeployWithPushImage": { - "triggerTargets": [ - "KUBERNETES_SERVICE" - ], - "executionTargets": [ - "KUBERNETES_SERVICE" - ], - "displayName": " Redeploy with Push Image", - "workingDirectory": "etc/helm", - "terminalCommand": "./build-image.ps1 -ProjectPath {%{{{chart.metadata.projectPath}}}%} -ImageName {%{{{chart.metadata.imageName}}}%} -ProjectType {%{{{chart.metadata.projectType}}}%} &&& ./push-image.ps1 -ImageName {%{{{chart.metadata.imageName}}}%} &&& ./install.ps1 -ChartName {%{{{mainChart.name}}}%} -Namespace {%{{{profile.namespace}}}%} -ReleaseName {%{{{mainChart.name}}}%}-{%{{{profile.name}}}%} -DotnetEnvironment {%{{{mainChart.metadata.dotnetEnvironment}}}%}", - "requireConfirmation": "true", - "confirmationText": "Are you sure to redeploy with push image the related chart '{%{{{chart.name}}}%}' for the service '{%{{{name}}}%}'?", - "condition": "{%{{{chart != null && chart.metadata.projectPath != null && chart.metadata.imageName != null && chart.metadata.projectType != null}}}%}" - } -``` +### Custom Commands + +You can add custom commands to context menus in both the *Helm* and *Kubernetes* tabs. This is useful for automating workflows like pushing Docker images to a registry before installation, or running custom deployment scripts. -Once the command is added, reload the solution from *File* -> *Reload Solution* in the toolbar. After reloading, you will find the *Redeploy with Push Image* command in the context-menu of the service. +Custom commands can be added to the *Chart Root*, *Main Chart*, and *Sub Chart* in the *Helm* tab, as well as to the *Service* in the *Kubernetes* tab. ![redeploy-push-image](./images/kubernetes/redeploy-push-image.png) -The JSON object has the following properties: - -- `triggerTargets`: Specifies the trigger targets for the command. The added command will appear in these targets. You can add one or more trigger targets, accepting values such as *HELM_CHARTS_ROOT*, *HELM_MAIN_CHART*, *HELM_SUB_CHART* and *KUBERNETES_SERVICE*. -- `executionTargets`: Specifies the execution targets for the command. When executing the command on a root item, it will recursively execute the command for all children. Acceptable values include *HELM_CHARTS_ROOT*, *HELM_MAIN_CHART*, *HELM_SUB_CHART*, and *KUBERNETES_SERVICE*. -- `displayName`: Specifies the display name of the command. -- `workingDirectory`: Specifies the working directory of the command. It's relative to the solution path. -- `terminalCommand`: Specifies the terminal command for the custom command. The `&&&` operator can be used to run multiple commands in the terminal. Utilize the [Scriban](https://github.com/scriban/scriban/blob/master/doc/language.md) syntax to access input data, which varies based on the execution target. -- `requireConfirmation`: Specifies whether the command requires confirmation message before execution. Acceptable values include *true* and *false*. -- `confirmationText`: Specifies the confirmation text for the command. Utilize the [Scriban](https://github.com/scriban/scriban/blob/master/doc/language.md) syntax to access input data, which varies based on the execution target. -- `condition`: Specifies the condition for the command. If the condition returns *false*, it skips the current item and attempts to execute the command for the next item or child item. Utilize the [Scriban](https://github.com/scriban/scriban/blob/master/doc/language.md) syntax to access input data, which varies based on the execution target. - -You can use the following variables in the scriban syntax based on the execution target: - - `HELM_CHARTS_ROOT`: *profile*, *metadata*, *secrets* - - `HELM_MAIN_CHART`: *profile*, *chart*, *metadata*, *secret* - - `HELM_SUB_CHART`: *profile*, *chart*, *metadata*, *secret* - - `KUBERNETES_SERVICE`: *name*, *profile*, *mainChart*, *chart*, *metadata*, *secret* +For detailed information on creating and managing custom commands, see the [Custom Commands](custom-commands.md) documentation. diff --git a/docs/en/studio/running-applications.md b/docs/en/studio/running-applications.md index a36be39229..a46e137db4 100644 --- a/docs/en/studio/running-applications.md +++ b/docs/en/studio/running-applications.md @@ -250,7 +250,11 @@ The *Open with* submenu provides options to open the application project in exte - **Terminal**: Opens a terminal window in the project directory. - **Explorer / Finder**: Opens the project folder in the system file explorer. -#### Miscellaneous +### Custom Commands + +You can add custom commands that appear in the context menu of Solution Runner items (root, folders, and applications). These commands allow you to automate custom workflows and scripts. For details on creating and managing custom commands, see the [Custom Commands](custom-commands.md) documentation. + +### Miscellaneous - We can copy the selected application *Browse* URL with *Copy URL*. It copies the *Browse* URL instead of *Launch URL* since we could be connected to a *Kubernetes* service. - You can change the target framework by right-click the selected application and change the *Target Framework* option. This option visible if the project has multiple target framework such as MAUI applications. diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs index ae571cb5f9..6ffaa96ecf 100644 --- a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs @@ -19,7 +19,7 @@ namespace Volo.Abp.Caching; /// Represents a distributed cache of type. /// /// The type of cache item being cached. -public class DistributedCache : +public class DistributedCache : IDistributedCache where TCacheItem : class { @@ -683,35 +683,30 @@ public class DistributedCache : IDistributedCache x.Value != null)) - { - return result!; - } + var resultMap = result + .Where(x => x.Value != null) + .ToDictionary(x => x.Key, x => x.Value); - var missingKeys = new List(); - var missingValuesIndex = new List(); - for (var i = 0; i < keyArray.Length; i++) + if (resultMap.Count == keyArray.Length) { - if (result[i].Value != null) - { - continue; - } - - missingKeys.Add(keyArray[i]); - missingValuesIndex.Add(i); + return keyArray + .Select(key => new KeyValuePair(key, resultMap[key])) + .ToArray(); } + var missingKeys = keyArray.Where(key => !resultMap.ContainsKey(key)).ToList(); var missingValues = factory.Invoke(missingKeys).ToArray(); - var valueQueue = new Queue>(missingValues); SetMany(missingValues, optionsFactory?.Invoke(), hideErrors, considerUow); - foreach (var index in missingValuesIndex) + foreach (var pair in missingValues) { - result[index] = valueQueue.Dequeue()!; + resultMap[pair.Key] = pair.Value; } - return result; + return keyArray + .Select(key => new KeyValuePair(key, resultMap.GetOrDefault(key))) + .ToArray(); } @@ -779,35 +774,30 @@ public class DistributedCache : IDistributedCache x.Value != null)) - { - return result; - } + var resultMap = result + .Where(x => x.Value != null) + .ToDictionary(x => x.Key, x => x.Value); - var missingKeys = new List(); - var missingValuesIndex = new List(); - for (var i = 0; i < keyArray.Length; i++) + if (resultMap.Count == keyArray.Length) { - if (result[i].Value != null) - { - continue; - } - - missingKeys.Add(keyArray[i]); - missingValuesIndex.Add(i); + return keyArray + .Select(key => new KeyValuePair(key, resultMap[key])) + .ToArray(); } + var missingKeys = keyArray.Where(key => !resultMap.ContainsKey(key)).ToList(); var missingValues = (await factory.Invoke(missingKeys)).ToArray(); - var valueQueue = new Queue>(missingValues); await SetManyAsync(missingValues, optionsFactory?.Invoke(), hideErrors, considerUow, token); - foreach (var index in missingValuesIndex) + foreach (var pair in missingValues) { - result[index] = valueQueue.Dequeue()!; + resultMap[pair.Key] = pair.Value; } - return result; + return keyArray + .Select(key => new KeyValuePair(key, resultMap.GetOrDefault(key))) + .ToArray(); } /// diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Internal/RecreateInitialMigrationCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Internal/RecreateInitialMigrationCommand.cs index 857edf6bd7..5bbb521fc1 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Internal/RecreateInitialMigrationCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Internal/RecreateInitialMigrationCommand.cs @@ -53,6 +53,14 @@ public class RecreateInitialMigrationCommand : IConsoleCommand, ITransientDepend Directory.Delete(Path.Combine(projectDir, "TenantMigrations"), true); separateDbContext = true; } + + CmdHelper.RunCmd("dotnet build", workingDirectory: projectDir, exitCode: out var exitCode); + if (exitCode != 0) + { + Logger.LogError("Build failed for project {Project}. Skipping migration recreation.", csprojFile); + continue; + } + if (!separateDbContext) { CmdHelper.RunCmd($"dotnet ef migrations add Initial", workingDirectory: projectDir); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/InitialMigrationCreator.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/InitialMigrationCreator.cs index 860fcef60f..2288eeccf7 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/InitialMigrationCreator.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/InitialMigrationCreator.cs @@ -14,7 +14,7 @@ public class InitialMigrationCreator : ITransientDependency public ICmdHelper CmdHelper { get; } public DotnetEfToolManager DotnetEfToolManager { get; } public ILogger Logger { get; set; } - + public InitialMigrationCreator(ICmdHelper cmdHelper, DotnetEfToolManager dotnetEfToolManager) { CmdHelper = cmdHelper; @@ -30,11 +30,11 @@ public class InitialMigrationCreator : ITransientDependency Logger.LogError($"This path doesn't exist: {targetProjectFolder}"); return false; } - + Logger.LogInformation("Creating initial migrations..."); await DotnetEfToolManager.BeSureInstalledAsync(); - + var tenantDbContextName = FindTenantDbContextName(targetProjectFolder); var dbContextName = tenantDbContextName != null ? FindDbContextName(targetProjectFolder) @@ -60,7 +60,7 @@ public class InitialMigrationCreator : ITransientDependency return migrationSuccess; } - + private string FindTenantDbContextName(string projectFolder) { var tenantDbContext = Directory.GetFiles(projectFolder, "*TenantMigrationsDbContext.cs", SearchOption.AllDirectories) @@ -93,6 +93,12 @@ public class InitialMigrationCreator : ITransientDependency private string AddMigrationAndGetOutput(string dbMigrationsFolder, string dbContext, string outputDirectory) { + var output = CmdHelper.RunCmdAndGetOutput("dotnet build", out int buildExitCode, dbMigrationsFolder); + if (buildExitCode != 0) + { + return output; + } + var dbContextOption = string.IsNullOrWhiteSpace(dbContext) ? string.Empty : $"--context {dbContext}"; @@ -108,4 +114,4 @@ public class InitialMigrationCreator : ITransientDependency output.Contains("To undo this action") && output.Contains("ef migrations remove")); } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/EfCoreMigrationManager.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/EfCoreMigrationManager.cs index ffa6a50edf..c4f1181fa3 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/EfCoreMigrationManager.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/EfCoreMigrationManager.cs @@ -44,13 +44,20 @@ public class EfCoreMigrationManager : ITransientDependency string dbContext, string outputDirectory) { + CmdHelper.RunCmd($"dotnet build", workingDirectory: dbMigrationsProjectFolder, exitCode: out var buildExitCode); + if (buildExitCode != 0) + { + Logger.LogWarning("Dotnet build failed for project folder {ProjectFolder}. Skipping EF Core migration command.", dbMigrationsProjectFolder); + return; + } + var dbContextOption = string.IsNullOrWhiteSpace(dbContext) ? string.Empty : $"--context {dbContext}"; CmdHelper.RunCmd($"dotnet ef migrations add {migrationName}" + $" --output-dir {outputDirectory}" + - $" {dbContextOption}", + $" {dbContextOption}", workingDirectory: dbMigrationsProjectFolder); } diff --git a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs index c76f5be9ea..25cc4c9784 100644 --- a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs +++ b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Shouldly; using Volo.Abp.Testing; @@ -751,6 +752,74 @@ public class DistributedCache_Tests : AbpIntegratedTest cacheValue[1].Value.Name.ShouldBe("jack"); } + [Fact] + public async Task GetOrAddManyAsync_Should_Return_Values_By_Key_With_Uow() + { + var key1 = "testkey"; + var key2 = "testkey2"; + var keys = new[] { key1, key2 }; + + using (GetRequiredService().Begin()) + { + var personCache = GetRequiredService>(); + + await personCache.SetAsync(key2, new PersonCacheItem("cached"), considerUow: true); + + var result = await personCache.GetOrAddManyAsync(keys, missingKeys => + { + missingKeys.ToArray().ShouldBe(new[] { key1 }); + return Task.FromResult(new List> + { + new(key1, new PersonCacheItem("factory")) + }); + }, considerUow: true); + + result.Length.ShouldBe(2); + result[0].Key.ShouldBe(key1); + result[0].Value.ShouldNotBeNull(); + result[0].Value.Name.ShouldBe("factory"); + result[1].Key.ShouldBe(key2); + result[1].Value.ShouldNotBeNull(); + result[1].Value.Name.ShouldBe("cached"); + } + } + + [Fact] + public async Task GetOrAddManyAsync_Should_Map_By_Key_Under_Concurrency() + { + var key1 = "testkey"; + var key2 = "testkey2"; + var keys = new[] { key1, key2 }; + + var personCache = GetRequiredService>(); + + async Task>> Factory(IEnumerable missingKeys) + { + await Task.Yield(); + + return missingKeys + .Reverse() + .Select(x => new KeyValuePair(x, new PersonCacheItem(x == key1 ? "v1" : "v2"))) + .ToList(); + } + + var task1 = personCache.GetOrAddManyAsync(keys, Factory); + var task2 = personCache.GetOrAddManyAsync(keys, Factory); + + var results = await Task.WhenAll(task1, task2); + + foreach (var result in results) + { + result.Length.ShouldBe(2); + + result[0].Key.ShouldBe(key1); + result[0].Value!.Name.ShouldBe("v1"); + + result[1].Key.ShouldBe(key2); + result[1].Value!.Name.ShouldBe("v2"); + } + } + [Fact] public async Task Cache_Should_Only_Available_In_Uow_For_GetOrAddManyAsync() {