├── .gitattributes
├── .github
├── dependabot.yml
└── workflows
│ ├── dotnet.yml
│ └── dotnetcore-nuget.yml
├── .gitignore
├── .vscode
└── settings.json
├── AbpDevTools.sln
├── LICENSE.txt
├── README.md
├── art
├── logo.svg
├── logo_128.png
└── logo_512.png
├── images
├── abpdevbuild-interactive.gif
├── abpdevbuild.gif
├── abpdevenvapp.gif
├── abpdevlogs-clear.gif
├── abpdevlogs.gif
├── abpdevnotification.gif
├── abpdevprepare.gif
├── abpdevreplace.gif
├── abpdevrun-all.gif
├── abpdevrun-multiplesolutions.gif
└── abpdevrun.gif
├── install.ps1
├── pack.ps1
└── src
└── AbpDevTools
├── AbpDevTools.csproj
├── Commands
├── AbpBundleCommand.cs
├── AbpBundleListCommand.cs
├── BuildCommand.cs
├── CleanCommand.cs
├── ConfigurationClearCommand.cs
├── ConfigurationCommand.cs
├── DatabaseDropCommand.cs
├── DisableNotificationsCommand.cs
├── EnableNotificationsCommand.cs
├── EnvironmentAppCommand.cs
├── EnvironmentAppStartCommand.cs
├── EnvironmentAppStopCommand.cs
├── EnvironmentCommand.cs
├── EnvironmentConfigurationCommand.cs
├── FindFileCommand.cs
├── LogsClearCommand.cs
├── LogsCommand.cs
├── MigrateCommand.cs
├── Migrations
│ ├── AddMigrationCommand.cs
│ ├── ClearMigrationsCommand.cs
│ ├── MigrationsCommand.cs
│ └── MigrationsCommandBase.cs
├── PrepareCommand.cs
├── ReplaceCommand.cs
├── RunCommand.cs
├── RunningProjectItem.cs
├── SwitchToEnvironmentCommand.cs
├── TestCommand.cs
├── ToolsCommand.cs
└── UpdateCheckCommand.cs
├── Configuration
├── CleanConfiguration.cs
├── ConfigurationBase.cs
├── DictionaryConfigurationBase.cs
├── EnvironmentAppConfiguration.cs
├── EnvironmentConfiguration.cs
├── NotificationConfiguration.cs
├── ReplacementConfiguration.cs
├── ReplacementOption.cs
├── RunConfiguration.cs
└── ToolsConfiguration.cs
├── DotnetDependencyResolver.cs
├── Environments
├── AppEnvironmentMapping.cs
├── IProcessEnvironmentManager.cs
└── ProcessEnvironmentManager.cs
├── FileExplorer.cs
├── Global.usings.cs
├── LocalConfigurations
├── LocalConfiguration.cs
└── LocalConfigurationManager.cs
├── Notifications
├── DefaultNotificationManager.cs
├── INotificationManager.cs
├── MacCatalystNotificationManager.cs
└── WindowsNotificationManager.cs
├── Platform.cs
├── Program.cs
├── Properties
└── launchSettings.json
└── Services
├── EntityFrameworkCoreProjectsProvider.cs
├── RunnableProjectsProvider.cs
├── UpdateCheckResult.cs
└── UpdateChecker.cs
/.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 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "" # See documentation for possible values
9 | directory: "/" # Location of package manifests
10 | schedule:
11 | interval: "weekly"
12 |
--------------------------------------------------------------------------------
/.github/workflows/dotnet.yml:
--------------------------------------------------------------------------------
1 | # This workflow will build a .NET project
2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net
3 |
4 | name: .NET
5 |
6 | on:
7 | push:
8 | branches: [ "master" ]
9 | pull_request:
10 | branches: [ "master" ]
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | steps:
18 | - uses: actions/checkout@v3
19 | - name: Setup .NET
20 | uses: actions/setup-dotnet@v3
21 | with:
22 | dotnet-version: 9.0.x
23 | - name: Restore dependencies
24 | run: dotnet restore
25 | - name: Build
26 | run: dotnet build --no-restore
27 | # - name: Test
28 | # run: dotnet test --no-build --verbosity normal
29 |
--------------------------------------------------------------------------------
/.github/workflows/dotnetcore-nuget.yml:
--------------------------------------------------------------------------------
1 | name: Nuget Publish Pipeline
2 |
3 | on: workflow_dispatch
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: ubuntu-latest
9 |
10 | steps:
11 | - uses: actions/checkout@v2
12 | - name: Setup .NET Core
13 | uses: actions/setup-dotnet@v1
14 | with:
15 | dotnet-version: 9.0.100
16 | - name: Restore
17 | run: dotnet restore
18 | - name: Pack
19 | run: dotnet pack ./src/AbpDevTools/AbpDevTools.csproj -c Release --include-symbols --include-source -o ./nupkg
20 | - name: Push
21 | run: dotnet nuget push './nupkg/*.symbols.nupkg' --source ${{secrets.NUGET_SOURCE}} --api-key ${{secrets.NUGET_KEY}} --skip-duplicate
22 | #continue-on-error: true
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Ww][Ii][Nn]32/
27 | [Aa][Rr][Mm]/
28 | [Aa][Rr][Mm]64/
29 | bld/
30 | [Bb]in/
31 | [Oo]bj/
32 | [Oo]ut/
33 | [Ll]og/
34 | [Ll]ogs/
35 |
36 | # Visual Studio 2015/2017 cache/options directory
37 | .vs/
38 | # Uncomment if you have tasks that create the project's static files in wwwroot
39 | #wwwroot/
40 |
41 | # Visual Studio 2017 auto generated files
42 | Generated\ Files/
43 |
44 | # MSTest test Results
45 | [Tt]est[Rr]esult*/
46 | [Bb]uild[Ll]og.*
47 |
48 | # NUnit
49 | *.VisualState.xml
50 | TestResult.xml
51 | nunit-*.xml
52 |
53 | # Build Results of an ATL Project
54 | [Dd]ebugPS/
55 | [Rr]eleasePS/
56 | dlldata.c
57 |
58 | # Benchmark Results
59 | BenchmarkDotNet.Artifacts/
60 |
61 | # .NET Core
62 | project.lock.json
63 | project.fragment.lock.json
64 | artifacts/
65 |
66 | # ASP.NET Scaffolding
67 | ScaffoldingReadMe.txt
68 |
69 | # StyleCop
70 | StyleCopReport.xml
71 |
72 | # Files built by Visual Studio
73 | *_i.c
74 | *_p.c
75 | *_h.h
76 | *.ilk
77 | *.meta
78 | *.obj
79 | *.iobj
80 | *.pch
81 | *.pdb
82 | *.ipdb
83 | *.pgc
84 | *.pgd
85 | *.rsp
86 | *.sbr
87 | *.tlb
88 | *.tli
89 | *.tlh
90 | *.tmp
91 | *.tmp_proj
92 | *_wpftmp.csproj
93 | *.log
94 | *.vspscc
95 | *.vssscc
96 | .builds
97 | *.pidb
98 | *.svclog
99 | *.scc
100 |
101 | # Chutzpah Test files
102 | _Chutzpah*
103 |
104 | # Visual C++ cache files
105 | ipch/
106 | *.aps
107 | *.ncb
108 | *.opendb
109 | *.opensdf
110 | *.sdf
111 | *.cachefile
112 | *.VC.db
113 | *.VC.VC.opendb
114 |
115 | # Visual Studio profiler
116 | *.psess
117 | *.vsp
118 | *.vspx
119 | *.sap
120 |
121 | # Visual Studio Trace Files
122 | *.e2e
123 |
124 | # TFS 2012 Local Workspace
125 | $tf/
126 |
127 | # Guidance Automation Toolkit
128 | *.gpState
129 |
130 | # ReSharper is a .NET coding add-in
131 | _ReSharper*/
132 | *.[Rr]e[Ss]harper
133 | *.DotSettings.user
134 |
135 | # TeamCity is a build add-in
136 | _TeamCity*
137 |
138 | # DotCover is a Code Coverage Tool
139 | *.dotCover
140 |
141 | # AxoCover is a Code Coverage Tool
142 | .axoCover/*
143 | !.axoCover/settings.json
144 |
145 | # Coverlet is a free, cross platform Code Coverage Tool
146 | coverage*.json
147 | coverage*.xml
148 | coverage*.info
149 |
150 | # Visual Studio code coverage results
151 | *.coverage
152 | *.coveragexml
153 |
154 | # NCrunch
155 | _NCrunch_*
156 | .*crunch*.local.xml
157 | nCrunchTemp_*
158 |
159 | # MightyMoose
160 | *.mm.*
161 | AutoTest.Net/
162 |
163 | # Web workbench (sass)
164 | .sass-cache/
165 |
166 | # Installshield output folder
167 | [Ee]xpress/
168 |
169 | # DocProject is a documentation generator add-in
170 | DocProject/buildhelp/
171 | DocProject/Help/*.HxT
172 | DocProject/Help/*.HxC
173 | DocProject/Help/*.hhc
174 | DocProject/Help/*.hhk
175 | DocProject/Help/*.hhp
176 | DocProject/Help/Html2
177 | DocProject/Help/html
178 |
179 | # Click-Once directory
180 | publish/
181 |
182 | # Publish Web Output
183 | *.[Pp]ublish.xml
184 | *.azurePubxml
185 | # Note: Comment the next line if you want to checkin your web deploy settings,
186 | # but database connection strings (with potential passwords) will be unencrypted
187 | *.pubxml
188 | *.publishproj
189 |
190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
191 | # checkin your Azure Web App publish settings, but sensitive information contained
192 | # in these scripts will be unencrypted
193 | PublishScripts/
194 |
195 | # NuGet Packages
196 | *.nupkg
197 | # NuGet Symbol Packages
198 | *.snupkg
199 | # The packages folder can be ignored because of Package Restore
200 | **/[Pp]ackages/*
201 | # except build/, which is used as an MSBuild target.
202 | !**/[Pp]ackages/build/
203 | # Uncomment if necessary however generally it will be regenerated when needed
204 | #!**/[Pp]ackages/repositories.config
205 | # NuGet v3's project.json files produces more ignorable files
206 | *.nuget.props
207 | *.nuget.targets
208 |
209 | # Microsoft Azure Build Output
210 | csx/
211 | *.build.csdef
212 |
213 | # Microsoft Azure Emulator
214 | ecf/
215 | rcf/
216 |
217 | # Windows Store app package directories and files
218 | AppPackages/
219 | BundleArtifacts/
220 | Package.StoreAssociation.xml
221 | _pkginfo.txt
222 | *.appx
223 | *.appxbundle
224 | *.appxupload
225 |
226 | # Visual Studio cache files
227 | # files ending in .cache can be ignored
228 | *.[Cc]ache
229 | # but keep track of directories ending in .cache
230 | !?*.[Cc]ache/
231 |
232 | # Others
233 | ClientBin/
234 | ~$*
235 | *~
236 | *.dbmdl
237 | *.dbproj.schemaview
238 | *.jfm
239 | *.pfx
240 | *.publishsettings
241 | orleans.codegen.cs
242 |
243 | # Including strong name files can present a security risk
244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
245 | #*.snk
246 |
247 | # Since there are multiple workflows, uncomment next line to ignore bower_components
248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
249 | #bower_components/
250 |
251 | # RIA/Silverlight projects
252 | Generated_Code/
253 |
254 | # Backup & report files from converting an old project file
255 | # to a newer Visual Studio version. Backup files are not needed,
256 | # because we have git ;-)
257 | _UpgradeReport_Files/
258 | Backup*/
259 | UpgradeLog*.XML
260 | UpgradeLog*.htm
261 | ServiceFabricBackup/
262 | *.rptproj.bak
263 |
264 | # SQL Server files
265 | *.mdf
266 | *.ldf
267 | *.ndf
268 |
269 | # Business Intelligence projects
270 | *.rdl.data
271 | *.bim.layout
272 | *.bim_*.settings
273 | *.rptproj.rsuser
274 | *- [Bb]ackup.rdl
275 | *- [Bb]ackup ([0-9]).rdl
276 | *- [Bb]ackup ([0-9][0-9]).rdl
277 |
278 | # Microsoft Fakes
279 | FakesAssemblies/
280 |
281 | # GhostDoc plugin setting file
282 | *.GhostDoc.xml
283 |
284 | # Node.js Tools for Visual Studio
285 | .ntvs_analysis.dat
286 | node_modules/
287 |
288 | # Visual Studio 6 build log
289 | *.plg
290 |
291 | # Visual Studio 6 workspace options file
292 | *.opt
293 |
294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
295 | *.vbw
296 |
297 | # Visual Studio LightSwitch build output
298 | **/*.HTMLClient/GeneratedArtifacts
299 | **/*.DesktopClient/GeneratedArtifacts
300 | **/*.DesktopClient/ModelManifest.xml
301 | **/*.Server/GeneratedArtifacts
302 | **/*.Server/ModelManifest.xml
303 | _Pvt_Extensions
304 |
305 | # Paket dependency manager
306 | .paket/paket.exe
307 | paket-files/
308 |
309 | # FAKE - F# Make
310 | .fake/
311 |
312 | # CodeRush personal settings
313 | .cr/personal
314 |
315 | # Python Tools for Visual Studio (PTVS)
316 | __pycache__/
317 | *.pyc
318 |
319 | # Cake - Uncomment if you are using it
320 | # tools/**
321 | # !tools/packages.config
322 |
323 | # Tabs Studio
324 | *.tss
325 |
326 | # Telerik's JustMock configuration file
327 | *.jmconfig
328 |
329 | # BizTalk build output
330 | *.btp.cs
331 | *.btm.cs
332 | *.odx.cs
333 | *.xsd.cs
334 |
335 | # OpenCover UI analysis results
336 | OpenCover/
337 |
338 | # Azure Stream Analytics local run output
339 | ASALocalRun/
340 |
341 | # MSBuild Binary and Structured Log
342 | *.binlog
343 |
344 | # NVidia Nsight GPU debugger configuration file
345 | *.nvuser
346 |
347 | # MFractors (Xamarin productivity tool) working folder
348 | .mfractor/
349 |
350 | # Local History for Visual Studio
351 | .localhistory/
352 |
353 | # BeatPulse healthcheck temp database
354 | healthchecksdb
355 |
356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
357 | MigrationBackup/
358 |
359 | # Ionide (cross platform F# VS Code tools) working folder
360 | .ionide/
361 |
362 | # Fody - auto-generated XML schema
363 | FodyWeavers.xsd
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "dotnet.defaultSolution": "AbpDevTools.sln"
3 | }
--------------------------------------------------------------------------------
/AbpDevTools.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.6.33606.364
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{63A376BC-8AD1-46C6-B16F-F890103018DB}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AbpDevTools", "src\AbpDevTools\AbpDevTools.csproj", "{C58E2B8D-4310-4DA3-AD79-3B0555C6DFC8}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {C58E2B8D-4310-4DA3-AD79-3B0555C6DFC8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {C58E2B8D-4310-4DA3-AD79-3B0555C6DFC8}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {C58E2B8D-4310-4DA3-AD79-3B0555C6DFC8}.Release|Any CPU.ActiveCfg = Release|Any CPU
19 | {C58E2B8D-4310-4DA3-AD79-3B0555C6DFC8}.Release|Any CPU.Build.0 = Release|Any CPU
20 | EndGlobalSection
21 | GlobalSection(SolutionProperties) = preSolution
22 | HideSolutionNode = FALSE
23 | EndGlobalSection
24 | GlobalSection(NestedProjects) = preSolution
25 | {C58E2B8D-4310-4DA3-AD79-3B0555C6DFC8} = {63A376BC-8AD1-46C6-B16F-F890103018DB}
26 | EndGlobalSection
27 | GlobalSection(ExtensibilityGlobals) = postSolution
28 | SolutionGuid = {0798E145-DECF-44E8-9B64-2931A61A8E24}
29 | EndGlobalSection
30 | EndGlobal
31 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 enisn
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | # AbpDevTools
6 | A set of tools to make development with ABP easier. It's a dotnet tool and accessed via `abpdev` **CLI** command.
7 |
8 | It helps the developer, build, run, replace, and manage logs of the projects. It makes running **multiple** solutions and projects easier.
9 |
10 | > Done with [Enisn.Templates.CLI](https://github.com/enisn/Enisn.Templates.CLI). You can build similar CLI tools with this template.
11 |
12 |
13 |
14 | # Installation
15 |
16 | - Install [AbpDevTools from NuGet](https://www.nuget.org/packages/AbpDevTools) as dotnet tool:
17 | ```bash
18 | dotnet tool update -g AbpDevTools
19 | ```
20 |
21 | - Installation for a specific runtime other than the latest:
22 |
23 | ```bash
24 | dotnet tool update -g AbpDevTools --framework net8.0
25 | # or
26 | dotnet tool update -g AbpDevTools --framework net6.0
27 | ```
28 |
29 | > This package is compiled for .NET 6.0, 7.0, 8.0 and 9.0. So you can install it for a specific runtime. If you don't specify a runtime, it'll install the latest version.
30 |
31 | ## Local Installation
32 | If you don't have access to the package source. You can install it from the source code by the following code:
33 |
34 | ```bash
35 | pwsh install.ps1
36 | ```
37 |
38 | # Getting Started
39 | You can watch the **'Getting Started'** video for onboarding this tool:
40 |
41 |
42 |
43 |
44 | # Usage
45 |
46 | Execute `abpdev` command in the terminal and it'll show you the help message.
47 |
48 | ```bash
49 | abpdev --help
50 | ```
51 |
52 | # Commands
53 | The following commands are available:
54 |
55 | ## abpdev build
56 | Builds all solutions/projects in the current directory recursively. _(Multiple solutions)_
57 |
58 | ```
59 | abpdev build [options]
60 | ```
61 |
62 | ```bash
63 | abpdev build -h
64 |
65 | PARAMETERS
66 | workingdirectory Working directory to run build. Probably project or solution directory path goes here. Default: . (Current Directory)
67 | OPTIONS
68 | -f|--build-files (Array) Names or part of names of projects or solutions will be built.
69 | -i|--interactive Interactive build file selection. Default: "False".
70 | -c|--configuration
71 | -h|--help Shows help text.
72 | ```
73 |
74 | Convention: `*.sln` files are considered as solutions and `*.csproj` files are considered as projects.
75 |
76 | 
77 |
78 | ### Example commands
79 |
80 | - Run in a specific path
81 | ```bash
82 | abpdev build C:\Path\To\Projects
83 | ```
84 |
85 | - Run in a specific configuration
86 | ```bash
87 | abpdev build C:\Path\To\Projects -c Release
88 | ```
89 |
90 |
91 | - Run in a specific path with specific configuration and specific projects
92 | ```bash
93 | abpdev build C:\Path\To\Projects -c Release -f ProjectA.csproj ProjectB.csproj
94 | ```
95 |
96 | - Run in interactive mode **(Select projects to build)**
97 | ```bash
98 | abpdev build -i
99 | ```
100 | 
101 |
102 | ## abpdev run
103 | Runs the solution in the current directory. _(Multiple solution, multiple applications including DbMigrator)_
104 |
105 | ```
106 | abpdev run [options]
107 | ```
108 |
109 | ```bash
110 | PARAMETERS
111 | workingdirectory Working directory to run build. Probably project or solution directory path goes here. Default: . (Current Directory)
112 |
113 | OPTIONS
114 | -w|--watch Watch mode Default: "False".
115 | --skip-migrate Skips migration and runs projects directly. Default: "False".
116 | -a|--all Projects to run will not be asked as prompt. All of them will run. Default: "False".
117 | --no-build Skipts build before running. Passes '--no-build' parameter to dotnet run. Default: "False".
118 | -i|--install-libs Runs 'abp install-libs' command while running the project simultaneously. Default: "False".
119 | -g|--graphBuild Uses /graphBuild while running the applications. So no need building before running. But it may cause some performance. Default: "False".
120 | -p|--projects (Array) Names or part of names of projects will be ran.
121 | -c|--configuration
122 | -e| --env Virtual Environment name. You can manage virtual environments by using 'abpdev env config'
123 | -h|--help Shows help text.
124 | ```
125 |
126 | Convention: `*.csproj` files with specific names are considered as applications or dbmigrators.
127 |
128 | > _Use `abpdev run config` command to change project name conventions according to your requirements_
129 |
130 | 
131 |
132 | ### Example commands
133 |
134 | - Run multiple solutions
135 | ```bash
136 | abpdev run C:\Path\To\Top\Folder\Of\Solutions
137 | ```
138 | 
139 |
140 | - Run in a specific path
141 | ```bash
142 | abpdev run C:\Path\To\Projects
143 | ```
144 |
145 | - Run in a specific configuration and specific path
146 | ```bash
147 | abpdev run C:\Path\To\Projects -c Release
148 | ```
149 |
150 | - Run all projects instead prompt selection
151 | ```bash
152 | abpdev run -a
153 | ```
154 |
155 | 
156 |
157 | - Skip migration and run projects directly
158 | ```bash
159 | abpdev run --skip-migrate
160 | ```
161 |
162 | - Run in watch mode
163 | ```bash
164 | abpdev run -w
165 | ```
166 | > Please note that we cannot print URL's because dotnet does give any output.
167 |
168 | ## Virtual Environments
169 | Virtual environments are used to run multiple solutions with different configurations. For example, you can run different solutions with different environments _(connectionstrings etc.)_.
170 |
171 | You can manage virtual environments by using `abpdev env config` command and use those pre-configured environments with other commands like: `abpdev run -e SqlServer`. This command will use the environment named SqlServer. You can set different connectionstrings for each environment.
172 |
173 | ```bash
174 | abpdev env config
175 | ```
176 |
177 | > You'll see the following screen. You can add, edit, delete, and select virtual environments.
178 | > ```json
179 | > {
180 | > "SqlServer": {
181 | > "Variables": {
182 | > "ConnectionStrings__Default": "Server=localhost;Database={AppName}_{Today};User ID=SA;Password=12345678Aa;TrustServerCertificate=True"
183 | > }
184 | > },
185 | > "MongoDB": {
186 | > "Variables": {
187 | > "ConnectionStrings__Default": "mongodb://localhost:27017/{AppName}_{Today}"
188 | > }
189 | > }
190 | >}
191 | > ```
192 | > **{Today}** will be replaced with the current date. So you can run multiple solutions with different databases.
193 | > **{AppName}** will be replaced with the application name. So you can run multiple solutions with different databases. _When app name couldn't be detected, folder name will be used.
194 |
195 |
196 | ### Example commands
197 |
198 | - Run in a specific virtual environment
199 | ```bash
200 | abpdev run -e SqlServer
201 | ```
202 |
203 |
204 |
205 |
206 | ## abpdev prepare
207 | Prepares the project for the first running on this machine. Automatically detects project dependencies, starts required environment apps (databases, message brokers), installs ABP libraries, and creates local configuration files. You can use the `abpdev.yml` configuration file to run the project with different environments without changing the `appsettings.json` files. You can modify the created `abpdev.yml` file to change or add new environment variables to run profile.
208 |
209 | ```
210 | abpdev prepare [options]
211 | ```
212 |
213 | ```bash
214 | abpdev prepare -h
215 |
216 | PARAMETERS
217 | workingdirectory Working directory to run build. Probably project or solution directory path goes here. Default: . (Current Directory)
218 |
219 | OPTIONS
220 | --no-config Do not create local configuration file. (abpdev.yml) Default: "False".
221 | -h|--help Shows help text.
222 | ```
223 |
224 | Convention: The command analyzes project dependencies to determine which environment apps are needed (SQL Server, MongoDB, Redis, etc.) and automatically configures the environment accordingly.
225 |
226 | 
227 |
228 | ### Example commands
229 |
230 | - Prepare project in current directory
231 | ```bash
232 | abpdev prepare
233 | ```
234 |
235 | - Prepare project in a specific path
236 | ```bash
237 | abpdev prepare C:\Path\To\Projects
238 | ```
239 |
240 | - Prepare without creating local configuration files
241 | ```bash
242 | abpdev prepare --no-config
243 | ```
244 |
245 | This command performs the following operations:
246 | - **Dependency Analysis**: Scans projects for database and messaging dependencies
247 | - **Environment Apps**: Starts required Docker containers (SQL Server, MongoDB, Redis, etc.)
248 | - **Library Installation**: Runs `abp install-libs` to install client-side libraries
249 | - **Blazor Bundling**: Bundles Blazor WASM projects
250 | - **Configuration**: Creates `abpdev.yml` files with appropriate environment settings
251 |
252 | ## abpdev logs
253 | Finds given project under the current directory and shows logs of it.
254 |
255 | 
256 | ---
257 | 
258 |
259 |
260 | ```bash
261 | abpdev logs [options]
262 | abpdev logs [command] [...]
263 |
264 | PARAMETERS
265 | projectname Determines the project to open logs of it.
266 |
267 | OPTIONS
268 | -p|--path Working directory of the command. Probably solution directory. Default: . (CurrentDirectory)
269 | -i|--interactive Options will be asked as prompt when this option used. Default: "False".
270 | -h|--help Shows help text.
271 |
272 | COMMANDS
273 | clear
274 | ```
275 |
276 | ### Example commands
277 |
278 | - Show logs of the **.Web** project
279 | ```bash
280 | abpdev logs Web
281 | ```
282 |
283 | - Clear logs of the **.Web** project
284 | ```bash
285 | abpdev logs clear -p Web
286 | ```
287 |
288 | - Clear logs without approval
289 | ```bash
290 | abpdev logs clear -p Web -f
291 | ```
292 |
293 | ## abpdev replace
294 | Replaces specified text in files under the current directory recursively. Mostly used to replace connection strings in `appsettings.json` files. But it can be used for any other purposes.
295 |
296 | ```bash
297 | USAGE
298 | abpdev replace [options]
299 | abpdev replace [command] [...]
300 |
301 | DESCRIPTION
302 | Runs file replacement according to configuration.
303 |
304 | PARAMETERS
305 | replacementconfigname If you execute single option from config, you can pass the name or pass 'all' to execute all of them
306 |
307 | OPTIONS
308 | -p|--path Working directory of the command. Probably solution directory. Default: . (CurrentDirectory)
309 | -i|--interactive Interactive Mode. It'll ask prompt to pick one config. Default: "False".
310 | -h|--help Shows help text.
311 |
312 | COMMANDS
313 | config Allows managing replacement configuration. Subcommands: config clear.
314 | ```
315 |
316 | 
317 |
318 |
319 | > Use `abpdev replace config` command to change file name conventions according to your requirements.
320 | > _You'll see something like that by default:
321 | > ```json
322 | > {
323 | > "ConnectionStrings": {
324 | > "FilePattern": "appsettings.json",
325 | > "Find": "Trusted_Connection=True;",
326 | > "Replace": "User ID=SA;Password=12345678Aa;"
327 | > }
328 | > }
329 | > ```
330 |
331 | ### Example commands
332 |
333 | - Replace connection strings in `appsettings.json` files
334 | ```bash
335 | abpdev replace ConnectionStrings
336 | ```
337 |
338 | - Run all the configurations at once
339 | ```bash
340 | abpdev replace all
341 | ```
342 |
343 | ## Enable Notifications
344 | You can enable notifications to get notified when a build or run process is completed. You can enable it by using `abpdev enable-notifications` command and disable it by using `abpdev disable-notifications` command.
345 |
346 | > _It only works on **Windows** and **MacOS**. Linux is not supported yet._
347 |
348 | ```bash
349 | abpdev enable-notifications
350 | abpdev disable-notifications
351 | ```
352 |
353 | It'll send a notification when a **migration**, **build** or **run** process is completed.
354 |
355 | 
356 |
357 |
358 | ## Environment Apps
359 | You can easily run commonly used environment apps like **SQL Server**, **PostgreSQL**, **Redis**, **MySQL**, **MongoDB** and **RabbitMQ** by using `abpdev envapp start` command.
360 |
361 | ```bash
362 | abpdev envapp [command] [options]
363 |
364 | abpdev envapp start [options]
365 | abpdev envapp stop [options]
366 | ```
367 |
368 | > You can change the default running commands by using `abpdev envapp config` command.
369 |
370 | 
371 |
372 | Available app names by **default**:
373 | ```bash
374 | - sqlserver
375 | - sqlserver-edge
376 | - postgresql
377 | - mysql
378 | - mongodb
379 | - redis
380 | - rabbitmq
381 | ```
382 |
383 | _You can extend the list or change environments of apps by using `abpdev envapp config` command._
384 |
385 | ### Example commands
386 |
387 | - Start SQL Server with custom SA password
388 | ```bash
389 | abpdev envapp start sqlserver -p myPassw0rd
390 | ```
391 |
--------------------------------------------------------------------------------
/art/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/art/logo_128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enisn/AbpDevTools/c26f328ce3474c08d839045ccfa991fdf7b24eae/art/logo_128.png
--------------------------------------------------------------------------------
/art/logo_512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enisn/AbpDevTools/c26f328ce3474c08d839045ccfa991fdf7b24eae/art/logo_512.png
--------------------------------------------------------------------------------
/images/abpdevbuild-interactive.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enisn/AbpDevTools/c26f328ce3474c08d839045ccfa991fdf7b24eae/images/abpdevbuild-interactive.gif
--------------------------------------------------------------------------------
/images/abpdevbuild.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enisn/AbpDevTools/c26f328ce3474c08d839045ccfa991fdf7b24eae/images/abpdevbuild.gif
--------------------------------------------------------------------------------
/images/abpdevenvapp.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enisn/AbpDevTools/c26f328ce3474c08d839045ccfa991fdf7b24eae/images/abpdevenvapp.gif
--------------------------------------------------------------------------------
/images/abpdevlogs-clear.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enisn/AbpDevTools/c26f328ce3474c08d839045ccfa991fdf7b24eae/images/abpdevlogs-clear.gif
--------------------------------------------------------------------------------
/images/abpdevlogs.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enisn/AbpDevTools/c26f328ce3474c08d839045ccfa991fdf7b24eae/images/abpdevlogs.gif
--------------------------------------------------------------------------------
/images/abpdevnotification.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enisn/AbpDevTools/c26f328ce3474c08d839045ccfa991fdf7b24eae/images/abpdevnotification.gif
--------------------------------------------------------------------------------
/images/abpdevprepare.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enisn/AbpDevTools/c26f328ce3474c08d839045ccfa991fdf7b24eae/images/abpdevprepare.gif
--------------------------------------------------------------------------------
/images/abpdevreplace.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enisn/AbpDevTools/c26f328ce3474c08d839045ccfa991fdf7b24eae/images/abpdevreplace.gif
--------------------------------------------------------------------------------
/images/abpdevrun-all.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enisn/AbpDevTools/c26f328ce3474c08d839045ccfa991fdf7b24eae/images/abpdevrun-all.gif
--------------------------------------------------------------------------------
/images/abpdevrun-multiplesolutions.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enisn/AbpDevTools/c26f328ce3474c08d839045ccfa991fdf7b24eae/images/abpdevrun-multiplesolutions.gif
--------------------------------------------------------------------------------
/images/abpdevrun.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enisn/AbpDevTools/c26f328ce3474c08d839045ccfa991fdf7b24eae/images/abpdevrun.gif
--------------------------------------------------------------------------------
/install.ps1:
--------------------------------------------------------------------------------
1 | pwsh ./pack.ps1
2 | dotnet tool update -g AbpDevTools --add-source ./nupkg --prerelease
--------------------------------------------------------------------------------
/pack.ps1:
--------------------------------------------------------------------------------
1 | dotnet pack ./src/AbpDevTools/AbpDevTools.csproj -c Release --include-symbols --include-source -o ./nupkg
--------------------------------------------------------------------------------
/src/AbpDevTools/AbpDevTools.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | 1.9.2
6 | enable
7 | true
8 | logo_128.png
9 | https://github.com/enisn/AbpDevTools
10 | LICENSE.txt
11 | enable
12 | true
13 | abpdev
14 | ../../nupkg
15 |
16 |
17 |
18 | net6.0;net7.0;net8.0;net9.0
19 |
20 |
21 | net9.0
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/src/AbpDevTools/Commands/AbpBundleCommand.cs:
--------------------------------------------------------------------------------
1 | using AbpDevTools.Configuration;
2 | using CliFx.Infrastructure;
3 | using Spectre.Console;
4 | using System.Diagnostics;
5 | using System.Text;
6 |
7 | namespace AbpDevTools.Commands;
8 |
9 | [Command("bundle", Description = "Runs 'abp bundle' command for each Blazor WASM projects recursively.")]
10 | public class AbpBundleCommand : ICommand
11 | {
12 | [CommandParameter(0, IsRequired = false, Description = "Working directory to run build. Probably project or solution directory path goes here. Default: . (Current Directory)")]
13 | public string? WorkingDirectory { get; set; }
14 |
15 | [CommandOption("graphBuild", 'g', Description = "Graph builds project before running 'abp bundle'.")]
16 | public bool GraphBuild { get; set; }
17 |
18 | protected IConsole? console;
19 | protected AbpBundleListCommand listCommand;
20 |
21 | protected ToolOption Tools { get; }
22 |
23 | public AbpBundleCommand(AbpBundleListCommand listCommand, ToolsConfiguration toolsConfiguration)
24 | {
25 | this.listCommand = listCommand;
26 | Tools = toolsConfiguration.GetOptions();
27 | }
28 |
29 | public async ValueTask ExecuteAsync(IConsole console)
30 | {
31 | this.console = console;
32 | if (string.IsNullOrEmpty(WorkingDirectory))
33 | {
34 | WorkingDirectory = Directory.GetCurrentDirectory();
35 | }
36 | listCommand.WorkingDirectory = WorkingDirectory;
37 |
38 | console.RegisterCancellationHandler().Register(() =>
39 | {
40 | console.Output.WriteLine("Abp bundle cancelled.");
41 | throw new OperationCanceledException("Abp bundle cancelled.");
42 | });
43 |
44 | var wasmCsprojs = await AnsiConsole.Status()
45 | .StartAsync("Looking for projects", async ctx =>
46 | {
47 | ctx.Spinner(Spinner.Known.SimpleDotsScrolling);
48 |
49 | await Task.Yield();
50 |
51 | return listCommand.GetWasmProjects().ToArray();
52 | });
53 |
54 | if (!wasmCsprojs.Any())
55 | {
56 | await console.Output.WriteLineAsync("No Blazor WASM projects found. No files to bundle.");
57 |
58 | return;
59 | }
60 |
61 | AnsiConsole.MarkupLine($"[green]{wasmCsprojs.Length}[/] blazor wasm projects found.");
62 |
63 | foreach (var csproj in wasmCsprojs)
64 | {
65 | if (GraphBuild)
66 | {
67 | var index = Array.IndexOf(wasmCsprojs, csproj) + 1;
68 | var compiled = await AnsiConsole.Status().StartAsync($"[grey]{index/wasmCsprojs.Length} Building {csproj.Name}...[/]", async (ctx) =>
69 | {
70 | ctx.Spinner(Spinner.Known.SimpleDotsScrolling);
71 |
72 | var startInfo = new ProcessStartInfo("dotnet", $"build /graphBuild")
73 | {
74 | WorkingDirectory = Path.GetDirectoryName(csproj.FullName)!,
75 | };
76 | startInfo.RedirectStandardOutput = true;
77 | using var process = Process.Start(startInfo)!;
78 | await process.WaitForExitAsync();
79 |
80 | if (process.ExitCode == 0)
81 | {
82 | AnsiConsole.MarkupLine($"[green]Completed[/][grey] Building {csproj.Name}[/]");
83 | return true;
84 | }
85 | else
86 | {
87 | AnsiConsole.MarkupLine($"[red]Couldn't compile[/] {csproj.Name}");
88 | return false;
89 | }
90 | });
91 |
92 | if (!compiled)
93 | {
94 | continue;
95 | }
96 | }
97 |
98 | await AnsiConsole.Status().StartAsync($"Running 'abp bundle' for {csproj.Name}...", async ctx =>
99 | {
100 | ctx.Spinner(Spinner.Known.SimpleDotsScrolling);
101 |
102 | var startInfo = new ProcessStartInfo(Tools["abp"], $"bundle -wd {Path.GetDirectoryName(csproj.FullName)}");
103 | startInfo.RedirectStandardOutput = true;
104 | using var process = Process.Start(startInfo)!;
105 | process.BeginOutputReadLine();
106 | await process.WaitForExitAsync();
107 |
108 | if (process.ExitCode == 0)
109 | {
110 | AnsiConsole.MarkupLine($"[green]Success[/] while running 'abp bundle' for {csproj.Name}.");
111 | }
112 | else
113 | {
114 | AnsiConsole.MarkupLine($"[red]Error[/] while running 'abp bundle' for {csproj.Name}.");
115 | }
116 | });
117 | }
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/AbpDevTools/Commands/AbpBundleListCommand.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using CliFx.Infrastructure;
3 | using System.Xml;
4 | using Spectre.Console;
5 |
6 | [Command("bundle list", Description = "List projects that needs to run 'abp bundle'.")]
7 | public class AbpBundleListCommand : ICommand
8 | {
9 | [CommandParameter(0, IsRequired = false, Description = "Working directory for the command. Probably project or solution directory path goes here. Default: . (Current Directory)")]
10 | public string? WorkingDirectory { get; set; }
11 |
12 | public async ValueTask ExecuteAsync(IConsole console)
13 | {
14 | if (string.IsNullOrEmpty(WorkingDirectory))
15 | {
16 | WorkingDirectory = Directory.GetCurrentDirectory();
17 | }
18 |
19 | var wasmCsprojs = await AnsiConsole.Status()
20 | .StartAsync("Searching for Blazor WASM projects...", async ctx =>
21 | {
22 | ctx.Spinner(Spinner.Known.Dots);
23 | var wasmCsprojs = GetWasmProjects();
24 | foreach (var csproj in wasmCsprojs)
25 | {
26 | AnsiConsole.MarkupLine($"- .{Path.DirectorySeparatorChar}{Path.GetRelativePath(WorkingDirectory!, csproj.DirectoryName ?? string.Empty)}");
27 | }
28 | return wasmCsprojs;
29 | });
30 |
31 | if (!wasmCsprojs.Any())
32 | {
33 | await console.Output.WriteLineAsync("No Blazor WASM projects found. No files to bundle.");
34 | return;
35 | }
36 | }
37 |
38 | public IEnumerable GetWasmProjects(){
39 | return Directory.EnumerateFiles(WorkingDirectory!, "*.csproj", SearchOption.AllDirectories)
40 | .Where(IsCsprojBlazorWasm)
41 | .Select(x => new FileInfo(x));
42 | }
43 |
44 | private static bool IsCsprojBlazorWasm(string file)
45 | {
46 | using var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read);
47 | using var reader = XmlReader.Create(fileStream, new XmlReaderSettings
48 | {
49 | DtdProcessing = DtdProcessing.Ignore,
50 | IgnoreWhitespace = true
51 | });
52 |
53 | try
54 | {
55 | // Look for the Project element
56 | while (reader.Read())
57 | {
58 | if (reader.NodeType == XmlNodeType.Element && reader.Name == "Project")
59 | {
60 | var sdk = reader.GetAttribute("Sdk");
61 | return sdk == "Microsoft.NET.Sdk.BlazorWebAssembly";
62 | }
63 | }
64 |
65 | return false;
66 | }
67 | catch (XmlException)
68 | {
69 | return false;
70 | }
71 | }
72 | }
--------------------------------------------------------------------------------
/src/AbpDevTools/Commands/BuildCommand.cs:
--------------------------------------------------------------------------------
1 | using AbpDevTools.Configuration;
2 | using AbpDevTools.Notifications;
3 | using CliFx.Infrastructure;
4 | using Spectre.Console;
5 | using System;
6 | using System.Diagnostics;
7 |
8 | namespace AbpDevTools.Commands;
9 |
10 | [Command("build", Description = "Shortcut for dotnet build /graphBuild")]
11 | public class BuildCommand : ICommand
12 | {
13 | [CommandParameter(0, IsRequired = false, Description = "Working directory to run build. Probably project or solution directory path goes here. Default: . (Current Directory)")]
14 | public string? WorkingDirectory { get; set; }
15 |
16 | [CommandOption("build-files", 'f', Description = "(Array) Names or part of names of projects or solutions will be built.")]
17 | public string[]? BuildFiles { get; set; }
18 |
19 | [CommandOption("interactive", 'i', Description = "Interactive build file selection.")]
20 | public bool Interactive { get; set; }
21 |
22 | [CommandOption("configuration", 'c')]
23 | public string? Configuration { get; set; }
24 |
25 | Process? runningProcess;
26 | protected readonly INotificationManager notificationManager;
27 | protected readonly ToolsConfiguration toolsConfiguration;
28 |
29 | public BuildCommand(INotificationManager notificationManager, ToolsConfiguration toolsConfiguration)
30 | {
31 | this.notificationManager = notificationManager;
32 | this.toolsConfiguration = toolsConfiguration;
33 | }
34 |
35 | public async ValueTask ExecuteAsync(IConsole console)
36 | {
37 | if (string.IsNullOrEmpty(WorkingDirectory))
38 | {
39 | WorkingDirectory = Directory.GetCurrentDirectory();
40 | }
41 |
42 | var cancellationToken = console.RegisterCancellationHandler();
43 | var buildFiles = await FindBuildFilesAsync("*.sln", "solution");
44 |
45 | if (buildFiles.Length == 0)
46 | {
47 | await console.Output.WriteLineAsync("No .sln files found. Looking for .csproj files.");
48 |
49 | buildFiles = await FindBuildFilesAsync("*.csproj", "csproj");
50 | }
51 |
52 | if (buildFiles.Length == 0)
53 | {
54 | await console.Output.WriteLineAsync("No .csproj files found. No files to build.");
55 |
56 | return;
57 | }
58 |
59 | var successfulCount = await AnsiConsole.Status().StartAsync("Starting build...", async ctx =>
60 | {
61 | int completed = 0;
62 | for (int i = 0; i < buildFiles.Length; i++)
63 | {
64 | var buildFile = buildFiles[i];
65 | var progressRatio = $"[yellow]{i + 1}/{buildFiles.Length}[/]";
66 | ctx.Status($"{progressRatio} - [bold]Building[/] {buildFile.FullName}");
67 |
68 | var commandSuffix = string.Empty;
69 |
70 | if (!string.IsNullOrEmpty(Configuration))
71 | {
72 | commandSuffix += $" --configuration {Configuration}";
73 | }
74 |
75 | var tools = toolsConfiguration.GetOptions();
76 | runningProcess = Process.Start(new ProcessStartInfo(tools["dotnet"], "build /graphBuild" + commandSuffix)
77 | {
78 | WorkingDirectory = Path.GetDirectoryName(buildFile.FullName),
79 | UseShellExecute = false,
80 | RedirectStandardOutput = true,
81 | RedirectStandardError = true,
82 | })!;
83 |
84 | // equivalent of WaitForExit
85 | var _output = await runningProcess.StandardOutput.ReadToEndAsync();
86 | await runningProcess.WaitForExitAsync();
87 |
88 | if (runningProcess.ExitCode == 0)
89 | {
90 | completed++;
91 | AnsiConsole.MarkupLine($"{progressRatio} - [green]completed[/] [bold]Building[/] [silver]{buildFile.Name}[/]");
92 | }
93 | else
94 | {
95 | AnsiConsole.MarkupLine($"{progressRatio} - [red]failed [bold]Building[/] {buildFile.Name} Exit Code: {runningProcess.ExitCode}[/]");
96 | AnsiConsole.MarkupLine($"[grey]{_output}[/]");
97 | }
98 |
99 | if (cancellationToken.IsCancellationRequested)
100 | {
101 | break;
102 | }
103 |
104 | runningProcess.Kill(entireProcessTree: true);
105 | }
106 |
107 | return completed;
108 | });
109 |
110 | if (buildFiles.Length == 1)
111 | {
112 | await notificationManager.SendAsync("Build "+ (successfulCount > 0 ? "Completed!" : "Failed!"), $"{buildFiles[0].Name} has been built.");
113 | }
114 | else
115 | {
116 | await notificationManager.SendAsync("Build Done!", $"{successfulCount} of {buildFiles.Length} projects have been built in '{WorkingDirectory}' folder.");
117 | }
118 |
119 | cancellationToken.Register(KillRunningProcesses);
120 | }
121 |
122 | private async Task FindBuildFilesAsync(string pattern, string? nameOfPattern = null)
123 | {
124 | nameOfPattern ??= "build";
125 |
126 | var files = await AnsiConsole.Status()
127 | .StartAsync($"Looking for {nameOfPattern} files ({pattern})", async ctx =>
128 | {
129 | ctx.Spinner(Spinner.Known.SimpleDotsScrolling);
130 |
131 | await Task.Yield();
132 |
133 | var query = Directory.EnumerateFiles(WorkingDirectory!, pattern, SearchOption.AllDirectories);
134 |
135 | if (BuildFiles?.Length > 0)
136 | {
137 | query = query.Where(x => BuildFiles.Any(y => x.Contains(y, StringComparison.InvariantCultureIgnoreCase)));
138 | }
139 |
140 | var fileInfos = query
141 | .Select(x => new FileInfo(x))
142 | .ToArray();
143 |
144 | AnsiConsole.MarkupLine($"[green]{fileInfos.Length}[/] {pattern.Replace('*', '\0')} files found.");
145 |
146 | return fileInfos;
147 | });
148 |
149 | if (Interactive && files.Length > 1)
150 | {
151 | var choosed = AnsiConsole.Prompt(
152 | new MultiSelectionPrompt()
153 | .Title("Choose files to be built:")
154 | .NotRequired() // Not required to have a favorite fruit
155 | .PageSize(12)
156 | .HighlightStyle(new Style(foreground: Color.MediumPurple2))
157 | .MoreChoicesText("[grey](Move up and down to reveal more files)[/]")
158 | .InstructionsText(
159 | "[grey](Press [mediumpurple2][/] to toggle a file, " +
160 | "[green][/] to accept)[/]")
161 | .AddChoices(files.Select(s => s.DirectoryName!.Replace(WorkingDirectory!, "."))));
162 |
163 | files = files.Where(x => choosed.Contains(x.FullName)).ToArray();
164 | }
165 |
166 | return files;
167 | }
168 |
169 | protected void KillRunningProcesses()
170 | {
171 | runningProcess?.Kill(entireProcessTree: true);
172 |
173 | runningProcess?.WaitForExit();
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/src/AbpDevTools/Commands/CleanCommand.cs:
--------------------------------------------------------------------------------
1 | using AbpDevTools.Configuration;
2 | using CliFx.Infrastructure;
3 | using Spectre.Console;
4 |
5 | namespace AbpDevTools.Commands;
6 |
7 | [Command("clean", Description = "Cleans 'bin', 'obj' and 'node_modules' folders recursively.")]
8 | public class CleanCommand : ICommand
9 | {
10 | [CommandParameter(0, IsRequired = false, Description = "Working directory to run build. Probably project or solution directory path goes here. Default: . (Current Directory)")]
11 | public string? WorkingDirectory { get; set; }
12 |
13 | private readonly CleanConfiguration cleanConfiguration;
14 |
15 | public CleanCommand(CleanConfiguration cleanConfiguration)
16 | {
17 | this.cleanConfiguration = cleanConfiguration;
18 | }
19 |
20 | public async ValueTask ExecuteAsync(IConsole console)
21 | {
22 | if (string.IsNullOrEmpty(WorkingDirectory))
23 | {
24 | WorkingDirectory = Directory.GetCurrentDirectory();
25 | }
26 |
27 | var foldersToDelete = cleanConfiguration.GetOptions()
28 | .Folders.Select(x => Path.DirectorySeparatorChar + x)
29 | .ToArray();
30 |
31 | await AnsiConsole.Status()
32 | .StartAsync("Looking for directories...", async ctx =>
33 | {
34 | ctx.Spinner(Spinner.Known.SimpleDotsScrolling);
35 |
36 | await Task.Yield();
37 |
38 | var directories = Directory.EnumerateDirectories(WorkingDirectory!, string.Empty, SearchOption.AllDirectories)
39 | .Where(x => foldersToDelete.Any(a => x.EndsWith(a)));
40 |
41 | foreach (var directory in directories)
42 | {
43 | ctx.Status($"Deleting {directory}...");
44 | Directory.Delete(directory, true);
45 | }
46 | });
47 |
48 | console.Output.WriteLine("Cleaned successfully.");
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/AbpDevTools/Commands/ConfigurationClearCommand.cs:
--------------------------------------------------------------------------------
1 | using AbpDevTools.Configuration;
2 | using CliFx.Infrastructure;
3 |
4 | namespace AbpDevTools.Commands;
5 |
6 | public abstract class ConfigurationClearCommandBase : ICommand
7 | {
8 | [CommandOption("force", 'f')]
9 | public bool Force { get; set; }
10 |
11 | public virtual ValueTask ExecuteAsync(IConsole console)
12 | {
13 | throw new NotImplementedException();
14 | }
15 | }
16 |
17 | public abstract class ConfigurationClearCommandBase : ConfigurationClearCommandBase
18 | where TConfiguration : IConfigurationBase
19 | {
20 |
21 | private readonly TConfiguration configuration;
22 |
23 | public ConfigurationClearCommandBase(TConfiguration configuration)
24 | {
25 | this.configuration = configuration;
26 | }
27 |
28 | public override async ValueTask ExecuteAsync(IConsole console)
29 | {
30 | if (!Force)
31 | {
32 | await console
33 | .Output
34 | .WriteAsync($"Are you sure to remove existing configuration at path {configuration.FilePath}?\nY/N?");
35 |
36 | var confirm = await console.Input.ReadLineAsync();
37 | if (!confirm!.Equals("Y", StringComparison.InvariantCultureIgnoreCase))
38 | {
39 | return;
40 | }
41 | }
42 |
43 | File.Delete(configuration.FilePath);
44 | }
45 | }
46 |
47 | [Command("config clear", Description = "Clears the current configuration.")]
48 | public class ConfigurationClearCommand : ICommand
49 | {
50 | [CommandOption("force", 'f')]
51 | public bool Force { get; set; }
52 |
53 | protected readonly ConfigurationClearCommandBase[] configurationClearCommands;
54 |
55 | public ConfigurationClearCommand(
56 | ReplacementConfigClearCommand replacementConfigClearCommand,
57 | EnvironmentAppConfigClearCommand environmentAppConfigClearCommand,
58 | RunConfigClearCommand runConfigClearCommand,
59 | CleanConfigClearCommand cleanConfigClearCommand,
60 | ToolsConfigClearCommand toolsConfigClearCommand)
61 | {
62 | configurationClearCommands = new ConfigurationClearCommandBase[]
63 | {
64 | replacementConfigClearCommand,
65 | environmentAppConfigClearCommand,
66 | runConfigClearCommand,
67 | cleanConfigClearCommand,
68 | toolsConfigClearCommand,
69 | };
70 | }
71 |
72 | public async ValueTask ExecuteAsync(IConsole console)
73 | {
74 | foreach (var command in configurationClearCommands)
75 | {
76 | command.Force = Force;
77 | await command.ExecuteAsync(console);
78 | }
79 | }
80 | }
81 |
82 | [Command("replace config clear")]
83 | [RegisterTransient]
84 | public class ReplacementConfigClearCommand : ConfigurationClearCommandBase
85 | {
86 | public ReplacementConfigClearCommand(ReplacementConfiguration configuration) : base(configuration)
87 | {
88 | }
89 | }
90 |
91 | [Command("envapp config clear")]
92 | public class EnvironmentAppConfigClearCommand : ConfigurationClearCommandBase
93 | {
94 | public EnvironmentAppConfigClearCommand(EnvironmentAppConfiguration configuration) : base(configuration)
95 | {
96 | }
97 | }
98 |
99 | [Command("run config clear")]
100 | public class RunConfigClearCommand : ConfigurationClearCommandBase
101 | {
102 | public RunConfigClearCommand(RunConfiguration configuration) : base(configuration)
103 | {
104 | }
105 | }
106 |
107 | [Command("clean config clear")]
108 | public class CleanConfigClearCommand : ConfigurationClearCommandBase
109 | {
110 | public CleanConfigClearCommand(CleanConfiguration configuration) : base(configuration)
111 | {
112 | }
113 | }
114 | [Command("tools config clear")]
115 | public class ToolsConfigClearCommand : ConfigurationClearCommandBase
116 | {
117 | public ToolsConfigClearCommand(ToolsConfiguration configuration) : base(configuration)
118 | {
119 | }
120 | }
--------------------------------------------------------------------------------
/src/AbpDevTools/Commands/ConfigurationCommand.cs:
--------------------------------------------------------------------------------
1 | using AbpDevTools.Configuration;
2 | using CliFx.Exceptions;
3 | using CliFx.Infrastructure;
4 | using System.Diagnostics;
5 | using System.Runtime.InteropServices;
6 |
7 | namespace AbpDevTools.Commands;
8 |
9 | [Command("config")]
10 | public class ConfigCommand : ICommand
11 | {
12 | public async ValueTask ExecuteAsync(IConsole console)
13 | {
14 | await console.Output.WriteLineAsync("Available commands:\n");
15 | await console.Output.WriteLineAsync("-abpdev replace config");
16 | await console.Output.WriteLineAsync("-abpdev envapp config");
17 | await console.Output.WriteLineAsync("-abpdev run config");
18 | await console.Output.WriteLineAsync("-abpdev clean config");
19 | await console.Output.WriteLineAsync("-abpdev tools config");
20 | await console.Output.WriteLineAsync("-abpdev config clear | Resets all the configurations to defaults.");
21 | }
22 | }
23 |
24 | public abstract class ConfigurationBaseCommand : ICommand
25 | where TConfiguration : IConfigurationBase
26 | {
27 | public TConfiguration Configuration { get; }
28 |
29 | public ConfigurationBaseCommand(TConfiguration configuration)
30 | {
31 | Configuration = configuration;
32 | }
33 |
34 | public virtual ValueTask ExecuteAsync(IConsole console)
35 | {
36 | console.Output.WriteLine("Opening file " + Configuration.FilePath);
37 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
38 | {
39 | Process.Start(new ProcessStartInfo("explorer", Configuration.FilePath));
40 | }
41 | else
42 | {
43 | Process.Start(new ProcessStartInfo("open", $"\"{Configuration.FilePath}\""));
44 | }
45 | return ValueTask.CompletedTask;
46 | }
47 | }
48 |
49 | [Command("replace config", Description = "Allows managing replacement configuration.")]
50 | public class ReplaceConfigurationCommand : ConfigurationBaseCommand
51 | {
52 | public ReplaceConfigurationCommand(ReplacementConfiguration configuration) : base(configuration)
53 | {
54 | }
55 |
56 | public override ValueTask ExecuteAsync(IConsole console)
57 | {
58 | Configuration.GetOptions();
59 | return base.ExecuteAsync(console);
60 | }
61 | }
62 |
63 | [Command("envapp config", Description = "Allows managing replacement configuration.")]
64 | public class EnvironmentAppConfigurationCommand : ConfigurationBaseCommand
65 | {
66 | public EnvironmentAppConfigurationCommand(EnvironmentAppConfiguration configuration) : base(configuration)
67 | {
68 | }
69 |
70 | public override ValueTask ExecuteAsync(IConsole console)
71 | {
72 | Configuration.GetOptions();
73 | return base.ExecuteAsync(console);
74 | }
75 | }
76 |
77 | [Command("run config")] [Obsolete]
78 | public class RunConfigurationCommand : ConfigurationBaseCommand
79 | {
80 | public RunConfigurationCommand(RunConfiguration configuration) : base(configuration)
81 | {
82 | }
83 |
84 | public override ValueTask ExecuteAsync(IConsole console)
85 | {
86 | Configuration.GetOptions();
87 |
88 | // This command is deprecated.
89 | // TODO: Remove this command in the future.
90 | throw new CommandException("This command is deprecated. Use \"abpdev run\" directly instead.");
91 | }
92 | }
93 |
94 | [Command("clean config")]
95 | public class CleanConfigurationCommand : ConfigurationBaseCommand
96 | {
97 | public CleanConfigurationCommand(CleanConfiguration configuration) : base(configuration)
98 | {
99 | }
100 |
101 | public override ValueTask ExecuteAsync(IConsole console)
102 | {
103 | Configuration.GetOptions();
104 | return base.ExecuteAsync(console);
105 | }
106 | }
107 |
108 | [Command("tools config")]
109 | public class ToolsConfigurationCommand : ConfigurationBaseCommand
110 | {
111 | public ToolsConfigurationCommand(ToolsConfiguration configuration) : base(configuration)
112 | {
113 | }
114 |
115 | public override ValueTask ExecuteAsync(IConsole console)
116 | {
117 | Configuration.GetOptions();
118 | return base.ExecuteAsync(console);
119 | }
120 | }
--------------------------------------------------------------------------------
/src/AbpDevTools/Commands/DatabaseDropCommand.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using AbpDevTools.Configuration;
3 | using AbpDevTools.Notifications;
4 | using CliFx.Infrastructure;
5 | using Spectre.Console;
6 |
7 | namespace AbpDevTools.Commands;
8 |
9 | [Command("database-drop", Description = "Drops all databases in the working directory")]
10 | public class DatabaseDropCommand : ICommand
11 | {
12 | [CommandParameter(0, IsRequired = false, Description = "Working directory to search for EntityFramework projects. Default: . (Current Directory)")]
13 | public string? WorkingDirectory { get; set; }
14 |
15 | [CommandOption("force", 'f')]
16 | public bool Force { get; set; }
17 |
18 | protected readonly INotificationManager notificationManager;
19 | protected readonly ToolsConfiguration toolsConfiguration;
20 |
21 | public DatabaseDropCommand(INotificationManager notificationManager, ToolsConfiguration toolsConfiguration)
22 | {
23 | this.notificationManager = notificationManager;
24 | this.toolsConfiguration = toolsConfiguration;
25 | }
26 |
27 | public async ValueTask ExecuteAsync(IConsole console)
28 | {
29 | if (string.IsNullOrEmpty(WorkingDirectory))
30 | {
31 | WorkingDirectory = Directory.GetCurrentDirectory();
32 | }
33 |
34 | var efCoreProjects = await GetEfCoreProjectsAsync();
35 |
36 | var cancellationToken = console.RegisterCancellationHandler();
37 |
38 | var projectCount = efCoreProjects.Length;
39 | if (projectCount == 0)
40 | {
41 | await console.Output.WriteLineAsync("Could not find any EntityFrameworkCore project in the working directory...");
42 | return;
43 | }
44 |
45 | AnsiConsole.MarkupLine($"[green]{projectCount}[/] EntityFrameworkCore project(s) found in the directory. Trying to find and drop databases...");
46 |
47 | var forcePostfix = Force ? " --force" : string.Empty;
48 |
49 | for (var i = 0; i < projectCount; i++)
50 | {
51 | var efCoreProject = efCoreProjects[i];
52 |
53 | AnsiConsole.MarkupLine($"[blue]## Project{(i + 1)} - {efCoreProject.Name.Replace(".csproj", string.Empty)}[/]");
54 |
55 | var tools = toolsConfiguration.GetOptions();
56 | var startInfo = new ProcessStartInfo(tools["dotnet"], $"ef database drop{forcePostfix}")
57 | {
58 | WorkingDirectory = efCoreProject.DirectoryName!,
59 | RedirectStandardOutput = true,
60 | CreateNoWindow = false
61 | };
62 |
63 | var process = Process.Start(startInfo)!;
64 |
65 | process.OutputDataReceived += async (sender, args) =>
66 | {
67 | if (args?.Data != null)
68 | {
69 | await console.Output.WriteLineAsync("* " + args.Data);
70 | }
71 | };
72 |
73 | process.BeginOutputReadLine();
74 |
75 | await process.WaitForExitAsync(cancellationToken);
76 | }
77 |
78 | if (!cancellationToken.IsCancellationRequested)
79 | {
80 | await notificationManager.SendAsync("Dropped database(s)", $"Dropped all databases in {WorkingDirectory}");
81 | }
82 | }
83 |
84 | private async Task GetEfCoreProjectsAsync()
85 | {
86 | return await AnsiConsole.Status()
87 | .StartAsync("Searching EntityFrameworkCore projects...", ctx =>
88 | {
89 | ctx.Spinner(Spinner.Known.SimpleDotsScrolling);
90 |
91 | var efCoreProjects = Directory.EnumerateFiles(WorkingDirectory!, "*.csproj", SearchOption.AllDirectories)
92 | .Where(x => x.EndsWith("EntityFrameworkCore.csproj"))
93 | .Select(x => new FileInfo(x))
94 | .ToArray();
95 |
96 | return Task.FromResult(efCoreProjects);
97 | });
98 | }
99 | }
--------------------------------------------------------------------------------
/src/AbpDevTools/Commands/DisableNotificationsCommand.cs:
--------------------------------------------------------------------------------
1 | using AbpDevTools.Configuration;
2 | using CliFx.Exceptions;
3 | using CliFx.Infrastructure;
4 | using System.Diagnostics;
5 | using System.Runtime.InteropServices;
6 |
7 | namespace AbpDevTools.Commands;
8 |
9 | [Command("disable-notifications")]
10 | public class DisableNotificationsCommand : ICommand
11 | {
12 | [CommandOption("uninstall", 'u', Description = "Uninstalls the 'BurntToast' powershell module.")]
13 | public bool UninstallBurntToast { get; set; }
14 |
15 | protected readonly NotificationConfiguration notificationConfiguration;
16 |
17 | public DisableNotificationsCommand(NotificationConfiguration notificationConfiguration)
18 | {
19 | this.notificationConfiguration = notificationConfiguration;
20 | }
21 |
22 | public async ValueTask ExecuteAsync(IConsole console)
23 | {
24 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
25 | {
26 | if (UninstallBurntToast)
27 | {
28 | var process = Process.Start("powershell", "-Command Uninstall-Module -Name BurntToast");
29 |
30 | console.RegisterCancellationHandler().Register(() => process.Kill(entireProcessTree: true));
31 |
32 | await process.WaitForExitAsync();
33 | }
34 | }
35 |
36 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
37 | {
38 | var options = notificationConfiguration.GetOptions();
39 | options.Enabled = false;
40 | notificationConfiguration.SetOptions(options);
41 |
42 | return;
43 | }
44 |
45 | throw new CommandException($"This operation isn't supported on {RuntimeInformation.OSDescription} currently. :(");
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/AbpDevTools/Commands/EnableNotificationsCommand.cs:
--------------------------------------------------------------------------------
1 | using AbpDevTools.Configuration;
2 | using AbpDevTools.Notifications;
3 | using CliFx.Exceptions;
4 | using CliFx.Infrastructure;
5 | using System.Diagnostics;
6 | using System.Runtime.InteropServices;
7 |
8 | namespace AbpDevTools.Commands;
9 |
10 | [Command("enable-notifications")]
11 | public class EnableNotificationsCommand : ICommand
12 | {
13 | protected INotificationManager notificationManager;
14 | protected ToolsConfiguration toolsConfiguration;
15 | protected NotificationConfiguration notificationConfiguration;
16 |
17 | public EnableNotificationsCommand(INotificationManager notificationManager, ToolsConfiguration toolsConfiguration, NotificationConfiguration notificationConfiguration)
18 | {
19 | this.notificationManager = notificationManager;
20 | this.toolsConfiguration = toolsConfiguration;
21 | this.notificationConfiguration = notificationConfiguration;
22 | }
23 |
24 | public async ValueTask ExecuteAsync(IConsole console)
25 | {
26 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
27 | {
28 | if (!PowerShellExistsInWindows())
29 | {
30 | throw new CommandException($"PowerShell is not installed in your system. Please install it and try again.");
31 | }
32 |
33 | var tools = toolsConfiguration.GetOptions();
34 | var process = Process.Start(tools["powershell"], "-Command Install-Module -Name BurntToast");
35 |
36 | console.RegisterCancellationHandler().Register(() => process.Kill(entireProcessTree: true));
37 |
38 | await process.WaitForExitAsync();
39 |
40 | var options = notificationConfiguration.GetOptions();
41 | options.Enabled = true;
42 | notificationConfiguration.SetOptions(options);
43 |
44 | await notificationManager.SendAsync("Notifications Enabled", "Notifications will be displayed like this.");
45 |
46 | return;
47 | }
48 |
49 | if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
50 | {
51 | var options = notificationConfiguration.GetOptions();
52 | options.Enabled = true;
53 | notificationConfiguration.SetOptions(options);
54 |
55 | await notificationManager.SendAsync("Notifications Enabled", "Notifications will be displayed like this.");
56 |
57 | return;
58 | }
59 |
60 | throw new CommandException($"This operation isn't supported on {RuntimeInformation.OSDescription} currently. :(");
61 | }
62 |
63 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "")]
64 | public bool PowerShellExistsInWindows()
65 | {
66 | string regval = Microsoft.Win32.Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PowerShell\1", "Install", null).ToString();
67 | return regval.Equals("1");
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/AbpDevTools/Commands/EnvironmentAppCommand.cs:
--------------------------------------------------------------------------------
1 | using AbpDevTools.Configuration;
2 | using CliFx.Infrastructure;
3 |
4 | namespace AbpDevTools.Commands;
5 |
6 | [Command("envapp", Description = "Environment apps that required while development.")]
7 | public class EnvironmentAppCommand : ICommand
8 | {
9 | private readonly EnvironmentAppConfiguration environmentAppConfiguration;
10 |
11 | public EnvironmentAppCommand(EnvironmentAppConfiguration environmentAppConfiguration)
12 | {
13 | this.environmentAppConfiguration = environmentAppConfiguration;
14 | }
15 |
16 | public ValueTask ExecuteAsync(IConsole console)
17 | {
18 | var options = environmentAppConfiguration.GetOptions();
19 |
20 | console.Output.WriteLine("Available env apps:\n - " + string.Join("\n - ", options.Keys));
21 |
22 | console.Output.WriteLine("\nRunning env app: \n envapp start \n envapp start redis");
23 | console.Output.WriteLine("\nStopping env app: \n envapp stop \n envapp stop redis");
24 | console.Output.WriteLine("\nEditing env apps: \n envapp config");
25 |
26 | return ValueTask.CompletedTask;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/AbpDevTools/Commands/EnvironmentAppStartCommand.cs:
--------------------------------------------------------------------------------
1 | using AbpDevTools.Configuration;
2 | using CliFx.Exceptions;
3 | using CliFx.Infrastructure;
4 | using System.Diagnostics;
5 |
6 | namespace AbpDevTools.Commands;
7 | [Command("envapp start", Description = "Deploys infrastructural tools to docker. Such as Redis, RabbitMQ, SqlServer etc.")]
8 | public class EnvironmentAppStartCommand : ICommand
9 | {
10 | [CommandParameter(0, IsRequired = false, Description = "Name of the app.")]
11 | public string[] AppNames { get; set; } = Array.Empty();
12 |
13 | [CommandOption("password", 'p', Description = "Default password for sql images when applicable. Default: 12345678Aa")]
14 | public string DefaultPassword { get; set; } = "12345678Aa";
15 |
16 | protected IConsole? console;
17 | protected Dictionary configurations;
18 |
19 | public EnvironmentAppStartCommand(EnvironmentAppConfiguration environmentAppConfiguration)
20 | {
21 | configurations = environmentAppConfiguration.GetOptions();
22 | }
23 |
24 | public async ValueTask ExecuteAsync(IConsole console)
25 | {
26 | this.console = console;
27 |
28 | if (AppNames == null || AppNames.Length == 0)
29 | {
30 | console.Output.WriteLine("You must specify an app to run.\n" +
31 | "envapp start \n" +
32 | "Available app names:\n - " + string.Join("\n - ", configurations.Keys));
33 |
34 | return;
35 | }
36 |
37 | foreach (var appName in AppNames)
38 | {
39 | await StartAppAsync(appName);
40 | }
41 | }
42 |
43 | protected async Task StartAppAsync(string appName)
44 | {
45 | if (string.IsNullOrEmpty(appName))
46 | {
47 | await console!.Output.WriteAsync("App Name can't be null or empty.");
48 | return;
49 | }
50 |
51 | if (!configurations.TryGetValue(appName, out var option))
52 | {
53 | throw new CommandException($"ToolName '{appName}' couldn't be recognized. Try one of them: \n - " + string.Join("\n - ", configurations.Keys));
54 | }
55 |
56 | if (string.IsNullOrEmpty(DefaultPassword))
57 | {
58 | DefaultPassword = "12345678Aa";
59 | }
60 |
61 | await RunCommandAsync(option.StartCmd.Replace("Passw0rd", DefaultPassword));
62 | }
63 |
64 | protected async Task RunCommandAsync(string command)
65 | {
66 | var commands = command.Split(';');
67 |
68 | // Legacy support for old commands:
69 | if (commands.Length == 1 && commands[0].Contains(" || "))
70 | {
71 | commands = commands[0].Split(" || ");
72 | }
73 |
74 | foreach (var c in commands)
75 | {
76 | var fileName = c[..c.IndexOf(' ')];
77 |
78 | var process = Process.Start(fileName, c[c.IndexOf(' ')..]);
79 | await process.WaitForExitAsync();
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/AbpDevTools/Commands/EnvironmentAppStopCommand.cs:
--------------------------------------------------------------------------------
1 | using AbpDevTools.Configuration;
2 | using CliFx.Exceptions;
3 | using CliFx.Infrastructure;
4 | using System.Diagnostics;
5 |
6 | namespace AbpDevTools.Commands;
7 |
8 | [Command("envapp stop", Description = "Stops previously deployed environment app.")]
9 | public class EnvironmentAppStopCommand : ICommand
10 | {
11 | private readonly EnvironmentAppConfiguration environmentAppConfiguration;
12 |
13 | [CommandParameter(0, IsRequired = false , Description = "Name of the app.")]
14 | public string? AppName { get; set; }
15 |
16 | public EnvironmentAppStopCommand(EnvironmentAppConfiguration environmentAppConfiguration)
17 | {
18 | this.environmentAppConfiguration = environmentAppConfiguration;
19 | }
20 | public async ValueTask ExecuteAsync(IConsole console)
21 | {
22 | var configurations = environmentAppConfiguration.GetOptions();
23 |
24 | if (string.IsNullOrEmpty(AppName))
25 | {
26 | console.Output.WriteLine("You must specify an app to run.\n" +
27 | "envapp start \n" +
28 | "Available app names:\n - all\n" + string.Join("\n - ", configurations.Keys));
29 | return;
30 | }
31 |
32 | var cancellationToken = console.RegisterCancellationHandler();
33 |
34 | if (AppName.Equals("all", StringComparison.InvariantCultureIgnoreCase))
35 | {
36 | foreach (var config in configurations)
37 | {
38 | await console.Output.WriteLineAsync($"Stopping {config.Key}...");
39 |
40 | await StopWithCmdAsync(config.Value, cancellationToken);
41 |
42 | await console.Output.WriteLineAsync($"Stopped {config.Key}.");
43 | }
44 |
45 | return;
46 | }
47 |
48 | if (!configurations.TryGetValue(AppName, out var option))
49 | {
50 | throw new CommandException("App name couldn't be recognized. Try one of them: \n" + string.Join("\n - ", configurations.Keys));
51 | }
52 |
53 | if (option.StopCmd.StartsWith("docker "))
54 | {
55 | await StopWithCmdAsync(option, cancellationToken);
56 | }
57 | else
58 | {
59 | throw new CommandException($"Only docker apps supported currently. Your command can't be executed. \n'{option.StartCmd}'\n");
60 | }
61 | }
62 |
63 | private async Task StopWithCmdAsync(EnvironmentToolOption option, CancellationToken cancellationToken)
64 | {
65 | var commands = option.StopCmd.Split(';');
66 | foreach (var command in commands)
67 | {
68 | var process = Process.Start(
69 | "docker",
70 | command.Replace("docker ", string.Empty));
71 |
72 | await process.WaitForExitAsync(cancellationToken);
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/AbpDevTools/Commands/EnvironmentCommand.cs:
--------------------------------------------------------------------------------
1 | using CliFx.Infrastructure;
2 |
3 | namespace AbpDevTools.Commands;
4 |
5 | [Command("env", Description = "Virtual Environment. You can create virtual envionments and run your applications on pre-built virtual environments without changing any files on your computer.")]
6 | public class EnvironmentCommand : ICommand
7 | {
8 | public async ValueTask ExecuteAsync(IConsole console)
9 | {
10 | await console.Output.WriteLineAsync("-----------------------------------------------------------");
11 | await console.Output.WriteLineAsync("AbpDev Environment provides you to build virtual environemnts managed by \n\t'abpdev env config'");
12 | await console.Output.WriteLineAsync("\n\nUsage:\nIt's not used standalone. You should use created environments with other commands like:");
13 | await console.Output.WriteLineAsync("\t'abpdev run --env sqlserver'");
14 | await console.Output.WriteLineAsync("-----------------------------------------------------------");
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/AbpDevTools/Commands/EnvironmentConfigurationCommand.cs:
--------------------------------------------------------------------------------
1 | using AbpDevTools.Configuration;
2 | using CliFx.Infrastructure;
3 |
4 | namespace AbpDevTools.Commands;
5 |
6 | [Command("env config")]
7 | public class EnvironmentConfigurationCommand : ConfigurationBaseCommand
8 | {
9 | public EnvironmentConfigurationCommand(EnvironmentConfiguration configuration) : base(configuration)
10 | {
11 | }
12 |
13 | public override ValueTask ExecuteAsync(IConsole console)
14 | {
15 | Configuration.GetOptions();
16 | return base.ExecuteAsync(console);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/AbpDevTools/Commands/FindFileCommand.cs:
--------------------------------------------------------------------------------
1 | using CliFx.Infrastructure;
2 |
3 | namespace AbpDevTools.Commands;
4 |
5 | [Command("find-file", Description = "Finds the specified text in the solution.")]
6 | public class FindFileCommand : ICommand
7 | {
8 | protected FileExplorer FileExplorer { get; }
9 |
10 | [CommandOption("ascendant", 'a', Description = "Determined searching direction as 'Ascendant' or 'Descendants'.")]
11 | public bool Ascendant { get; set; } = false;
12 |
13 | [CommandParameter(0, Description = "Text to search.")]
14 | public string SearchTerm { get; set; } = string.Empty;
15 |
16 | [CommandParameter(1, Description = "Directory to search", IsRequired = false)]
17 | public string WorkingDirectory { get; set; } = string.Empty;
18 |
19 | public FindFileCommand(FileExplorer fileExplorer)
20 | {
21 | FileExplorer = fileExplorer;
22 | }
23 |
24 | public async ValueTask ExecuteAsync(IConsole console)
25 | {
26 | if (string.IsNullOrWhiteSpace(WorkingDirectory))
27 | {
28 | WorkingDirectory = Directory.GetCurrentDirectory();
29 | }
30 |
31 | foreach (var file in Find())
32 | {
33 | await console.Output.WriteLineAsync(file);
34 | }
35 | }
36 |
37 | IEnumerable Find()
38 | {
39 | if (Ascendant)
40 | {
41 | return FileExplorer.FindAscendants(WorkingDirectory, SearchTerm);
42 | }
43 | else
44 | {
45 | return FileExplorer.FindDescendants(WorkingDirectory, SearchTerm);
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/AbpDevTools/Commands/LogsClearCommand.cs:
--------------------------------------------------------------------------------
1 | using AbpDevTools.Configuration;
2 | using AbpDevTools.Services;
3 | using CliFx.Infrastructure;
4 | using Spectre.Console;
5 | using System;
6 |
7 | namespace AbpDevTools.Commands;
8 |
9 | [Command("logs clear")]
10 | public class LogsClearCommand : ICommand
11 | {
12 | [CommandParameter(0, IsRequired = false, Description = "Working directory to run build. Probably project or solution directory path goes here. Default: . (Current Directory)")]
13 | public string? WorkingDirectory { get; set; }
14 |
15 | [CommandOption("project", 'p', Description = "Determines the project to open logs of it.")]
16 | public string? ProjectName { get; set; }
17 |
18 | [CommandOption("interactive", 'i', Description = "Options will be asked as prompt when this option used.")]
19 | public bool Interactive { get; set; }
20 |
21 | [CommandOption("force", 'f')]
22 | public bool Force { get; set; }
23 |
24 | protected IConsole? console;
25 | protected readonly RunnableProjectsProvider runnableProjectsProvider;
26 |
27 | public LogsClearCommand(RunnableProjectsProvider runnableProjectsProvider)
28 | {
29 | this.runnableProjectsProvider = runnableProjectsProvider;
30 | }
31 |
32 | public async ValueTask ExecuteAsync(IConsole console)
33 | {
34 | this.console = console;
35 | if (string.IsNullOrEmpty(WorkingDirectory))
36 | {
37 | WorkingDirectory = Directory.GetCurrentDirectory();
38 | }
39 |
40 | var csprojs = runnableProjectsProvider.GetRunnableProjects(WorkingDirectory);
41 |
42 | if (string.IsNullOrEmpty(ProjectName))
43 | {
44 | if (Interactive)
45 | {
46 | await console.Output.WriteLineAsync($"\n");
47 | ProjectName = AnsiConsole.Prompt(
48 | new SelectionPrompt()
49 | .Title("Choose a [blueviolet]project[/] to open logs?")
50 | .PageSize(12)
51 | .HighlightStyle(new Style(foreground: Color.BlueViolet))
52 | .MoreChoicesText("[grey](Move up and down to reveal more rules)[/]")
53 | .AddChoices(csprojs.Select(s => s.Name)));
54 | }
55 | else
56 | {
57 | await console.Output.WriteLineAsync("You have to pass a project name.\n");
58 | await console.Output.WriteLineAsync("\n\tUsage:");
59 | await console.Output.WriteLineAsync("\tlogs -p ");
60 | await console.Output.WriteLineAsync("\nExample:\n\n\t" +
61 | "abpdev logs Web.csproj");
62 | return;
63 | }
64 | }
65 |
66 | if (ProjectName.Equals("all", StringComparison.InvariantCultureIgnoreCase))
67 | {
68 | foreach (var csproj in csprojs)
69 | {
70 | await DeleteCsprojLogsAsync(csproj);
71 | }
72 |
73 | return;
74 | }
75 |
76 | var selectedCsproj = csprojs.FirstOrDefault(x => x.FullName.Contains(ProjectName));
77 |
78 | if (selectedCsproj == null)
79 | {
80 | await console.Output.WriteLineAsync($"No project found with the name '{ProjectName}'");
81 | return;
82 | }
83 |
84 | await DeleteCsprojLogsAsync(selectedCsproj);
85 | }
86 |
87 | protected async Task DeleteCsprojLogsAsync(FileInfo csproj)
88 | {
89 | var dir = Path.GetDirectoryName(csproj.FullName)!;
90 | var logsDir = Path.Combine(dir, "Logs");
91 | if (Directory.Exists(logsDir))
92 | {
93 | var filePath = Path.Combine(logsDir, "logs.txt");
94 | if (File.Exists(filePath))
95 | {
96 | if (!Force && !AnsiConsole.Confirm($"{filePath} will be deleted. Are you sure?"))
97 | {
98 | return;
99 | }
100 |
101 | File.Delete(filePath);
102 | await console!.Output.WriteLineAsync($"{filePath} deleted.");
103 | return;
104 | }
105 | }
106 |
107 | await console!.Output.WriteLineAsync($"No logs found for {csproj.Name}");
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/AbpDevTools/Commands/LogsCommand.cs:
--------------------------------------------------------------------------------
1 | using AbpDevTools.Configuration;
2 | using AbpDevTools.Services;
3 | using CliFx.Infrastructure;
4 | using Spectre.Console;
5 |
6 | namespace AbpDevTools.Commands;
7 |
8 | [Command("logs")]
9 | public class LogsCommand : ICommand
10 | {
11 | [CommandParameter(0, Description = "Determines the project to open logs of it.", IsRequired = false)]
12 | public string? ProjectName { get; set; }
13 |
14 | [CommandOption("path", 'p', Description = "Working directory of the command. Probably solution directory. Default: . (CurrentDirectory) ")]
15 | public string? WorkingDirectory { get; set; }
16 |
17 | [CommandOption("interactive", 'i', Description = "Options will be asked as prompt when this option used.")]
18 | public bool Interactive { get; set; }
19 |
20 | protected readonly RunnableProjectsProvider runnableProjectsProvider;
21 | protected readonly Platform platform;
22 |
23 | public LogsCommand(RunnableProjectsProvider runnableProjectsProvider, Platform platform)
24 | {
25 | this.runnableProjectsProvider = runnableProjectsProvider;
26 | this.platform = platform;
27 | }
28 |
29 | public async ValueTask ExecuteAsync(IConsole console)
30 | {
31 | if (string.IsNullOrEmpty(WorkingDirectory))
32 | {
33 | WorkingDirectory = Directory.GetCurrentDirectory();
34 | }
35 |
36 | var csprojs = await AnsiConsole.Status()
37 | .StartAsync("Looking for projects...", async ctx =>
38 | {
39 | ctx.Spinner(Spinner.Known.SimpleDotsScrolling);
40 |
41 | await Task.Yield();
42 |
43 | var projects = runnableProjectsProvider.GetRunnableProjects(WorkingDirectory);
44 |
45 | AnsiConsole.MarkupLine($"[green]{projects.Length}[/] project files found.");
46 |
47 | return projects;
48 | });
49 |
50 | if (string.IsNullOrEmpty(ProjectName))
51 | {
52 | if (Interactive)
53 | {
54 | await console.Output.WriteLineAsync($"\n");
55 | ProjectName = AnsiConsole.Prompt(
56 | new SelectionPrompt()
57 | .Title("Choose a [mediumpurple2]project[/] to open logs?")
58 | .PageSize(12)
59 | .HighlightStyle(new Style(foreground: Color.MediumPurple2))
60 | .MoreChoicesText("[grey](Move up and down to reveal more rules)[/]")
61 | .AddChoices(csprojs.Select(s => s.Name)));
62 | }
63 | else
64 | {
65 | await console.Output.WriteLineAsync("You have to pass a project name.\n");
66 | await console.Output.WriteLineAsync("\n\tUsage:");
67 | await console.Output.WriteLineAsync("\tlogs -p ");
68 | await console.Output.WriteLineAsync("\nAvailable project names:\n\n\t - " +
69 | string.Join("\n\t - ", csprojs.Select(x => x.Name.Split(Path.DirectorySeparatorChar).Last())));
70 | return;
71 | }
72 | }
73 |
74 | var selectedCsproj = csprojs.FirstOrDefault(x => x.FullName.Contains(ProjectName));
75 |
76 | if (selectedCsproj == null)
77 | {
78 | await console.Output.WriteLineAsync($"No project found with the name '{ProjectName}'");
79 | return;
80 | }
81 |
82 | var dir = Path.GetDirectoryName(selectedCsproj.FullName)!;
83 | var logsDir = Path.Combine(dir, "Logs");
84 | if (Directory.Exists(logsDir))
85 | {
86 | var filePath = Path.Combine(logsDir, "logs.txt");
87 | if (File.Exists(filePath))
88 | {
89 | platform.Open(filePath);
90 | }
91 | else
92 | {
93 | platform.Open(logsDir);
94 | }
95 | }
96 | else
97 | {
98 | await console.Output.WriteLineAsync("No logs folder found for project.\nOpening project folder...");
99 |
100 | platform.Open(dir);
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/AbpDevTools/Commands/MigrateCommand.cs:
--------------------------------------------------------------------------------
1 | using AbpDevTools.Configuration;
2 | using AbpDevTools.Environments;
3 | using AbpDevTools.LocalConfigurations;
4 | using AbpDevTools.Notifications;
5 | using AbpDevTools.Services;
6 | using CliFx.Infrastructure;
7 | using Spectre.Console;
8 | using System.Diagnostics;
9 | using System.Text;
10 |
11 | namespace AbpDevTools.Commands;
12 |
13 | [Command("migrate", Description = "Runs all .DbMigrator projects in folder recursively.")]
14 | public class MigrateCommand : ICommand
15 | {
16 | [CommandParameter(0, IsRequired = false, Description = "Working directory to run build. Probably project or solution directory path goes here. Default: . (Current Directory)")]
17 | public string? WorkingDirectory { get; set; }
18 |
19 | [CommandOption("no-build", Description = "Skipts build before running. Passes '--no-build' parameter to dotnet run.")]
20 | public bool NoBuild { get; set; }
21 |
22 | [CommandOption("env", 'e', Description = "Uses the virtual environment for this process. Use 'abpdev env config' command to see/manage environments.")]
23 | public string? EnvironmentName { get; set; }
24 |
25 | protected readonly List runningProjects = new();
26 |
27 | protected IConsole? console;
28 |
29 | protected readonly INotificationManager notificationManager;
30 | protected readonly IProcessEnvironmentManager environmentManager;
31 | protected readonly ToolsConfiguration toolsConfiguration;
32 | protected readonly LocalConfigurationManager localConfigurationManager;
33 | protected readonly RunnableProjectsProvider runnableProjectsProvider;
34 |
35 | public MigrateCommand(INotificationManager notificationManager,
36 | IProcessEnvironmentManager environmentManager,
37 | ToolsConfiguration toolsConfiguration,
38 | LocalConfigurationManager localConfigurationManager,
39 | RunnableProjectsProvider runnableProjectsProvider)
40 | {
41 | this.notificationManager = notificationManager;
42 | this.environmentManager = environmentManager;
43 | this.toolsConfiguration = toolsConfiguration;
44 | this.localConfigurationManager = localConfigurationManager;
45 | this.runnableProjectsProvider = runnableProjectsProvider;
46 | }
47 |
48 | public async ValueTask ExecuteAsync(IConsole console)
49 | {
50 | this.console = console;
51 | if (string.IsNullOrEmpty(WorkingDirectory))
52 | {
53 | WorkingDirectory = Directory.GetCurrentDirectory();
54 | }
55 |
56 | var dbMigrators = Directory.EnumerateFiles(WorkingDirectory, "*.csproj", SearchOption.AllDirectories)
57 | .Where(IsDbMigrator)
58 | .Select(x => new FileInfo(x))
59 | .ToList();
60 |
61 | var cancellationToken = console.RegisterCancellationHandler();
62 |
63 | if (dbMigrators.Count == 0)
64 | {
65 | await console.Output.WriteLineAsync($"No migrator(s) found in this folder. Migration not applied.");
66 | await RunParameterMigrationFallbackAsync();
67 | return;
68 | }
69 |
70 | await console.Output.WriteLineAsync($"{dbMigrators.Count} db migrator(s) found.");
71 |
72 | var commandPostFix = NoBuild ? " --no-build" : string.Empty;
73 |
74 | foreach (var dbMigrator in dbMigrators)
75 | {
76 | var tools = toolsConfiguration.GetOptions();
77 | var startInfo = new ProcessStartInfo(tools["dotnet"], $"run --project \"{dbMigrator.FullName}\"" + commandPostFix)
78 | {
79 | WorkingDirectory = Path.GetDirectoryName(dbMigrator.FullName),
80 | RedirectStandardOutput = true,
81 | };
82 |
83 | localConfigurationManager.ApplyLocalEnvironmentForProcess(dbMigrator.FullName, startInfo);
84 |
85 | if (!string.IsNullOrEmpty(EnvironmentName))
86 | {
87 | environmentManager.SetEnvironmentForProcess(EnvironmentName, startInfo);
88 | }
89 |
90 | var process = Process.Start(startInfo)!;
91 |
92 | runningProjects.Add(new RunningProjectItem
93 | {
94 | Name = dbMigrator.Name,
95 | Process = process,
96 | Status = "Running..."
97 | });
98 | }
99 |
100 | await console.Output.WriteAsync("Waiting for db migrators to finish...");
101 | cancellationToken.Register(KillRunningProcesses);
102 |
103 | await RenderStatusAsync();
104 |
105 | if (!cancellationToken.IsCancellationRequested)
106 | {
107 | await console.Output.WriteLineAsync("Migrations finished.");
108 | await notificationManager.SendAsync("Migration Completed", $"Complated migrations in {WorkingDirectory}");
109 | }
110 |
111 | KillRunningProcesses();
112 | }
113 |
114 | protected async Task RunParameterMigrationFallbackAsync()
115 | {
116 | if (!AnsiConsole.Confirm("Do you want to run any of projects in this folder with '--migrate-database' parameter?"))
117 | {
118 | return;
119 | }
120 |
121 | FileInfo[] csprojs = await AnsiConsole.Status()
122 | .StartAsync("Looking for projects", async ctx =>
123 | {
124 | ctx.Spinner(Spinner.Known.SimpleDotsScrolling);
125 |
126 | await Task.Yield();
127 |
128 | return runnableProjectsProvider.GetRunnableProjects(WorkingDirectory);
129 | });
130 |
131 | if (csprojs.Length <= 0)
132 | {
133 | await console.Output.WriteLineAsync("No project found to run.");
134 | return;
135 | }
136 |
137 | if(csprojs.Length == 1)
138 | {
139 | await console.Output.WriteLineAsync("Only one project found. Running it with '--migrate-database' parameter.");
140 | await RunProjectWithMigrateDatabaseAsync(csprojs[0]);
141 | }
142 | else{
143 |
144 | var selectedProject = AnsiConsole.Prompt(
145 | new SelectionPrompt()
146 | .Title("Select a project to run with '--migrate-database' parameter")
147 | .PageSize(10)
148 | .AddChoices(csprojs)
149 | );
150 |
151 | await RunProjectWithMigrateDatabaseAsync(selectedProject);
152 | }
153 |
154 | await RenderStatusAsync();
155 | }
156 |
157 | protected Task RunProjectWithMigrateDatabaseAsync(FileInfo project)
158 | {
159 | var tools = toolsConfiguration.GetOptions();
160 | var startInfo = new ProcessStartInfo(tools["dotnet"], $"run --project \"{project.FullName}\" -- --migrate-database")
161 | {
162 | WorkingDirectory = Path.GetDirectoryName(project.FullName),
163 | RedirectStandardOutput = true,
164 | };
165 |
166 | localConfigurationManager.ApplyLocalEnvironmentForProcess(project.FullName, startInfo);
167 |
168 | if (!string.IsNullOrEmpty(EnvironmentName))
169 | {
170 | environmentManager.SetEnvironmentForProcess(EnvironmentName, startInfo);
171 | }
172 |
173 | var process = Process.Start(startInfo)!;
174 |
175 | runningProjects.Add(new RunningProjectItem
176 | {
177 | Name = project.Name,
178 | Process = process,
179 | Status = "Running..."
180 | });
181 |
182 | return Task.CompletedTask;
183 | }
184 |
185 | private bool IsDbMigrator(string file)
186 | {
187 | if (!file.EndsWith("Migrator.csproj", StringComparison.InvariantCultureIgnoreCase))
188 | {
189 | return false;
190 | }
191 |
192 | using var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read);
193 | using var streamReader = new StreamReader(fileStream, Encoding.UTF8, true);
194 |
195 | while (!streamReader.EndOfStream)
196 | {
197 | var line = streamReader.ReadLine();
198 |
199 | if (line == null)
200 | {
201 | continue;
202 | }
203 |
204 | if (line.Contains("Exe"))
205 | {
206 | return true;
207 | }
208 |
209 | if (line.Contains(""))
210 | {
211 | break;
212 | }
213 | }
214 |
215 | return false;
216 | }
217 |
218 | private async Task RenderStatusAsync()
219 | {
220 | var table = new Table().Border(TableBorder.Ascii);
221 |
222 | AnsiConsole.WriteLine(Environment.NewLine);
223 | await AnsiConsole.Live(table)
224 | .StartAsync(async ctx =>
225 | {
226 | table.AddColumn("Project");
227 | table.AddColumn("Status");
228 |
229 | UpdateTable(table);
230 | ctx.UpdateTarget(table);
231 |
232 | foreach (var runningProject in runningProjects)
233 | {
234 | runningProject.Process!.OutputDataReceived += (sender, args) =>
235 | {
236 | if (args?.Data != null && args.Data.Length < 90)
237 | {
238 | runningProject.Status = args.Data[args.Data.IndexOf(']')..].Replace('[', '\0').Replace(']', '\0');
239 | UpdateTable(table);
240 | ctx.UpdateTarget(table);
241 | }
242 | };
243 | runningProject.Process.BeginOutputReadLine();
244 | }
245 |
246 | await Task.WhenAll(runningProjects.Select(x => x.Process!.WaitForExitAsync()));
247 | });
248 | }
249 |
250 | private void UpdateTable(Table table)
251 | {
252 | table.Rows.Clear();
253 | foreach (var runningProject in runningProjects)
254 | {
255 | table.AddRow(
256 | runningProject.Name!,
257 | runningProject.Status!);
258 | }
259 | }
260 |
261 | protected void KillRunningProcesses()
262 | {
263 | console!.Output.WriteLine($"- Killing running {runningProjects.Count} processes...");
264 | foreach (var project in runningProjects)
265 | {
266 | project.Process?.Kill(entireProcessTree: true);
267 |
268 | project.Process?.WaitForExit();
269 | }
270 | }
271 | }
272 |
--------------------------------------------------------------------------------
/src/AbpDevTools/Commands/Migrations/AddMigrationCommand.cs:
--------------------------------------------------------------------------------
1 | using AbpDevTools.Services;
2 | using CliFx.Infrastructure;
3 | using Spectre.Console;
4 | using System.Diagnostics;
5 |
6 | namespace AbpDevTools.Commands.Migrations;
7 |
8 | [Command("migrations add", Description = "Adds migration with specified name in EntityFrameworkCore project(s). Used to add bulk migrations to multiple project at the same time.")]
9 | public class AddMigrationCommand : MigrationsCommandBase, ICommand
10 | {
11 | [CommandOption("name", 'n', Description = "Name of the migration.")]
12 | public string Name { get; set; } = "Initial";
13 |
14 | public List RunningProgresses { get; } = new();
15 |
16 |
17 | public AddMigrationCommand(EntityFrameworkCoreProjectsProvider entityFrameworkCoreProjectsProvider) : base(entityFrameworkCoreProjectsProvider)
18 | {
19 | }
20 |
21 | public override async ValueTask ExecuteAsync(IConsole console)
22 | {
23 | await base.ExecuteAsync(console);
24 |
25 | var cancellationToken = console.RegisterCancellationHandler();
26 |
27 | var projectFiles = await ChooseProjectsAsync();
28 |
29 | if (projectFiles.Length == 0)
30 | {
31 | await console.Output.WriteLineAsync("No EF Core projects found. No migrations to add.");
32 | return;
33 | }
34 |
35 | foreach (var project in projectFiles)
36 | {
37 | var arguments = $"migrations add {Name} --project {project.FullName}";
38 | var process = new Process
39 | {
40 | StartInfo = new ProcessStartInfo("dotnet-ef", arguments)
41 | {
42 | WorkingDirectory = WorkingDirectory,
43 | RedirectStandardOutput = true,
44 | RedirectStandardError = true,
45 | }
46 | };
47 |
48 | var projectName = Path.GetFileNameWithoutExtension(project.Name);
49 | RunningProgresses.Add(new RunningProgressItem(process!, projectName, "Running..."));
50 | }
51 |
52 | cancellationToken.Register(KillAllProcesses);
53 | await RenderProgressesAsync(cancellationToken);
54 | }
55 |
56 | private async Task RenderProgressesAsync(CancellationToken cancellationToken)
57 | {
58 | var table = new Table().Border(TableBorder.Ascii)
59 | .AddColumn("Project")
60 | .AddColumn("Status")
61 | .AddColumn("Result");
62 |
63 | await AnsiConsole.Live(table).StartAsync(async ctx =>
64 | {
65 | while (!cancellationToken.IsCancellationRequested)
66 | {
67 | RenderProgresses(table);
68 | await Task.Delay(500, cancellationToken);
69 |
70 | ctx.Refresh();
71 |
72 | if (RunningProgresses.All(p => !p.IsRunning))
73 | {
74 | break;
75 | }
76 | }
77 |
78 | await Task.WhenAll(RunningProgresses.Select(p => p.Process.WaitForExitAsync()));
79 |
80 | RenderProgresses(table);
81 | ctx.Refresh();
82 | });
83 | }
84 |
85 | private async void RenderProgresses(Table table)
86 | {
87 | table.Rows.Clear();
88 | foreach (var progress in RunningProgresses)
89 | {
90 | if (progress.Process.HasExited)
91 | {
92 | progress.Status = progress.ExitCode == 0 ? "Completed!"
93 | : $"Failed! ( Exit Code: {progress.ExitCode})";
94 | }
95 |
96 | table.AddRow(progress.Name, progress.Status, progress.LastLine);
97 | }
98 | }
99 |
100 | protected void KillAllProcesses()
101 | {
102 | foreach (var progress in RunningProgresses)
103 | {
104 | if (progress.IsRunning)
105 | {
106 | progress.Process.Kill(entireProcessTree: true);
107 | }
108 | }
109 | }
110 | }
111 |
112 | public class RunningProgressItem
113 | {
114 | public RunningProgressItem(Process process, string name, string initialStatus)
115 | {
116 | Process = process;
117 | Name = name;
118 | Status = initialStatus;
119 | process.OutputDataReceived += OutputReceived;
120 | process.Start();
121 | process.BeginOutputReadLine();
122 | }
123 |
124 | private void OutputReceived(object sender, DataReceivedEventArgs e)
125 | {
126 | if (e.Data != null)
127 | {
128 | Output += e.Data + Environment.NewLine;
129 | LastLine = e.Data;
130 | }
131 | }
132 |
133 | public string Name { get; set; }
134 |
135 | public Process Process { get; set; }
136 |
137 | public string Status { get; set; } = string.Empty;
138 |
139 | public string Output { get; set; } = string.Empty;
140 |
141 | public string LastLine { get; set; } = string.Empty;
142 |
143 | public int ExitCode => Process.HasExited ? Process.ExitCode : 0;
144 |
145 | public bool IsRunning => Process.HasExited == false;
146 | }
147 |
--------------------------------------------------------------------------------
/src/AbpDevTools/Commands/Migrations/ClearMigrationsCommand.cs:
--------------------------------------------------------------------------------
1 | using AbpDevTools.Services;
2 | using CliFx.Infrastructure;
3 | using Spectre.Console;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 |
10 | namespace AbpDevTools.Commands.Migrations;
11 |
12 | [Command("migrations clear", Description = "Clears 'Migrations' folders from EntityFrameworkCore projects.")]
13 | public class ClearMigrationsCommand : MigrationsCommandBase, ICommand
14 | {
15 | public ClearMigrationsCommand(EntityFrameworkCoreProjectsProvider entityFrameworkCoreProjectsProvider) : base(entityFrameworkCoreProjectsProvider)
16 | {
17 | }
18 |
19 | public override async ValueTask ExecuteAsync(IConsole console)
20 | {
21 | await base.ExecuteAsync(console);
22 |
23 | var projectFiles = await ChooseProjectsAsync();
24 |
25 | if (projectFiles.Length == 0)
26 | {
27 | await console.Output.WriteLineAsync("No EF Core projects found. No migrations to add.");
28 | return;
29 | }
30 |
31 | await AnsiConsole.Status().StartAsync("Clearing migrations...", async ctx =>
32 | {
33 | ctx.Spinner(Spinner.Known.SimpleDotsScrolling);
34 |
35 | foreach (var project in projectFiles)
36 | {
37 | var migrationsFolder = Path.Combine(Path.GetDirectoryName(project.FullName)!, "Migrations");
38 | if (Directory.Exists(migrationsFolder))
39 | {
40 | Directory.Delete(migrationsFolder, true);
41 | AnsiConsole.MarkupLine($"[green]Cleared[/] migrations of [bold]{Path.GetFileNameWithoutExtension(project.Name)}[/]");
42 | }
43 | else
44 | {
45 | AnsiConsole.MarkupLine($"No migrations found in [bold]{Path.GetFileNameWithoutExtension(project.Name)}[/]");
46 | }
47 | }
48 | });
49 |
50 | await console.Output.WriteLineAsync("Migrations cleared.");
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/AbpDevTools/Commands/Migrations/MigrationsCommand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using CliFx.Infrastructure;
3 |
4 | namespace AbpDevTools.Commands.Migrations;
5 |
6 | [Command("migrations", Description = "Manages EntityFrameworkCore migrations in multiple projects.")]
7 | public class MigrationsCommand : ICommand
8 | {
9 | public async ValueTask ExecuteAsync(IConsole console)
10 | {
11 | await console.Output.WriteLineAsync("Specify a subcommand.Available subcommands:\n\n");
12 |
13 | await console.Output.WriteLineAsync("\tabpdev add");
14 | await console.Output.WriteLineAsync("\tabpdev clear");
15 |
16 | await console.Output.WriteLineAsync("\n\nExample:");
17 | await console.Output.WriteLineAsync("\tabpdev migrations add --name Initial");
18 | await console.Output.WriteLineAsync("\tabpdev migrations clear");
19 |
20 | await console.Output.WriteLineAsync("\n\nGet Help:");
21 | await console.Output.WriteLineAsync("\tabpdev migrations --help");
22 | await console.Output.WriteLineAsync("\tabpdev migrations add --help");
23 |
24 | return;
25 |
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/AbpDevTools/Commands/Migrations/MigrationsCommandBase.cs:
--------------------------------------------------------------------------------
1 | using AbpDevTools.Services;
2 | using CliFx.Infrastructure;
3 | using Spectre.Console;
4 |
5 | namespace AbpDevTools.Commands.Migrations;
6 | public abstract class MigrationsCommandBase : ICommand
7 | {
8 | [CommandOption("all", 'a', Description = "Run the command for all the EF Core projects.")]
9 | public bool RunAll { get; set; }
10 |
11 | [CommandParameter(0, IsRequired = false, Description = "Working directory to run build. Probably project or solution directory path goes here. Default: . (Current Directory)")]
12 | public string? WorkingDirectory { get; set; }
13 |
14 | [CommandOption("projects", 'p', Description = "(Array) Names or part of names of projects will be ran.")]
15 | public string[] Projects { get; set; } = Array.Empty();
16 |
17 | protected readonly EntityFrameworkCoreProjectsProvider entityFrameworkCoreProjectsProvider;
18 |
19 | protected MigrationsCommandBase(EntityFrameworkCoreProjectsProvider entityFrameworkCoreProjectsProvider)
20 | {
21 | this.entityFrameworkCoreProjectsProvider = entityFrameworkCoreProjectsProvider;
22 | }
23 |
24 | public virtual ValueTask ExecuteAsync(IConsole console)
25 | {
26 | if (string.IsNullOrEmpty(WorkingDirectory))
27 | {
28 | WorkingDirectory = Directory.GetCurrentDirectory();
29 | }
30 |
31 | return default;
32 | }
33 |
34 | protected async Task ChooseProjectsAsync()
35 | {
36 | var projectFiles = GetEfCoreProjects();
37 |
38 | if (projectFiles.Length == 0)
39 | {
40 | return Array.Empty();
41 | }
42 |
43 | if (Projects.Length > 0)
44 | {
45 | projectFiles = projectFiles.Where(pf => Projects.Any(a => pf.FullName.Contains(a))).ToArray();
46 | }
47 | else if (!RunAll && projectFiles.Length > 0)
48 | {
49 | var chosenProjects = AnsiConsole.Prompt(new MultiSelectionPrompt()
50 | .Title("Choose project to create migrations.")
51 | .Required(true)
52 | .PageSize(12)
53 | .HighlightStyle(new Style(foreground: Color.MediumPurple2))
54 | .MoreChoicesText("[grey](Move up and down to reveal more projects)[/]")
55 | .InstructionsText(
56 | "[grey](Press [mediumpurple2][/] to toggle a project, " +
57 | "[green][/] to accept)[/]")
58 | .AddChoices(projectFiles
59 | .Select(p => Path.GetDirectoryName(p.FullName.Replace(WorkingDirectory, string.Empty)).Trim('\\'))
60 | .ToArray())
61 | );
62 |
63 | projectFiles = projectFiles.Where(p => chosenProjects.Any(cp => p.FullName.Contains(cp))).ToArray();
64 | }
65 |
66 | return projectFiles;
67 | }
68 |
69 | FileInfo[] GetEfCoreProjects()
70 | {
71 | return entityFrameworkCoreProjectsProvider.GetEfCoreProjects(WorkingDirectory!);
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/AbpDevTools/Commands/ReplaceCommand.cs:
--------------------------------------------------------------------------------
1 | using AbpDevTools.Configuration;
2 | using CliFx.Infrastructure;
3 | using Spectre.Console;
4 | using System.Text.RegularExpressions;
5 |
6 | namespace AbpDevTools.Commands;
7 |
8 | [Command("replace", Description = "Runs file replacement according to configuration.")]
9 | public class ReplaceCommand : ICommand
10 | {
11 | [CommandOption("path", 'p', Description = "Working directory of the command. Probably solution directory. Default: . (CurrentDirectory) ")]
12 | public string? WorkingDirectory { get; set; }
13 |
14 | [CommandParameter(0, IsRequired = false, Description = "If you execute single option from config, you can pass the name or pass 'all' to execute all of them")]
15 | public string? ReplacementConfigName { get; set; }
16 |
17 | [CommandOption("interactive", 'i', Description = "Interactive Mode. It'll ask prompt to pick one config.")]
18 | public bool InteractiveMode { get; set; }
19 |
20 | private readonly ReplacementConfiguration replacementConfiguration;
21 |
22 | public ReplaceCommand(ReplacementConfiguration replacementConfiguration)
23 | {
24 | this.replacementConfiguration = replacementConfiguration;
25 | }
26 |
27 | public async ValueTask ExecuteAsync(IConsole console)
28 | {
29 | WorkingDirectory ??= Directory.GetCurrentDirectory();
30 |
31 | var options = replacementConfiguration.GetOptions();
32 |
33 | if (string.IsNullOrEmpty(ReplacementConfigName))
34 | {
35 | if (InteractiveMode)
36 | {
37 | await console.Output.WriteLineAsync($"\n");
38 | ReplacementConfigName = AnsiConsole.Prompt(
39 | new SelectionPrompt()
40 | .Title("Choose a [mediumpurple2]rule[/] to execute?")
41 | .PageSize(12)
42 | .HighlightStyle(new Style(foreground: Color.MediumPurple2))
43 | .MoreChoicesText("[grey](Move up and down to reveal more rules)[/]")
44 | .AddChoices(options.Keys));
45 | }
46 | else
47 | {
48 | console.Output.WriteLine("You should specify a execution rule name.\n");
49 | console.Output.WriteLine("\tUse 'replace ' to execute a rule");
50 | console.Output.WriteLine("\tUse 'replace all' to execute a rules");
51 | console.Output.WriteLine("\tUse 'replace config' to manage rules.\n\n");
52 | console.Output.WriteLine("Available execution rules:\n\n\t - " + string.Join("\n\t - ", options.Keys));
53 | return;
54 | }
55 | }
56 |
57 | if (ReplacementConfigName.Equals("all", StringComparison.InvariantCultureIgnoreCase))
58 | {
59 | foreach (var item in options)
60 | {
61 | await ExecuteConfigAsync(item.Key, item.Value);
62 | }
63 |
64 | return;
65 | }
66 |
67 | if (!string.IsNullOrEmpty(ReplacementConfigName))
68 | {
69 | if (!options.TryGetValue(ReplacementConfigName, out var option))
70 | {
71 | console.ForegroundColor = ConsoleColor.Red;
72 | await console.Error.WriteLineAsync($"No replacement config found with name '{ReplacementConfigName}'");
73 | console.ResetColor();
74 |
75 | await console.Output.WriteLineAsync("Available configurations: " + string.Join(',', options.Keys));
76 | await console.Output.WriteLineAsync("Check existing configurations with 'abpdev config' command.");
77 | return;
78 | }
79 | await ExecuteConfigAsync(ReplacementConfigName, option);
80 | return;
81 | }
82 | }
83 |
84 | protected virtual async ValueTask ExecuteConfigAsync(string configurationName, ReplacementOption option)
85 | {
86 | await AnsiConsole.Status()
87 | .StartAsync($"Executing...", async ctx =>
88 | {
89 | AnsiConsole.MarkupLine($"Executing [blue]'{configurationName}'[/] replacement configuration...");
90 |
91 | await Task.Yield();
92 |
93 | ctx.Status($"[blue]{option.FilePattern}[/] file pattern executing.");
94 | var files = Directory.EnumerateFiles(WorkingDirectory!, "*.*", SearchOption.AllDirectories)
95 | .Where(x => Regex.IsMatch(x, option.FilePattern))
96 | .ToList();
97 |
98 | ctx.Status($"[green]{files.Count}[/] file(s) found with pattern.");
99 |
100 | int affectedFileCount = 0;
101 | foreach (var file in files)
102 | {
103 | var text = File.ReadAllText(file);
104 |
105 | if (text.Contains(option.Find))
106 | {
107 | File.WriteAllText(file, text.Replace(option.Find, option.Replace));
108 | ctx.Status($"{file} updated.");
109 | affectedFileCount++;
110 | }
111 | }
112 | AnsiConsole.MarkupLine($"Totally [green]{affectedFileCount}[/] files updated.");
113 | });
114 |
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/src/AbpDevTools/Commands/RunCommand.cs:
--------------------------------------------------------------------------------
1 | using AbpDevTools.Configuration;
2 | using AbpDevTools.Environments;
3 | using AbpDevTools.LocalConfigurations;
4 | using AbpDevTools.Notifications;
5 | using AbpDevTools.Services;
6 | using CliFx.Infrastructure;
7 | using Spectre.Console;
8 | using System.Diagnostics;
9 |
10 | namespace AbpDevTools.Commands;
11 |
12 | [Command("run", Description = "Run all the required applications")]
13 | public partial class RunCommand : ICommand
14 | {
15 | [CommandParameter(0, IsRequired = false, Description = "Working directory to run build. Probably project or solution directory path goes here. Default: . (Current Directory)")]
16 | public string? WorkingDirectory { get; set; }
17 |
18 | [CommandOption("watch", 'w', Description = "Watch mode")]
19 | public bool Watch { get; set; }
20 |
21 | [CommandOption("skip-migrate", Description = "Skips migration and runs projects directly.")]
22 | public bool SkipMigration { get; set; }
23 |
24 | [CommandOption("all", 'a', Description = "Projects to run will not be asked as prompt. All of them will run.")]
25 | public bool RunAll { get; set; }
26 |
27 | [CommandOption("no-build", Description = "Skips build before running. Passes '--no-build' parameter to dotnet run.")]
28 | public bool NoBuild { get; set; }
29 |
30 | [CommandOption("install-libs", 'i', Description = "Runs 'abp install-libs' command while running the project simultaneously.")]
31 | public bool InstallLibs { get; set; }
32 |
33 | [CommandOption("graphBuild", 'g', Description = "Uses /graphBuild while running the applications. So no need building before running. But it may cause some performance.")]
34 | public bool GraphBuild { get; set; }
35 |
36 | [CommandOption("projects", 'p', Description = "(Array) Names or part of names of projects will be ran.")]
37 | public string[] Projects { get; set; } = Array.Empty();
38 |
39 | [CommandOption("configuration", 'c')]
40 | public string? Configuration { get; set; }
41 |
42 | [CommandOption("env", 'e', Description = "Uses the virtual environment for this process. Use 'abpdev env config' command to see/manage environments.")]
43 | public string? EnvironmentName { get; set; }
44 |
45 | [CommandOption("retry", 'r', Description = "Retries running again when application exits.")]
46 | public bool Retry { get; set; }
47 |
48 | [CommandOption("verbose", 'v', Description = "Shows verbose output from the projects.")]
49 | public bool Verbose { get; set; }
50 |
51 | [CommandOption("yml", Description = "Path to the yml file to be used for running the project.")]
52 | public string? YmlPath { get; set; }
53 |
54 | protected IConsole? console;
55 |
56 | protected readonly List runningProjects = new();
57 |
58 | protected readonly INotificationManager notificationManager;
59 | protected readonly MigrateCommand migrateCommand;
60 | protected readonly IProcessEnvironmentManager environmentManager;
61 | protected readonly UpdateCheckCommand updateCheckCommand;
62 | protected readonly RunnableProjectsProvider runnableProjectsProvider;
63 | protected readonly ToolsConfiguration toolsConfiguration;
64 | protected readonly FileExplorer fileExplorer;
65 | private readonly LocalConfigurationManager localConfigurationManager;
66 |
67 | public RunCommand(
68 | INotificationManager notificationManager,
69 | MigrateCommand migrateCommand,
70 | IProcessEnvironmentManager environmentManager,
71 | UpdateCheckCommand updateCheckCommand,
72 | RunnableProjectsProvider runnableProjectsProvider,
73 | ToolsConfiguration toolsConfiguration,
74 | FileExplorer fileExplorer,
75 | LocalConfigurationManager localConfigurationManager)
76 | {
77 | this.notificationManager = notificationManager;
78 | this.migrateCommand = migrateCommand;
79 | this.environmentManager = environmentManager;
80 | this.updateCheckCommand = updateCheckCommand;
81 | this.runnableProjectsProvider = runnableProjectsProvider;
82 | this.toolsConfiguration = toolsConfiguration;
83 | this.fileExplorer = fileExplorer;
84 | this.localConfigurationManager = localConfigurationManager;
85 | }
86 |
87 | public async ValueTask ExecuteAsync(IConsole console)
88 | {
89 | this.console = console;
90 | if (string.IsNullOrEmpty(WorkingDirectory))
91 | {
92 | WorkingDirectory = Directory.GetCurrentDirectory();
93 | }
94 |
95 | if (string.IsNullOrEmpty(YmlPath))
96 | {
97 | YmlPath = Path.Combine(WorkingDirectory, "abpdev.yml");
98 | }
99 |
100 | var cancellationToken = console.RegisterCancellationHandler();
101 |
102 | if (localConfigurationManager.TryLoad(YmlPath!, out var localRootConfig, FileSearchDirection.OnlyCurrent))
103 | {
104 | console.Output.WriteLine($"Loaded YAML configuration from '{YmlPath}' with environment '{localRootConfig?.Environment?.Name ?? "Default"}'.");
105 | }
106 |
107 | FileInfo[] csprojs = await AnsiConsole.Status()
108 | .StartAsync("Looking for projects", async ctx =>
109 | {
110 | ctx.Spinner(Spinner.Known.SimpleDotsScrolling);
111 |
112 | await Task.Yield();
113 |
114 | return runnableProjectsProvider.GetRunnableProjects(WorkingDirectory);
115 | });
116 |
117 | await console.Output.WriteLineAsync($"{csprojs.Length} csproj file(s) found.");
118 |
119 | if (!SkipMigration && localRootConfig?.Run?.SkipMigrate != true)
120 | {
121 | migrateCommand.WorkingDirectory = this.WorkingDirectory;
122 | migrateCommand.NoBuild = this.NoBuild;
123 | migrateCommand.EnvironmentName = this.EnvironmentName;
124 |
125 | await migrateCommand.ExecuteAsync(console);
126 | }
127 |
128 | await console.Output.WriteLineAsync("Starting projects...");
129 |
130 | var projectFiles = csprojs.Where(x => !x.Name.Contains(".DbMigrator")).ToArray();
131 |
132 | if (!RunAll && projectFiles.Length > 1)
133 | {
134 | await console.Output.WriteLineAsync($"\n");
135 |
136 | ApplyLocalProjects(localRootConfig);
137 |
138 | if (Projects.Length == 0)
139 | {
140 | var choosedProjects = AnsiConsole.Prompt(
141 | new MultiSelectionPrompt()
142 | .Title("Choose [mediumpurple2]projects[/] to run.")
143 | .Required(true)
144 | .PageSize(12)
145 | .HighlightStyle(new Style(foreground: Color.MediumPurple2))
146 | .MoreChoicesText("[grey](Move up and down to reveal more projects)[/]")
147 | .InstructionsText(
148 | "[grey](Press [mediumpurple2][/] to toggle a project, " +
149 | "[green][/] to accept)[/]")
150 | .AddChoices(projectFiles.Select(s => s.Name)));
151 |
152 | projectFiles = projectFiles.Where(x => choosedProjects.Contains(x.Name)).ToArray();
153 | }
154 | else
155 | {
156 | projectFiles = projectFiles.Where(x => Projects.Any(y => x.FullName.Contains(y, StringComparison.InvariantCultureIgnoreCase))).ToArray();
157 | }
158 | }
159 |
160 | foreach (var csproj in projectFiles)
161 | {
162 | localConfigurationManager.TryLoad(csproj.FullName, out var localConfiguration);
163 |
164 | var commandPrefix = BuildCommandPrefix(localConfiguration?.Run?.Watch);
165 | var commandSuffix = BuildCommandSuffix(
166 | localConfiguration?.Run?.NoBuild,
167 | localConfiguration?.Run?.GraphBuild,
168 | localConfiguration?.Run?.Configuration);
169 |
170 | var tools = toolsConfiguration.GetOptions();
171 | var startInfo = new ProcessStartInfo(tools["dotnet"], commandPrefix + $"run --project \"{csproj.FullName}\"" + commandSuffix)
172 | {
173 | WorkingDirectory = Path.GetDirectoryName(csproj.FullName),
174 | UseShellExecute = false,
175 | RedirectStandardOutput = true,
176 | RedirectStandardError = true,
177 | };
178 |
179 | localConfigurationManager.ApplyLocalEnvironmentForProcess(csproj.FullName, startInfo, localConfiguration);
180 |
181 | if (!string.IsNullOrEmpty(EnvironmentName))
182 | {
183 | environmentManager.SetEnvironmentForProcess(EnvironmentName, startInfo);
184 | }
185 |
186 | runningProjects.Add(
187 | new RunningCsProjItem(
188 | csproj.Name,
189 | Process.Start(startInfo)!,
190 | verbose: Verbose
191 | )
192 | );
193 |
194 | if (InstallLibs)
195 | {
196 | var wwwRootLibs = Path.Combine(Path.GetDirectoryName(csproj.FullName)!, "wwwroot/libs");
197 | if (!Directory.Exists(wwwRootLibs))
198 | {
199 | Directory.CreateDirectory(wwwRootLibs);
200 | }
201 |
202 | if (!Directory.EnumerateFiles(wwwRootLibs).Any())
203 | {
204 | File.WriteAllText(Path.Combine(wwwRootLibs, "abplibs.installing"), string.Empty);
205 | }
206 |
207 | var installLibsRunninItem = new RunningInstallLibsItem(
208 | csproj.Name.Replace(".csproj", " install-libs"),
209 | Process.Start(new ProcessStartInfo(tools["abp"], "install-libs")
210 | {
211 | WorkingDirectory = Path.GetDirectoryName(csproj.FullName),
212 | UseShellExecute = false,
213 | RedirectStandardOutput = true,
214 | })!
215 | );
216 |
217 | runningProjects.Add(installLibsRunninItem);
218 | }
219 | }
220 |
221 | cancellationToken.Register(KillRunningProcesses);
222 |
223 | await RenderProcesses(cancellationToken);
224 |
225 | await updateCheckCommand.SoftCheckAsync(console);
226 | }
227 |
228 | private void ApplyLocalProjects(LocalConfiguration? localConfiguration)
229 | {
230 | if(localConfiguration is not null)
231 | {
232 | if (Projects.Length == 0 && localConfiguration?.Run?.Projects.Length > 0)
233 | {
234 | Projects = localConfiguration.Run.Projects;
235 | }
236 | }
237 | }
238 |
239 | private string BuildCommandSuffix(bool? noBuild = null, bool? graphBuild = null, string? configuration = null)
240 | {
241 | var commandSuffix = (NoBuild || noBuild == true) ? " --no-build" : string.Empty;
242 |
243 | if (GraphBuild || graphBuild == true)
244 | {
245 | commandSuffix += " /graphBuild";
246 | }
247 |
248 | if (configuration != null)
249 | {
250 | commandSuffix += $" --configuration {configuration}";
251 | }
252 | else if (!string.IsNullOrEmpty(Configuration))
253 | {
254 | commandSuffix += $" --configuration {Configuration}";
255 | }
256 |
257 | return commandSuffix;
258 | }
259 |
260 | private string BuildCommandPrefix(bool? watchOverride)
261 | {
262 | if (watchOverride is not null)
263 | {
264 | return watchOverride.Value ? "watch " : string.Empty;
265 | }
266 | return Watch ? "watch " : string.Empty;
267 | }
268 |
269 | private async Task RenderProcesses(CancellationToken cancellationToken)
270 | {
271 | var table = new Table().Ascii2Border();
272 |
273 | await AnsiConsole.Live(table)
274 | .StartAsync(async ctx =>
275 | {
276 | table.AddColumn("Project").AddColumn("Status");
277 |
278 | foreach (var project in runningProjects)
279 | {
280 | table.AddRow(project.Name!, project.Status!);
281 | }
282 | ctx.Refresh();
283 |
284 | while (!cancellationToken.IsCancellationRequested)
285 | {
286 | #if DEBUG
287 | await Task.Delay(100);
288 | #else
289 | await Task.Delay(500);
290 | #endif
291 | table.Rows.Clear();
292 |
293 | foreach (var project in runningProjects)
294 | {
295 | if (project.IsCompleted)
296 | {
297 | table.AddRow(project.Name!, $"[green]*[/] {project.Status}");
298 | }
299 | else
300 | {
301 | if (project.Process!.HasExited && !project.Queued)
302 | {
303 | project.Status = $"[red]*[/] Exited({project.Process.ExitCode})";
304 |
305 | if (Retry)
306 | {
307 | project.Status = $"[orange1]*[/] Exited({project.Process.ExitCode})";
308 |
309 | _ = RestartProject(project, cancellationToken); // fire and forget
310 | }
311 | }
312 | table.AddRow(project.Name!, project.Status!);
313 | }
314 | }
315 |
316 | ctx.Refresh();
317 | }
318 | });
319 | }
320 |
321 | private static async Task RestartProject(RunningProjectItem project, CancellationToken cancellationToken = default)
322 | {
323 | project.Queued = true;
324 | await Task.Delay(3100, cancellationToken);
325 |
326 | if (cancellationToken.IsCancellationRequested)
327 | {
328 | return;
329 | }
330 |
331 | project.Status = $"[orange1]*[/] Exited({project.Process!.ExitCode}) (Retrying...)";
332 | project.Process = Process.Start(project.Process!.StartInfo)!;
333 | project.StartReadingOutput();
334 | }
335 |
336 | protected void KillRunningProcesses()
337 | {
338 | console!.Output.WriteLine($"- Killing running {runningProjects.Count} processes...");
339 | foreach (var project in runningProjects)
340 | {
341 | project.Process?.Kill(entireProcessTree: true);
342 |
343 | project.Process?.WaitForExit();
344 | }
345 | }
346 | }
--------------------------------------------------------------------------------
/src/AbpDevTools/Commands/RunningProjectItem.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 |
3 | namespace AbpDevTools.Commands;
4 |
5 | public class RunningProjectItem
6 | {
7 | public string? Name { get; set; }
8 | public Process? Process { get; set; }
9 | public virtual string? Status { get; set; }
10 | public virtual bool IsCompleted { get; set; }
11 | public virtual bool Queued { get; set; }
12 | public bool Verbose { get; set; }
13 |
14 | public virtual void StartReadingOutput()
15 | {
16 | }
17 | }
18 |
19 | public class RunningCsProjItem : RunningProjectItem
20 | {
21 | public RunningCsProjItem(string name, Process process, string? status = null, bool verbose = false)
22 | {
23 | this.Name = name;
24 | this.Process = process;
25 | this.Status = status ?? "Building...";
26 | this.Verbose = verbose;
27 | StartReadingOutput();
28 | }
29 |
30 | public override void StartReadingOutput()
31 | {
32 | Queued = false;
33 | Process!.OutputDataReceived -= OutputReceived;
34 | Process!.OutputDataReceived += OutputReceived;
35 | Process!.BeginOutputReadLine();
36 | }
37 |
38 | protected virtual void OutputReceived(object sender, DataReceivedEventArgs args)
39 | {
40 | if (!IsCompleted && Verbose)
41 | {
42 | Status = args.Data?.Replace("[", string.Empty).Replace("]", string.Empty) ?? string.Empty;
43 | }
44 |
45 | if (args.Data != null && args.Data.Contains("Now listening on: "))
46 | {
47 | Status = args.Data[args.Data.IndexOf("Now listening on: ")..];
48 | Process?.CancelOutputRead();
49 | IsCompleted = true;
50 | }
51 |
52 | if (args.Data != null &&
53 | args.Data.Contains("dotnet watch ") &&
54 | args.Data.Contains(" Started"))
55 | {
56 | Status = args.Data;
57 | Process?.CancelOutputRead();
58 | IsCompleted = true;
59 | }
60 |
61 | if (DateTime.Now - Process?.StartTime > TimeSpan.FromMinutes(5))
62 | {
63 | Status = "Stale";
64 | Process!.OutputDataReceived -= OutputReceived;
65 | Process.CancelOutputRead();
66 | }
67 | }
68 | }
69 |
70 | public class RunningInstallLibsItem : RunningProjectItem
71 | {
72 | public RunningInstallLibsItem(string name, Process process, string? status = null)
73 | {
74 | this.Name = name;
75 | this.Process = process;
76 | this.Status = status ?? "Installing...";
77 | StartReadingOutput();
78 | }
79 |
80 | public override void StartReadingOutput()
81 | {
82 | Process!.OutputDataReceived -= OutputReceived;
83 | Process!.OutputDataReceived += OutputReceived;
84 | Process!.BeginOutputReadLine();
85 | }
86 |
87 | protected virtual void OutputReceived(object sender, DataReceivedEventArgs args)
88 | {
89 | if (args.Data != null && args.Data.Contains("Done in"))
90 | {
91 | Status = "Completed.";
92 | Process!.CancelOutputRead();
93 | IsCompleted = true;
94 | }
95 |
96 | if (DateTime.Now - Process!.StartTime > TimeSpan.FromMinutes(5))
97 | {
98 | Status = "Stale";
99 | Process!.OutputDataReceived -= OutputReceived;
100 | Process!.CancelOutputRead();
101 | }
102 | }
103 | }
--------------------------------------------------------------------------------
/src/AbpDevTools/Commands/SwitchToEnvironmentCommand.cs:
--------------------------------------------------------------------------------
1 | using AbpDevTools.Configuration;
2 | using AbpDevTools.Environments;
3 | using CliFx.Infrastructure;
4 | using System.Diagnostics;
5 | using System.Runtime.InteropServices;
6 |
7 | namespace AbpDevTools.Commands;
8 |
9 | [Command("switch-to-env", Description = "Switches to the specified environment.")]
10 | public class SwitchToEnvironmentCommand : ICommand
11 | {
12 | protected readonly IProcessEnvironmentManager processEnvironmentManager;
13 | protected readonly ToolsConfiguration toolsConfiguration;
14 |
15 | [CommandParameter(0, IsRequired = true, Description = "Virtual Environment name to switch in this process")]
16 | public string? EnvironmentName { get; set; }
17 |
18 | public SwitchToEnvironmentCommand(IProcessEnvironmentManager processEnvironmentManager, ToolsConfiguration toolsConfiguration)
19 | {
20 | this.processEnvironmentManager = processEnvironmentManager;
21 | this.toolsConfiguration = toolsConfiguration;
22 | }
23 |
24 | public ValueTask ExecuteAsync(IConsole console)
25 | {
26 | if (string.IsNullOrEmpty(EnvironmentName))
27 | {
28 | throw new ArgumentException("Environment name can not be null or empty.");
29 | }
30 |
31 | var startInfo = GetStartInfo();
32 |
33 | processEnvironmentManager.SetEnvironmentForProcess(EnvironmentName, startInfo);
34 |
35 | var process = Process.Start(startInfo)!;
36 | console.Output.WriteAsync($"Switched to {EnvironmentName} on the process (PID: {process.Id} - {process.ProcessName}).");
37 | process.WaitForExit();
38 |
39 | return default;
40 | }
41 |
42 | private ProcessStartInfo GetStartInfo()
43 | {
44 | var terminal = toolsConfiguration.GetOptions()["terminal"];
45 |
46 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
47 | {
48 | return new ProcessStartInfo(terminal);
49 | }
50 | else
51 | {
52 | return new ProcessStartInfo("open", $". -a {terminal}");
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/AbpDevTools/Commands/TestCommand.cs:
--------------------------------------------------------------------------------
1 | using AbpDevTools.Configuration;
2 | using CliFx.Infrastructure;
3 | using Spectre.Console;
4 | using System.Diagnostics;
5 |
6 | namespace AbpDevTools.Commands;
7 |
8 | [Command("test", Description = "runs 'dotnet test' command recursively.")]
9 | public class TestCommand : ICommand
10 | {
11 | [CommandParameter(0, IsRequired = false, Description = "Working directory to run test. Probably project or solution directory path goes here. Default: . (Current Directory)")]
12 | public string? WorkingDirectory { get; set; }
13 |
14 | [CommandOption("files", 'f', Description = "(Array) Names or part of names of solutions will be tested.")]
15 | public string[]? TestFiles { get; set; }
16 |
17 | [CommandOption("interactive", 'i', Description = "Interactive test solution selection.")]
18 | public bool Interactive { get; set; }
19 |
20 | [CommandOption("configuration", 'c')]
21 | public string? Configuration { get; set; }
22 |
23 | [CommandOption("no-build", Description = "Skips build before running. Passes '--no-build' parameter to dotnet test.")]
24 | public bool NoBuild { get; set; }
25 |
26 | protected IConsole? console;
27 | protected Process? runningProcess;
28 | protected readonly ToolsConfiguration toolsConfiguration;
29 |
30 | public TestCommand(ToolsConfiguration toolsConfiguration)
31 | {
32 | this.toolsConfiguration = toolsConfiguration;
33 | }
34 |
35 | public async ValueTask ExecuteAsync(IConsole console)
36 | {
37 | this.console = console;
38 | if (string.IsNullOrEmpty(WorkingDirectory))
39 | {
40 | WorkingDirectory = Directory.GetCurrentDirectory();
41 | }
42 | var cancellationToken = console.RegisterCancellationHandler();
43 |
44 | cancellationToken.Register(() =>
45 | {
46 | AnsiConsole.MarkupLine("[red]AbpDev Test cancelled by the user.[/]");
47 | console.Output.WriteLine("Killing process with id " + runningProcess?.Id);
48 | runningProcess?.Kill(true);
49 | });
50 |
51 | var buildFiles = await FindBuildFilesAsync("*.sln", "solution");
52 |
53 | if (buildFiles.Length == 0)
54 | {
55 | await console.Output.WriteLineAsync("No .sln files found. Looking for .csproj files.");
56 | return;
57 | }
58 |
59 | var successfulCount = await AnsiConsole.Status().StartAsync("Starting tests...", async ctx =>
60 | {
61 | int completed = 0;
62 | for (int i = 0; i < buildFiles.Length; i++)
63 | {
64 | var buildFile = buildFiles[i];
65 |
66 | var commandSuffix = NoBuild ? " --no-build" : string.Empty;
67 | if (!string.IsNullOrEmpty(Configuration))
68 | {
69 | commandSuffix += $" --configuration {Configuration}";
70 | }
71 |
72 | var tools = toolsConfiguration.GetOptions();
73 | var startInfo = new ProcessStartInfo(tools["dotnet"], $"test {buildFile.FullName}{commandSuffix}");
74 | startInfo.RedirectStandardOutput = true;
75 | startInfo.WorkingDirectory = WorkingDirectory;
76 |
77 | runningProcess = Process.Start(startInfo);
78 | ctx.Status($"Running tests for {buildFile.Name}.");
79 | runningProcess!.OutputDataReceived += (s, e) =>
80 | {
81 | if (e.Data != null)
82 | {
83 | AnsiConsole.MarkupLine($"[grey]{e.Data}[/]");
84 | }
85 | };
86 | runningProcess!.BeginOutputReadLine();
87 |
88 | await runningProcess.WaitForExitAsync(cancellationToken);
89 |
90 | if (runningProcess.ExitCode == 0)
91 | {
92 | completed++;
93 | }
94 | }
95 |
96 | return completed;
97 | });
98 | }
99 |
100 | private async Task FindBuildFilesAsync(string pattern, string? nameOfPattern = null)
101 | {
102 | nameOfPattern ??= "solution";
103 |
104 | var files = await AnsiConsole.Status()
105 | .StartAsync($"Looking for {nameOfPattern} files ({pattern})", async ctx =>
106 | {
107 | ctx.Spinner(Spinner.Known.SimpleDotsScrolling);
108 |
109 | await Task.Yield();
110 |
111 | var query = Directory.EnumerateFiles(WorkingDirectory!, pattern, SearchOption.AllDirectories);
112 |
113 | if (TestFiles?.Length > 0)
114 | {
115 | query = query.Where(x => TestFiles.Any(y => x.Contains(y, StringComparison.InvariantCultureIgnoreCase)));
116 | }
117 |
118 | var fileInfo = query
119 | .Select(x => new FileInfo(x))
120 | .ToArray();
121 |
122 | AnsiConsole.MarkupLine($"[green]{fileInfo.Length}[/] {pattern.Replace('*', '\0')} files found.");
123 |
124 | return fileInfo;
125 | });
126 |
127 | if (Interactive && files.Length > 1)
128 | {
129 | var choosed = AnsiConsole.Prompt(
130 | new MultiSelectionPrompt()
131 | .Title("Choose files to be tested:")
132 | .NotRequired() // Not required to have a favorite fruit
133 | .PageSize(12)
134 | .HighlightStyle(new Style(foreground: Color.MediumPurple2))
135 | .MoreChoicesText("[grey](Move up and down to reveal more files)[/]")
136 | .InstructionsText(
137 | "[grey](Press [mediumpurple2][/] to toggle a file, " +
138 | "[green][/] to accept)[/]")
139 | .AddChoices(files.Select(s => s.FullName)));
140 |
141 | files = files.Where(x => choosed.Contains(x.FullName)).ToArray();
142 | }
143 |
144 | return files;
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/src/AbpDevTools/Commands/ToolsCommand.cs:
--------------------------------------------------------------------------------
1 | using AbpDevTools.Configuration;
2 | using CliFx.Infrastructure;
3 | using Spectre.Console;
4 |
5 | namespace AbpDevTools.Commands;
6 |
7 | [Command("tools")]
8 | public class ToolsCommand : ICommand
9 | {
10 | protected readonly ToolsConfiguration toolsConfiguration;
11 |
12 | public ToolsCommand(ToolsConfiguration toolsConfiguration)
13 | {
14 | this.toolsConfiguration = toolsConfiguration;
15 | }
16 |
17 | public ValueTask ExecuteAsync(IConsole console)
18 | {
19 | var tools = toolsConfiguration.GetOptions();
20 |
21 | console.Output.WriteLine("Available tools:\n");
22 |
23 | var table = new Table();
24 |
25 | table.AddColumn("Tool");
26 | table.AddColumn("Path");
27 |
28 | foreach (var tool in tools)
29 | {
30 | table.AddRow(tool.Key, tool.Value);
31 | }
32 |
33 | AnsiConsole.Write(table);
34 |
35 | console.Output.WriteLine("\nYou can change tools with the 'abpdev tools config' command.\n");
36 | return ValueTask.CompletedTask;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/AbpDevTools/Commands/UpdateCheckCommand.cs:
--------------------------------------------------------------------------------
1 | using AbpDevTools.Notifications;
2 | using AbpDevTools.Services;
3 | using CliFx.Infrastructure;
4 | using Spectre.Console;
5 | using System.Net.Http.Json;
6 |
7 | namespace AbpDevTools.Commands;
8 |
9 | [Command("update", Description = "Checks for updates")]
10 | public class UpdateCheckCommand : ICommand
11 | {
12 | public bool Force { get; set; } = true;
13 |
14 | public bool Silent { get; set; }
15 |
16 | protected readonly UpdateChecker updateChecker;
17 | protected readonly INotificationManager notificationManager;
18 |
19 | public UpdateCheckCommand(UpdateChecker updateChecker, INotificationManager notificationManager)
20 | {
21 | this.updateChecker = updateChecker;
22 | this.notificationManager = notificationManager;
23 | }
24 |
25 | public ValueTask SoftCheckAsync(IConsole console)
26 | {
27 | Force = false;
28 | Silent = true;
29 | return ExecuteAsync(console);
30 | }
31 |
32 | public async ValueTask ExecuteAsync(IConsole console)
33 | {
34 | var command = "dotnet tool update -g AbpDevTools";
35 |
36 | if (Force)
37 | {
38 | console.Output.WriteLine($"Checking for updates...");
39 | }
40 |
41 | var result = await updateChecker.CheckAsync(force: Force);
42 |
43 | if (Force)
44 | {
45 | console.Output.WriteLine($"Current version: {result.CurrentVersion}");
46 | }
47 |
48 | if (result.UpdateAvailable)
49 | {
50 | await notificationManager.SendAsync(
51 | "AbpDevTools Update available!",
52 | $"Run '{command}' to update. A newer version {result.LatestVersion} available for AbpDevTools");
53 |
54 | AnsiConsole.Markup($"[yellow]A newer version {result.LatestVersion} available.[/]\n");
55 |
56 | AnsiConsole.Markup($"Run '[black on yellow]{command}[/]' to update.");
57 | }
58 | else
59 | {
60 | if (!Silent)
61 | {
62 | AnsiConsole.Markup($"[green]Your tool is up to date![/]\n");
63 | }
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/AbpDevTools/Configuration/CleanConfiguration.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json;
2 | using YamlDotNet.Serialization;
3 |
4 | namespace AbpDevTools.Configuration;
5 |
6 | [RegisterTransient]
7 | public class CleanConfiguration : ConfigurationBase
8 | {
9 | public CleanConfiguration(IDeserializer yamlDeserializer, ISerializer yamlSerializer) : base(yamlDeserializer, yamlSerializer)
10 | {
11 | }
12 |
13 | public override string FileName => "clean-configuration";
14 |
15 | protected override CleanOptions GetDefaults()
16 | {
17 | return new();
18 | }
19 | }
20 |
21 | public class CleanOptions
22 | {
23 | public string[] Folders { get; set; } = new[]
24 | {
25 | "bin",
26 | "obj",
27 | "node_modules",
28 | };
29 | }
--------------------------------------------------------------------------------
/src/AbpDevTools/Configuration/ConfigurationBase.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json;
2 | using YamlDotNet.Serialization;
3 |
4 | namespace AbpDevTools.Configuration;
5 |
6 | public interface IConfigurationBase
7 | {
8 | string FolderPath { get; }
9 |
10 | string FilePath { get; }
11 | }
12 |
13 | public abstract class ConfigurationBase : IConfigurationBase
14 | where T : class
15 | {
16 | private readonly IDeserializer _yamlDeserializer;
17 | private readonly ISerializer _yamlSerializer;
18 |
19 | protected ConfigurationBase(IDeserializer yamlDeserializer, ISerializer yamlSerializer)
20 | {
21 | _yamlDeserializer = yamlDeserializer;
22 | _yamlSerializer = yamlSerializer;
23 | }
24 |
25 | public string FolderPath => Path.Combine(
26 | Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
27 | "abpdev");
28 |
29 | public string FilePath => Path.Combine(
30 | FolderPath,
31 | FileName + ".yml");
32 |
33 | public virtual string FileName => GetType().Name + ".yml";
34 |
35 | protected virtual string LegacyJsonFilePath => Path.Combine(
36 | FolderPath,
37 | FileName + ".json");
38 |
39 | public virtual T GetOptions()
40 | {
41 | if (!Directory.Exists(FolderPath))
42 | Directory.CreateDirectory(FolderPath);
43 |
44 | var options = GetDefaults();
45 |
46 | // Check for legacy JSON file and migrate if needed
47 | if (File.Exists(LegacyJsonFilePath))
48 | {
49 | options = MigrateFromJson();
50 | }
51 | else if (File.Exists(FilePath))
52 | {
53 | options = ReadOptions();
54 | }
55 | else
56 | {
57 | SaveOptions(options);
58 | }
59 |
60 | return options;
61 | }
62 |
63 | protected virtual T ReadOptions()
64 | {
65 | var ymlContent = File.ReadAllText(FilePath);
66 | return _yamlDeserializer.Deserialize(ymlContent);
67 | }
68 |
69 | protected virtual void SaveOptions(T options)
70 | {
71 | var yaml = _yamlSerializer.Serialize(options);
72 | File.WriteAllText(FilePath, yaml);
73 | }
74 |
75 | private T MigrateFromJson()
76 | {
77 | var jsonContent = File.ReadAllText(LegacyJsonFilePath);
78 | var options = JsonSerializer.Deserialize(jsonContent)!;
79 |
80 | // Save as YAML
81 | SaveOptions(options);
82 |
83 | // Delete old JSON file
84 | File.Delete(LegacyJsonFilePath);
85 |
86 | return options;
87 | }
88 |
89 | protected abstract T GetDefaults();
90 | }
91 |
--------------------------------------------------------------------------------
/src/AbpDevTools/Configuration/DictionaryConfigurationBase.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json;
2 | using YamlDotNet.Serialization;
3 |
4 | namespace AbpDevTools.Configuration;
5 |
6 | public abstract class DictionaryConfigurationBase : ConfigurationBase>
7 | where T : class
8 | {
9 | protected DictionaryConfigurationBase(IDeserializer yamlDeserializer, ISerializer yamlSerializer) : base(yamlDeserializer, yamlSerializer)
10 | {
11 | }
12 |
13 | protected virtual bool PreserveExistingValues => true;
14 |
15 | public override Dictionary GetOptions()
16 | {
17 | if (!Directory.Exists(FolderPath))
18 | Directory.CreateDirectory(FolderPath);
19 |
20 | var options = GetDefaults()!;
21 | var shouldWrite = !File.Exists(FilePath);
22 |
23 | if (File.Exists(FilePath))
24 | {
25 | var existingOptions = base.GetOptions();
26 |
27 | if (PreserveExistingValues)
28 | {
29 | foreach (var defaultOption in options)
30 | {
31 | if (!existingOptions.ContainsKey(defaultOption.Key))
32 | {
33 | existingOptions[defaultOption.Key] = defaultOption.Value;
34 | shouldWrite = true;
35 | }
36 | }
37 | options = existingOptions;
38 | }
39 | else
40 | {
41 | // When not preserving, just add missing defaults to existing options
42 | options = GetDefaults()!;
43 | shouldWrite = true;
44 | }
45 | }
46 |
47 | if (shouldWrite)
48 | {
49 | SaveOptions(options);
50 | }
51 |
52 | return options;
53 | }
54 | }
--------------------------------------------------------------------------------
/src/AbpDevTools/Configuration/EnvironmentAppConfiguration.cs:
--------------------------------------------------------------------------------
1 | using YamlDotNet.Serialization;
2 |
3 | namespace AbpDevTools.Configuration;
4 |
5 | [RegisterTransient]
6 | public class EnvironmentAppConfiguration : DictionaryConfigurationBase
7 | {
8 | public const string SqlServer = "sqlserver";
9 | public const string SqlServerEdge = "sqlserver-edge";
10 | public const string PostgreSql = "postgresql";
11 | public const string MySql = "mysql";
12 | public const string MongoDb = "mongodb";
13 | public const string Redis = "redis";
14 | public const string RabbitMq = "rabbitmq";
15 |
16 | public EnvironmentAppConfiguration(IDeserializer yamlDeserializer, ISerializer yamlSerializer) : base(yamlDeserializer, yamlSerializer)
17 | {
18 | }
19 |
20 | public override string FileName => "environment-tools";
21 |
22 | protected override Dictionary GetDefaults()
23 | {
24 | return new Dictionary
25 | {
26 | { SqlServer, new EnvironmentToolOption("docker start tmp-sqlserver;docker run --name tmp-sqlserver --restart unless-stopped -e \"ACCEPT_EULA=Y\" -e \"SA_PASSWORD=Passw0rd\" -p 1433:1433 -d mcr.microsoft.com/mssql/server:2017-CU8-ubuntu", "docker kill tmp-sqlserver;docker rm tmp-sqlserver") },
27 | { SqlServerEdge, new EnvironmentToolOption("docker start tmp-sqlserver-edge;docker run --name tmp-sqlserver-edge --restart unless-stopped -d --cap-add SYS_PTRACE -e \"ACCEPT_EULA=1\" -e \"MSSQL_SA_PASSWORD=Passw0rd\" -p 1433:1433 mcr.microsoft.com/azure-sql-edge", "docker kill tmp-sqlserver-edge;docker rm tmp-sqlserver-edge") },
28 | { PostgreSql, new EnvironmentToolOption("docker start tmp-postgres;docker run --name tmp-postgres --restart unless-stopped -e POSTGRES_PASSWORD=Passw0rd -p 5432:5432 -d postgres", "docker kill tmp-posgres;docker rm tmp-posgres") },
29 | { MySql, new EnvironmentToolOption("docker start tmp-mysql;docker run --name tmp-mysql --restart unless-stopped -e \"MYSQL_ROOT_PASSWORD=Passw0rd\" -p 3306:3306 --platform linux/x86_64 -d mysql:5.7", "docker kill tmp-mysql;docker rm tmp-mysql" )},
30 | { MongoDb, new EnvironmentToolOption("docker start tmp-mongo;docker run --name tmp-mongo --restart unless-stopped -p 27017:27017 -d mongo:latest","docker kill tmp-mongo;docker rm tmp-mongo")},
31 | { Redis, new EnvironmentToolOption("docker start tmp-redis;docker run --name tmp-redis -p 6379:6379 -d --restart unless-stopped redis", "docker kill tmp-redis;docker rm tmp-redis") },
32 | { RabbitMq, new EnvironmentToolOption("docker start tmp-rabbitmq;docker run --name tmp-rabbitmq -d --restart unless-stopped -p 15672:15672 -p 5672:5672 rabbitmq:3-management", "docker kill tmp-rabbitmq;docker rm tmp-rabbitmq") }
33 | };
34 | }
35 | }
36 |
37 | public class EnvironmentToolOption
38 | {
39 | public EnvironmentToolOption()
40 | {
41 | }
42 |
43 | public EnvironmentToolOption(string startCmd, string stopCmd)
44 | {
45 | StartCmd = startCmd;
46 | StopCmd = stopCmd;
47 | }
48 |
49 | public string StartCmd { get; set; }
50 | public string StopCmd { get; set; }
51 | }
52 |
--------------------------------------------------------------------------------
/src/AbpDevTools/Configuration/EnvironmentConfiguration.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json;
2 | using YamlDotNet.Serialization;
3 |
4 | namespace AbpDevTools.Configuration;
5 |
6 | [RegisterTransient]
7 | public class EnvironmentConfiguration : DictionaryConfigurationBase
8 | {
9 | public const string SqlServer = "SqlServer";
10 | public const string PostgreSql = "PostgreSql";
11 | public const string MySql = "MySql";
12 | public const string MongoDb = "MongoDb";
13 |
14 | public EnvironmentConfiguration(IDeserializer yamlDeserializer, ISerializer yamlSerializer) : base(yamlDeserializer, yamlSerializer)
15 | {
16 | }
17 |
18 | protected override Dictionary GetDefaults()
19 | {
20 | return new Dictionary
21 | {
22 | {
23 | SqlServer, new EnvironmentOption
24 | {
25 | Variables = new Dictionary
26 | {
27 | { "ConnectionStrings__Default", "Server=localhost;Database={AppName}_{Today};User ID=SA;Password=12345678Aa;TrustServerCertificate=True" }
28 | }
29 | }
30 | },
31 | {
32 | MongoDb, new EnvironmentOption
33 | {
34 | Variables = new Dictionary
35 | {
36 | { "ConnectionStrings__Default", "mongodb://localhost:27017/{AppName}_{Today}" }
37 | }
38 | }
39 | },
40 | {
41 | PostgreSql, new EnvironmentOption
42 | {
43 | Variables = new Dictionary
44 | {
45 | { "ConnectionStrings__Default", "Server=localhost;Port=5432;Database={AppName}_{Today};User Id=postgres;Password=12345678Aa;" }
46 | }
47 | }
48 | },
49 | {
50 | MySql, new EnvironmentOption
51 | {
52 | Variables = new Dictionary
53 | {
54 | { "ConnectionStrings__Default", "Server=localhost;Port=3306;Database={AppName}_{Today};User Id=root;Password=12345678Aa;" }
55 | }
56 | }
57 | }
58 | };
59 | }
60 | }
61 |
62 | public class EnvironmentOption
63 | {
64 | public Dictionary Variables { get; set; }
65 | }
--------------------------------------------------------------------------------
/src/AbpDevTools/Configuration/NotificationConfiguration.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json;
2 | using YamlDotNet.Serialization;
3 |
4 | namespace AbpDevTools.Configuration;
5 |
6 | [RegisterTransient]
7 | public class NotificationConfiguration : ConfigurationBase
8 | {
9 | public NotificationConfiguration(IDeserializer yamlDeserializer, ISerializer yamlSerializer)
10 | : base(yamlDeserializer, yamlSerializer)
11 | {
12 | }
13 |
14 | public override string FileName => "notifications";
15 |
16 | public void SetOptions(NotificationOption options)
17 | {
18 | File.WriteAllText(FilePath, JsonSerializer.Serialize(options, new JsonSerializerOptions
19 | {
20 | WriteIndented = true
21 | }));
22 | }
23 |
24 | protected override NotificationOption GetDefaults() => new();
25 | }
26 |
27 | public class NotificationOption
28 | {
29 | public bool Enabled { get; set; }
30 | }
--------------------------------------------------------------------------------
/src/AbpDevTools/Configuration/ReplacementConfiguration.cs:
--------------------------------------------------------------------------------
1 | using YamlDotNet.Serialization;
2 |
3 | namespace AbpDevTools.Configuration;
4 |
5 | [RegisterTransient]
6 | public class ReplacementConfiguration : ConfigurationBase>
7 | {
8 | public ReplacementConfiguration(IDeserializer yamlDeserializer, ISerializer yamlSerializer) : base(yamlDeserializer, yamlSerializer)
9 | {
10 | }
11 |
12 | public override string FileName => "replacements";
13 |
14 | protected override string LegacyJsonFilePath => Path.Combine(FolderPath, "replacements.json");
15 |
16 | protected override Dictionary GetDefaults()
17 | {
18 | return new Dictionary
19 | {
20 | {
21 | "ConnectionStrings", new ReplacementOption
22 | {
23 | FilePattern = "appsettings.json",
24 | Find = "Trusted_Connection=True;",
25 | Replace = "User ID=SA;Password=12345678Aa;"
26 | }
27 | },
28 | {
29 | "LocalDb", new ReplacementOption
30 | {
31 | FilePattern = "appsettings.json",
32 | Find = "Server=(LocalDb)\\\\MSSQLLocalDB;",
33 | Replace = "Server=localhost;"
34 | }
35 | }
36 | };
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/AbpDevTools/Configuration/ReplacementOption.cs:
--------------------------------------------------------------------------------
1 | namespace AbpDevTools.Configuration;
2 | public class ReplacementOption
3 | {
4 | public string FilePattern { get; set; }
5 | public string Find { get; set; }
6 | public string Replace { get; set; }
7 | }
8 |
--------------------------------------------------------------------------------
/src/AbpDevTools/Configuration/RunConfiguration.cs:
--------------------------------------------------------------------------------
1 | using YamlDotNet.Serialization;
2 |
3 | namespace AbpDevTools.Configuration;
4 |
5 | [RegisterTransient]
6 | [Obsolete]
7 | public class RunConfiguration : ConfigurationBase
8 | {
9 | public RunConfiguration(IDeserializer yamlDeserializer, ISerializer yamlSerializer) : base(yamlDeserializer, yamlSerializer)
10 | {
11 | }
12 |
13 | public override string FileName => "run-configuration";
14 |
15 | [Obsolete]
16 | protected override RunOptions GetDefaults()
17 | {
18 | return new RunOptions
19 | {
20 | RunnableProjects = new[]
21 | {
22 | ".HttpApi.Host",
23 | ".HttpApi.HostWithIds",
24 | ".AuthServer",
25 | ".IdentityServer",
26 | ".Web",
27 | ".Web.Host",
28 | ".Web.Public",
29 | ".Mvc",
30 | ".Mvc.Host",
31 | ".Blazor",
32 | ".Blazor.Host",
33 | ".Blazor.Server",
34 | ".Blazor.Server.Host",
35 | ".Blazor.Server.Tiered",
36 | ".Unified",
37 | ".PublicWeb",
38 | ".PublicWebGateway",
39 | ".WebGateway"
40 | }
41 | };
42 | }
43 |
44 | [Obsolete]
45 | public override RunOptions GetOptions()
46 | {
47 | return base.GetOptions();
48 | }
49 |
50 | public void CleanObsolete()
51 | {
52 | if(File.Exists(FilePath))
53 | {
54 | File.Delete(FilePath);
55 | }
56 | }
57 | }
58 |
59 | [Obsolete]
60 | public class RunOptions
61 | {
62 | public string[] RunnableProjects { get; set; } = Array.Empty();
63 | }
--------------------------------------------------------------------------------
/src/AbpDevTools/Configuration/ToolsConfiguration.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.InteropServices;
2 | using System.Text.Json;
3 | using YamlDotNet.Serialization;
4 |
5 | namespace AbpDevTools.Configuration;
6 |
7 | [RegisterTransient]
8 | public class ToolsConfiguration : ConfigurationBase
9 | {
10 | public ToolsConfiguration(IDeserializer yamlDeserializer, ISerializer yamlSerializer) : base(yamlDeserializer, yamlSerializer)
11 | {
12 | }
13 |
14 | public override string FileName => "tools-configuration";
15 |
16 | public override ToolOption GetOptions()
17 | {
18 | if (!Directory.Exists(FolderPath))
19 | {
20 | Directory.CreateDirectory(FolderPath);
21 | }
22 |
23 | var _defaults = GetDefaults();
24 | var shouldSave = true;
25 |
26 | if (File.Exists(FilePath))
27 | {
28 | var options = ReadOptions()!;
29 |
30 | shouldSave = Merge(options, _defaults);
31 | }
32 |
33 | if(shouldSave)
34 | {
35 | SaveOptions(_defaults);
36 | }
37 |
38 | return _defaults;
39 | }
40 |
41 | protected override ToolOption GetDefaults()
42 | {
43 | var _defaults = new ToolOption
44 | {
45 | { "powershell", "pwsh"},
46 | { "dotnet", "dotnet" },
47 | { "abp", "abp" },
48 | };
49 |
50 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
51 | {
52 | _defaults["open"] = "explorer";
53 | _defaults["terminal"] = "wt";
54 | }
55 | else
56 | {
57 | _defaults["open"] = "open";
58 | if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
59 | {
60 | _defaults["osascript"] = "osascript";
61 | _defaults["terminal"] = "terminal";
62 | }
63 | }
64 |
65 | return _defaults;
66 | }
67 |
68 | private static bool Merge(Dictionary options, Dictionary defaults)
69 | {
70 | var changed = false;
71 | foreach (var (key, value) in defaults)
72 | {
73 | if (!options.ContainsKey(key))
74 | {
75 | options[key] = value;
76 | changed = true;
77 | }
78 | }
79 |
80 | return changed;
81 | }
82 | }
83 |
84 | public class ToolOption : Dictionary
85 | {
86 | }
87 |
--------------------------------------------------------------------------------
/src/AbpDevTools/DotnetDependencyResolver.cs:
--------------------------------------------------------------------------------
1 | using AbpDevTools.Configuration;
2 | using CliFx.Exceptions;
3 | using Spectre.Console;
4 | using System.Diagnostics;
5 | using System.Text.Json;
6 |
7 | namespace AbpDevTools;
8 |
9 | [RegisterTransient]
10 | public class DotnetDependencyResolver
11 | {
12 | protected ToolOption Tools { get; }
13 |
14 | public DotnetDependencyResolver(ToolsConfiguration toolsConfiguration)
15 | {
16 | Tools = toolsConfiguration.GetOptions();
17 | }
18 |
19 | public async Task CheckSingleDependencyAsync(string projectPath, string assemblyName, CancellationToken cancellationToken)
20 | {
21 | await RestoreProjectAsync(projectPath);
22 | return await IsPackageDependencyAsync(projectPath, assemblyName, cancellationToken);
23 | }
24 |
25 | private async Task IsPackageDependencyAsync(string projectPath, string assemblyName, CancellationToken cancellationToken)
26 | {
27 | var startInfo = new ProcessStartInfo
28 | {
29 | FileName = Tools["dotnet"],
30 | Arguments = $"nuget why \"{projectPath}\" \"{assemblyName}\"",
31 | RedirectStandardOutput = true,
32 | RedirectStandardError = true,
33 | UseShellExecute = false,
34 | CreateNoWindow = true
35 | };
36 |
37 | using var process = new Process { StartInfo = startInfo };
38 | var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
39 | cts.CancelAfter(TimeSpan.FromSeconds(30)); // 30-second timeout
40 |
41 | try
42 | {
43 | process.Start();
44 |
45 | var outputTask = process.StandardOutput.ReadToEndAsync();
46 | var errorTask = process.StandardError.ReadToEndAsync();
47 |
48 | // Wait for the process to exit or for the cancellation token to be triggered
49 | await Task.WhenAny(process.WaitForExitAsync(cts.Token), Task.Delay(Timeout.Infinite, cts.Token));
50 |
51 | if (!process.HasExited)
52 | {
53 | process.Kill(entireProcessTree: true);
54 | throw new TimeoutException($"'dotnet nuget why' command timed out for assembly '{assemblyName}' in project '{projectPath}'.");
55 | }
56 |
57 | var exitCode = process.ExitCode;
58 | var output = await outputTask;
59 | var error = await errorTask;
60 |
61 | if (exitCode != 0)
62 | {
63 | AnsiConsole.WriteLine($"Error executing 'dotnet nuget why': {error}");
64 | return false;
65 | }
66 |
67 | // Return false if the output indicates no dependency
68 | if (output.Contains("does not have a dependency on", StringComparison.OrdinalIgnoreCase))
69 | {
70 | return false;
71 | }
72 |
73 | // Only return true if there's actual dependency information
74 | return !string.IsNullOrWhiteSpace(output);
75 | }
76 | catch (OperationCanceledException)
77 | {
78 | if (!process.HasExited)
79 | {
80 | process.Kill(entireProcessTree: true);
81 | }
82 | AnsiConsole.WriteLine($"'dotnet nuget why' command was cancelled for assembly '{assemblyName}' in project '{projectPath}'.");
83 | return false;
84 | }
85 | catch (Exception ex)
86 | {
87 | AnsiConsole.WriteLine($"Unexpected error executing 'dotnet nuget why': {ex.Message}");
88 | return false;
89 | }
90 | }
91 |
92 | public async Task> GetProjectDependenciesAsync(string projectPath)
93 | {
94 | var packages = new HashSet();
95 |
96 | await RestoreProjectAsync(projectPath);
97 |
98 | // Get NuGet package references
99 | var listPackagesResult = await ExecuteDotnetListPackagesAsync(projectPath);
100 | var packageReferences = ParsePackageList(listPackagesResult);
101 | packages.UnionWith(packageReferences);
102 |
103 | // Get project references
104 | var listReferencesResult = await ExecuteDotnetListReferenceAsync(projectPath);
105 | var projectReferences = ParseProjectReferences(listReferencesResult);
106 | packages.UnionWith(projectReferences);
107 |
108 | return packages;
109 | }
110 |
111 | private async Task ExecuteDotnetListPackagesAsync(string projectPath)
112 | {
113 | var startInfo = new ProcessStartInfo
114 | {
115 | FileName = Tools["dotnet"],
116 | Arguments = $"list {projectPath} package --format json",
117 | RedirectStandardOutput = true,
118 | RedirectStandardError = true,
119 | UseShellExecute = false,
120 | CreateNoWindow = true
121 | };
122 |
123 | using var process = Process.Start(startInfo)
124 | ?? throw new CommandException("Failed to start 'dotnet list package' process.");
125 |
126 | // Asynchronously read output and error
127 | var outputTask = process.StandardOutput.ReadToEndAsync();
128 | var errorTask = process.StandardError.ReadToEndAsync();
129 |
130 | // Wait for exit with timeout
131 | var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
132 | try
133 | {
134 | process.WaitForExit(30000); // 30 seconds
135 |
136 | if (!process.HasExited)
137 | {
138 | process.Kill(entireProcessTree: true);
139 | throw new TimeoutException($"'dotnet list package' command timed out for project '{projectPath}'.");
140 | }
141 |
142 | if (process.ExitCode != 0)
143 | {
144 | var error = await errorTask;
145 | throw new CommandException($"'dotnet list package' failed with exit code {process.ExitCode}. Error: {error}");
146 | }
147 |
148 | return await outputTask;
149 | }
150 | catch (Exception ex) when (!(ex is CommandException))
151 | {
152 | process.Kill(entireProcessTree: true);
153 | throw new CommandException($"Error executing 'dotnet list package': {ex.Message}");
154 | }
155 | }
156 |
157 | private HashSet ParsePackageList(string jsonOutput)
158 | {
159 | var packages = new HashSet();
160 |
161 | try
162 | {
163 | using var doc = JsonDocument.Parse(jsonOutput);
164 | var projects = doc.RootElement.GetProperty("projects");
165 |
166 | foreach (var project in projects.EnumerateArray())
167 | {
168 | if (project.TryGetProperty("frameworks", out var frameworks))
169 | {
170 | foreach (var framework in frameworks.EnumerateArray())
171 | {
172 | if (framework.TryGetProperty("topLevelPackages", out var topLevelPackages))
173 | {
174 | foreach (var package in topLevelPackages.EnumerateArray())
175 | {
176 | var id = package.GetProperty("id").GetString();
177 | if (!string.IsNullOrEmpty(id))
178 | {
179 | packages.Add(id);
180 | }
181 | }
182 | }
183 | }
184 | }
185 | }
186 | }
187 | catch (JsonException ex)
188 | {
189 | throw new CommandException("Failed to parse package list output.\n\n" + ex.Message);
190 | }
191 |
192 | return packages;
193 | }
194 |
195 | private async Task ExecuteDotnetListReferenceAsync(string projectPath)
196 | {
197 | var startInfo = new ProcessStartInfo
198 | {
199 | FileName = Tools["dotnet"],
200 | Arguments = $"list {projectPath} reference",
201 | RedirectStandardOutput = true,
202 | RedirectStandardError = true,
203 | UseShellExecute = false,
204 | CreateNoWindow = true
205 | };
206 |
207 | using var process = Process.Start(startInfo)
208 | ?? throw new CommandException("Failed to start 'dotnet list reference' process.");
209 |
210 | // Asynchronously read output and error
211 | var outputTask = process.StandardOutput.ReadToEndAsync();
212 | var errorTask = process.StandardError.ReadToEndAsync();
213 |
214 | // Wait for exit with timeout
215 | var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
216 | try
217 | {
218 | process.WaitForExit(30000); // 30 seconds
219 |
220 | if (!process.HasExited)
221 | {
222 | process.Kill(entireProcessTree: true);
223 | throw new TimeoutException($"'dotnet list reference' command timed out for project '{projectPath}'.");
224 | }
225 |
226 | if (process.ExitCode != 0)
227 | {
228 | var error = await errorTask;
229 | throw new CommandException($"'dotnet list reference' failed with exit code {process.ExitCode}. Error: {error}");
230 | }
231 |
232 | return await outputTask;
233 | }
234 | catch (Exception ex) when (!(ex is CommandException))
235 | {
236 | process.Kill(entireProcessTree: true);
237 | throw new CommandException($"Error executing 'dotnet list reference': {ex.Message}");
238 | }
239 | }
240 |
241 | private HashSet ParseProjectReferences(string output)
242 | {
243 | var references = new HashSet();
244 |
245 | var lines = output.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
246 | foreach (var line in lines)
247 | {
248 | if (string.IsNullOrWhiteSpace(line)) continue;
249 |
250 | var projectPath = line.Trim();
251 | var projectName = Path.GetFileNameWithoutExtension(projectPath);
252 | references.Add(projectName);
253 | }
254 |
255 | return references;
256 | }
257 |
258 | private async Task RestoreProjectAsync(string projectPath)
259 | {
260 | var friendlyProjectName = Path.GetFileNameWithoutExtension(projectPath);
261 | if(!ShouldRestoreProject(projectPath))
262 | {
263 | return;
264 | }
265 |
266 | AnsiConsole.WriteLine($"{Emoji.Known.RecyclingSymbol} Restoring {friendlyProjectName}...");
267 |
268 | var restoreStartInfo = new ProcessStartInfo
269 | {
270 | FileName = Tools["dotnet"],
271 | Arguments = $"restore \"{projectPath}\"",
272 | RedirectStandardOutput = true,
273 | RedirectStandardError = true,
274 | UseShellExecute = false,
275 | CreateNoWindow = true
276 | };
277 |
278 | using var restoreProcess = Process.Start(restoreStartInfo)
279 | ?? throw new CommandException("Failed to start 'dotnet restore' process.");
280 |
281 | // Asynchronously read output and error
282 | var outputTask = restoreProcess.StandardOutput.ReadToEndAsync();
283 | var errorTask = restoreProcess.StandardError.ReadToEndAsync();
284 |
285 | // Wait for exit with timeout
286 | var cts = new CancellationTokenSource(TimeSpan.FromSeconds(120)); // 2-minute timeout
287 | try
288 | {
289 | restoreProcess.WaitForExit(120000); // 120 seconds
290 |
291 | if (!restoreProcess.HasExited)
292 | {
293 | restoreProcess.Kill(entireProcessTree: true);
294 | throw new TimeoutException($"'dotnet restore' command timed out for project '{friendlyProjectName}'.");
295 | }
296 |
297 | if (restoreProcess.ExitCode != 0)
298 | {
299 | var error = await errorTask;
300 | throw new CommandException($"'dotnet restore' failed with exit code {restoreProcess.ExitCode}. Error: {error}");
301 | }
302 | }
303 | catch (Exception ex) when (!(ex is CommandException))
304 | {
305 | restoreProcess.Kill(entireProcessTree: true);
306 | throw new CommandException($"Error executing 'dotnet restore': {ex.Message}");
307 | }
308 | }
309 |
310 | private bool ShouldRestoreProject(string projectPath)
311 | {
312 | var projectDirectory = Path.GetDirectoryName(projectPath)
313 | ?? throw new CommandException($"Could not get directory for project: {projectPath}");
314 |
315 | var binPath = Path.Combine(projectDirectory, "bin");
316 | var objPath = Path.Combine(projectDirectory, "obj");
317 |
318 | if (!Directory.Exists(binPath) && !Directory.Exists(objPath))
319 | {
320 | return true;
321 | }
322 |
323 | var assetsFile = Path.Combine(objPath, "project.assets.json");
324 | if (!File.Exists(assetsFile))
325 | {
326 | return true;
327 | }
328 |
329 | var projectFileInfo = new FileInfo(projectPath);
330 | var assetsFileInfo = new FileInfo(assetsFile);
331 |
332 | return projectFileInfo.LastWriteTimeUtc > assetsFileInfo.LastWriteTimeUtc;
333 | }
334 | }
--------------------------------------------------------------------------------
/src/AbpDevTools/Environments/AppEnvironmentMapping.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using AbpDevTools.Configuration;
3 |
4 | namespace AbpDevTools.Environments;
5 |
6 | public class AppEnvironmentMapping
7 | {
8 | public string EnvironmentName { get; set; } = string.Empty;
9 | public string AppName { get; set; } = string.Empty;
10 |
11 | public static Dictionary Default { get; } = new()
12 | {
13 | {
14 | "Volo.Abp.EntityFrameworkCore.SqlServer",
15 | new AppEnvironmentMapping
16 | {
17 | AppName = EnvironmentAppConfiguration.SqlServerEdge,
18 | EnvironmentName = EnvironmentConfiguration.SqlServer
19 | }
20 | },
21 | {
22 | "Volo.Abp.EntityFrameworkCore.MySQL",
23 | new AppEnvironmentMapping
24 | {
25 | AppName = EnvironmentAppConfiguration.MySql,
26 | EnvironmentName = EnvironmentConfiguration.MySql
27 | }
28 | },
29 | {
30 | "Volo.Abp.EntityFrameworkCore.PostgreSql",
31 | new AppEnvironmentMapping
32 | {
33 | AppName = EnvironmentAppConfiguration.PostgreSql,
34 | EnvironmentName = EnvironmentConfiguration.PostgreSql
35 | }
36 | },
37 | {
38 | "Volo.Abp.Caching.StackExchangeRedis",
39 | new AppEnvironmentMapping
40 | {
41 | AppName = EnvironmentAppConfiguration.Redis
42 | }
43 | },
44 | {
45 | "Volo.Abp.EventBus.RabbitMQ",
46 | new AppEnvironmentMapping
47 | {
48 | AppName = EnvironmentAppConfiguration.RabbitMq
49 | }
50 | },
51 | {
52 | "Volo.Abp.MongoDB",
53 | new AppEnvironmentMapping
54 | {
55 | AppName = EnvironmentAppConfiguration.MongoDb
56 | }
57 | }
58 | };
59 | }
60 |
--------------------------------------------------------------------------------
/src/AbpDevTools/Environments/IProcessEnvironmentManager.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 |
3 | namespace AbpDevTools.Environments;
4 | public interface IProcessEnvironmentManager
5 | {
6 | void SetEnvironment(string environment, string directory);
7 | void SetEnvironmentForProcess(string environment, ProcessStartInfo process);
8 | void SetEnvironmentVariablesForProcess(ProcessStartInfo process, Dictionary variables);
9 | }
10 |
--------------------------------------------------------------------------------
/src/AbpDevTools/Environments/ProcessEnvironmentManager.cs:
--------------------------------------------------------------------------------
1 | using AbpDevTools.Configuration;
2 | using AutoRegisterInject;
3 | using CliFx.Exceptions;
4 | using System.Diagnostics;
5 | using System.Text.RegularExpressions;
6 | using Unidecode.NET;
7 |
8 | namespace AbpDevTools.Environments;
9 |
10 | [RegisterTransient]
11 | public class ProcessEnvironmentManager : IProcessEnvironmentManager
12 | {
13 | private static Dictionary> replacements = new Dictionary>()
14 | {
15 | { "{Today}", (_) => DateTime.Today.ToString("yyyyMMdd") },
16 | { "{AppName}", FindAppName }
17 | };
18 |
19 | private readonly EnvironmentConfiguration environmentConfiguration;
20 |
21 | public ProcessEnvironmentManager(EnvironmentConfiguration environmentConfiguration)
22 | {
23 | this.environmentConfiguration = environmentConfiguration;
24 | }
25 |
26 | public void SetEnvironment(string environment, string directory)
27 | {
28 | var options = environmentConfiguration.GetOptions();
29 |
30 | if (!options.TryGetValue(environment, out var env))
31 | {
32 | var environments = string.Join('\n', options.Keys.Select(x => "\t- " + x));
33 | throw new CommandException("Environment not found! Check environments by 'abpdev env config' command.\nAvailable environments:\n" + environments);
34 | }
35 |
36 | foreach (var variable in env.Variables)
37 | {
38 | Environment.SetEnvironmentVariable(variable.Key, PrepareValue(variable.Value, directory), EnvironmentVariableTarget.Process);
39 | }
40 | }
41 |
42 | public void SetEnvironmentForProcess(string environment, ProcessStartInfo process)
43 | {
44 | var options = environmentConfiguration.GetOptions();
45 |
46 | if (!options.TryGetValue(environment, out var env))
47 | {
48 | var environments = string.Join('\n', options.Keys.Select(x => "\t- " + x));
49 | throw new CommandException("Environment not found! Check environments by 'abpdev env config' command.\nAvailable environments:\n" + environments);
50 | }
51 |
52 | SetEnvironmentVariablesForProcess(process, env.Variables);
53 | }
54 |
55 | public void SetEnvironmentVariablesForProcess(ProcessStartInfo process, Dictionary variables)
56 | {
57 | foreach (var variable in variables)
58 | {
59 |
60 | process.EnvironmentVariables[variable.Key] = PrepareValue(variable.Value, process.WorkingDirectory);
61 | }
62 | }
63 |
64 | protected virtual string PrepareValue(string value, string? directory = null)
65 | {
66 | var finalResult = value;
67 | foreach (var item in replacements)
68 | {
69 | finalResult = finalResult.Replace(item.Key, item.Value(directory));
70 | }
71 |
72 | return finalResult;
73 | }
74 |
75 | private static string FindAppName(string? directory)
76 | {
77 | var dir = directory;
78 |
79 | if (string.IsNullOrEmpty(dir))
80 | {
81 | dir = Directory.GetCurrentDirectory();
82 | }
83 |
84 | var folderName = new DirectoryInfo(dir).Name;
85 | if (folderName.Contains("."))
86 | {
87 | var appName = folderName.Split('.').First();
88 |
89 | dir = dir.Replace(folderName, appName);
90 | }
91 |
92 | var normalized = Regex.Replace(dir.Unidecode().ToLowerInvariant(), @"[^a-z0-9]", string.Empty);
93 |
94 | var result = TruncateStart(normalized, 116);
95 |
96 | return result;
97 | }
98 |
99 | private static string TruncateStart(string value, int maximumLength)
100 | {
101 | if (value.Length <= maximumLength)
102 | {
103 | return value;
104 | }
105 |
106 | return value[^maximumLength..];
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/AbpDevTools/FileExplorer.cs:
--------------------------------------------------------------------------------
1 | namespace AbpDevTools;
2 |
3 | [RegisterTransient]
4 | public class FileExplorer
5 | {
6 | public IEnumerable FindDescendants(string path, string pattern)
7 | {
8 | return Directory.EnumerateFiles(path, pattern, SearchOption.AllDirectories);
9 | }
10 |
11 | public IEnumerable FindDescendants(string path, string pattern, string[] excludeFolders)
12 | {
13 | return Directory.EnumerateFiles(path, pattern, SearchOption.AllDirectories)
14 | .Where(x => !excludeFolders.Any(x.Contains));
15 | }
16 |
17 | public IEnumerable FindAscendants(string path, string pattern)
18 | {
19 | foreach (var item in Directory.EnumerateFiles(path, pattern, SearchOption.TopDirectoryOnly))
20 | {
21 | yield return item;
22 | }
23 |
24 | var upLevelPath = Path.GetFullPath(Path.Combine(path, ".."));
25 |
26 | var root = Path.GetPathRoot(path);
27 |
28 | if (upLevelPath != root)
29 | {
30 | foreach (var item in FindAscendants(upLevelPath, pattern))
31 | {
32 | yield return item;
33 | }
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/AbpDevTools/Global.usings.cs:
--------------------------------------------------------------------------------
1 | global using System;
2 | global using CliFx;
3 | global using CliFx.Attributes;
4 |
--------------------------------------------------------------------------------
/src/AbpDevTools/LocalConfigurations/LocalConfiguration.cs:
--------------------------------------------------------------------------------
1 | using AbpDevTools.Configuration;
2 |
3 | namespace AbpDevTools.LocalConfigurations;
4 | public class LocalConfiguration
5 | {
6 | public LocalRunOption? Run { get; set; }
7 | public LocalEnvironmentOption? Environment { get; set; }
8 |
9 | public class LocalEnvironmentOption : EnvironmentOption
10 | {
11 | public string? Name { get; set; }
12 | }
13 |
14 | public class LocalRunOption
15 | {
16 | public bool Watch { get; set; }
17 | public bool NoBuild { get; set; }
18 | public bool GraphBuild { get; set; }
19 | public string? Configuration { get; set; }
20 | public bool SkipMigrate { get; set; }
21 | public string[] Projects { get; set; } = Array.Empty();
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/AbpDevTools/LocalConfigurations/LocalConfigurationManager.cs:
--------------------------------------------------------------------------------
1 | using AbpDevTools.Environments;
2 | using System.Diagnostics;
3 | using YamlDotNet.Serialization;
4 |
5 | namespace AbpDevTools.LocalConfigurations;
6 |
7 | [RegisterTransient]
8 | public class LocalConfigurationManager
9 | {
10 | protected readonly IDeserializer _deserializer;
11 | protected readonly ISerializer _serializer;
12 | protected readonly FileExplorer fileExplorer;
13 | protected readonly IProcessEnvironmentManager environmentManager;
14 |
15 | public LocalConfigurationManager(IDeserializer deserializer, ISerializer serializer, FileExplorer fileExplorer, IProcessEnvironmentManager environmentManager)
16 | {
17 | _deserializer = deserializer;
18 | _serializer = serializer;
19 | this.fileExplorer = fileExplorer;
20 | this.environmentManager = environmentManager;
21 | }
22 |
23 | public string Save(string path, LocalConfiguration configuration)
24 | {
25 | var directory = Path.GetDirectoryName(path);
26 | if (directory == null)
27 | {
28 | throw new ArgumentException("Invalid path", nameof(path));
29 | }
30 |
31 | if (!Directory.Exists(directory))
32 | {
33 | Directory.CreateDirectory(directory);
34 | }
35 |
36 | var yaml = _serializer.Serialize(configuration);
37 |
38 | var filePath = path;
39 | if (!path.EndsWith(".yml"))
40 | {
41 | filePath = Path.Combine(path, "abpdev.yml");
42 | }
43 |
44 | File.WriteAllText(filePath, yaml);
45 | return filePath;
46 | }
47 |
48 | public bool TryLoad(string path, out LocalConfiguration? localConfiguration, FileSearchDirection direction = FileSearchDirection.Ascendants)
49 | {
50 | localConfiguration = null;
51 |
52 | var directory = Path.GetDirectoryName(path);
53 | if (directory == null)
54 | {
55 | return false;
56 | }
57 |
58 | string? fileName = "abpdev.yml";
59 |
60 | if (path.EndsWith(".yml"))
61 | {
62 | fileName = Path.GetFileName(path);
63 | }
64 |
65 | var ymlPath = direction switch
66 | {
67 | FileSearchDirection.Ascendants => fileExplorer.FindAscendants(directory, fileName).FirstOrDefault(),
68 | FileSearchDirection.Descendants => fileExplorer.FindDescendants(directory, fileName).FirstOrDefault(),
69 | FileSearchDirection.OnlyCurrent => Path.Combine(directory, fileName),
70 | _ => throw new NotImplementedException()
71 | };
72 |
73 | if (string.IsNullOrEmpty(ymlPath) || !File.Exists(ymlPath))
74 | {
75 | return false;
76 | }
77 |
78 | var ymlContent = File.ReadAllText(ymlPath);
79 |
80 | localConfiguration = _deserializer.Deserialize(ymlContent);
81 |
82 | return true;
83 | }
84 |
85 | public void ApplyLocalEnvironmentForProcess(string path, ProcessStartInfo process, LocalConfiguration? localConfiguration = null)
86 | {
87 | if (localConfiguration is not null || TryLoad(path, out localConfiguration))
88 | {
89 | if (!string.IsNullOrEmpty(localConfiguration?.Environment?.Name))
90 | {
91 | environmentManager.SetEnvironmentForProcess(
92 | localConfiguration.Environment.Name,
93 | process);
94 | }
95 |
96 | if (localConfiguration!.Environment?.Variables != null)
97 | {
98 | environmentManager.SetEnvironmentVariablesForProcess(process, localConfiguration!.Environment!.Variables);
99 | }
100 | }
101 | }
102 | }
103 |
104 | public enum FileSearchDirection : byte
105 | {
106 | Ascendants,
107 | Descendants,
108 | OnlyCurrent
109 | }
--------------------------------------------------------------------------------
/src/AbpDevTools/Notifications/DefaultNotificationManager.cs:
--------------------------------------------------------------------------------
1 | namespace AbpDevTools.Notifications;
2 | public class DefaultNotificationManager : INotificationManager
3 | {
4 | public Task SendAsync(string title, string message, string icon = null)
5 | {
6 | return Task.CompletedTask;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/AbpDevTools/Notifications/INotificationManager.cs:
--------------------------------------------------------------------------------
1 | namespace AbpDevTools.Notifications;
2 | public interface INotificationManager
3 | {
4 | Task SendAsync(string title, string message = null, string icon = null);
5 | }
6 |
--------------------------------------------------------------------------------
/src/AbpDevTools/Notifications/MacCatalystNotificationManager.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using AbpDevTools.Configuration;
3 |
4 | namespace AbpDevTools.Notifications;
5 | public class MacCatalystNotificationManager : INotificationManager
6 | {
7 | protected readonly ToolsConfiguration toolsConfiguration;
8 | protected readonly NotificationConfiguration notificationConfiguration;
9 |
10 | public MacCatalystNotificationManager(ToolsConfiguration toolsConfiguration, NotificationConfiguration notificationConfiguration)
11 | {
12 | this.toolsConfiguration = toolsConfiguration;
13 | this.notificationConfiguration = notificationConfiguration;
14 | }
15 |
16 | public async Task SendAsync(string title, string message = null, string icon = null)
17 | {
18 | if(!notificationConfiguration.GetOptions().Enabled){
19 | return;
20 | }
21 |
22 | var tools = toolsConfiguration.GetOptions();
23 |
24 | var process = Process.Start(tools["osascript"], $"-e \"display notification \\\"{message}\\\" with title \\\"{title}\\\"\"");
25 |
26 | await process.WaitForExitAsync();
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/AbpDevTools/Notifications/WindowsNotificationManager.cs:
--------------------------------------------------------------------------------
1 | using AbpDevTools.Configuration;
2 | using System.Diagnostics;
3 |
4 | namespace AbpDevTools.Notifications;
5 | public class WindowsNotificationManager : INotificationManager
6 | {
7 | private string FolderPath => Path.Combine(
8 | Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
9 | "abpdev");
10 |
11 | protected readonly ToolsConfiguration toolsConfiguration;
12 | protected readonly NotificationConfiguration notificationConfiguration;
13 |
14 | public WindowsNotificationManager(ToolsConfiguration toolsConfiguration, NotificationConfiguration notificationConfiguration)
15 | {
16 | this.toolsConfiguration = toolsConfiguration;
17 | this.notificationConfiguration = notificationConfiguration;
18 | }
19 |
20 | public async Task SendAsync(string title, string message = null, string icon = null)
21 | {
22 | if (!notificationConfiguration.GetOptions().Enabled)
23 | {
24 | return;
25 | }
26 |
27 | var command = "New-BurntToastNotification";
28 |
29 | command += $" -Text \"{title}\"";
30 |
31 | if (!string.IsNullOrEmpty(message))
32 | {
33 | command += $", \"{message}\"";
34 | }
35 |
36 | if (!string.IsNullOrEmpty(icon))
37 | {
38 | command += $" -AppLogo \"{icon}\"";
39 | }
40 |
41 | var fileName = Guid.NewGuid() + ".ps1";
42 |
43 | var filePath = Path.Combine(FolderPath, fileName);
44 |
45 | await File.WriteAllTextAsync(filePath, command);
46 |
47 | var tools = toolsConfiguration.GetOptions();
48 | var process = Process.Start(tools["powershell"], filePath);
49 |
50 | await process.WaitForExitAsync();
51 |
52 | File.Delete(filePath);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/AbpDevTools/Platform.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using System.Runtime.InteropServices;
3 |
4 | namespace AbpDevTools;
5 |
6 | [RegisterTransient]
7 | public class Platform
8 | {
9 | public void Open(string filePath)
10 | {
11 | OpenProcess(filePath).WaitForExit();
12 | }
13 |
14 | public Task OpenAsync(string filePath)
15 | {
16 | return OpenProcess(filePath).WaitForExitAsync();
17 | }
18 |
19 | private Process OpenProcess(string filePath)
20 | {
21 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
22 | {
23 | return Process.Start(new ProcessStartInfo("explorer", filePath));
24 | }
25 | else
26 | {
27 | return Process.Start(new ProcessStartInfo("open", filePath));
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/AbpDevTools/Program.cs:
--------------------------------------------------------------------------------
1 | using AbpDevTools.Commands;
2 | using AbpDevTools.Commands.Migrations;
3 | using AbpDevTools.Notifications;
4 | using Microsoft.Extensions.DependencyInjection;
5 | using System.Runtime.InteropServices;
6 | using System.Text;
7 | using YamlDotNet.Serialization;
8 | using YamlDotNet.Serialization.NamingConventions;
9 |
10 | namespace AbpDevTools;
11 |
12 | public class Program
13 | {
14 | public static async Task Main() =>
15 | await new CliApplicationBuilder()
16 | .SetExecutableName("abpdev")
17 | .SetDescription("A set of tools to make development with ABP easier.")
18 | .SetTitle("Abp Dev Tools")
19 | .BuildServices()
20 | .Build()
21 | .RunAsync();
22 | }
23 |
24 | public static class Startup
25 | {
26 | public static CliApplicationBuilder BuildServices(this CliApplicationBuilder builder)
27 | {
28 | Console.OutputEncoding = Encoding.UTF8;
29 |
30 | var services = new ServiceCollection();
31 |
32 | var commands = new Type[] // Keep this instead reflection for performance
33 | {
34 | typeof(BuildCommand),
35 | typeof(ConfigurationClearCommand),
36 | typeof(ReplacementConfigClearCommand),
37 | typeof(EnvironmentAppConfigClearCommand),
38 | typeof(RunConfigClearCommand),
39 | typeof(CleanConfigClearCommand),
40 | typeof(ToolsConfigClearCommand),
41 | typeof(ReplaceConfigurationCommand),
42 | typeof(EnvironmentAppConfigurationCommand),
43 | typeof(RunConfigurationCommand),
44 | typeof(CleanConfigurationCommand),
45 | typeof(ToolsConfigurationCommand),
46 | typeof(ToolsCommand),
47 | typeof(ConfigCommand),
48 | typeof(DisableNotificationsCommand),
49 | typeof(EnableNotificationsCommand),
50 | typeof(EnvironmentAppCommand),
51 | typeof(EnvironmentAppStartCommand),
52 | typeof(EnvironmentAppStopCommand),
53 | typeof(LogsClearCommand),
54 | typeof(LogsCommand),
55 | typeof(MigrateCommand),
56 | typeof(ReplaceCommand),
57 | typeof(RunCommand),
58 | typeof(EnvironmentCommand),
59 | typeof(EnvironmentConfigurationCommand),
60 | typeof(AbpBundleCommand),
61 | typeof(AbpBundleListCommand),
62 | typeof(TestCommand),
63 | typeof(UpdateCheckCommand),
64 | typeof(CleanCommand),
65 | typeof(DatabaseDropCommand),
66 | typeof(SwitchToEnvironmentCommand),
67 | typeof(FindFileCommand),
68 | typeof(MigrationsCommand),
69 | typeof(AddMigrationCommand),
70 | typeof(ClearMigrationsCommand),
71 | typeof(PrepareCommand),
72 | };
73 |
74 | foreach (var commandType in commands)
75 | {
76 | if (!commandType.IsAbstract && typeof(ICommand).IsAssignableFrom(commandType))
77 | {
78 | builder.AddCommand(commandType);
79 | services.AddSingleton(commandType);
80 | }
81 | }
82 |
83 | services.AutoRegisterFromAbpDevTools();
84 |
85 | var yamlSerializer = new SerializerBuilder()
86 | .WithNamingConvention(HyphenatedNamingConvention.Instance)
87 | .Build();
88 |
89 | services.AddSingleton(yamlSerializer);
90 |
91 | services.AddSingleton(
92 | new DeserializerBuilder()
93 | .WithNamingConvention(HyphenatedNamingConvention.Instance)
94 | .Build());
95 |
96 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
97 | {
98 | services.AddTransient();
99 | }
100 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
101 | {
102 | services.AddTransient();
103 | }
104 | else
105 | {
106 | services.AddTransient();
107 | }
108 |
109 | var serviceProvider = services.BuildServiceProvider();
110 |
111 | return builder.UseTypeActivator(serviceProvider);
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/AbpDevTools/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "AbpDevTools": {
4 | "commandName": "Project",
5 | "commandLineArgs": ""
6 | }
7 | }
8 | }
--------------------------------------------------------------------------------
/src/AbpDevTools/Services/EntityFrameworkCoreProjectsProvider.cs:
--------------------------------------------------------------------------------
1 | namespace AbpDevTools.Services;
2 |
3 | [RegisterSingleton]
4 | public class EntityFrameworkCoreProjectsProvider
5 | {
6 | public FileInfo[] GetEfCoreProjects(string path)
7 | {
8 | return Directory.EnumerateFiles(path, "*.csproj", SearchOption.AllDirectories)
9 | .Where(DoesHaveEfCoreReference)
10 | .Select(x => new FileInfo(x))
11 | .ToArray();
12 | }
13 |
14 | private static bool DoesHaveEfCoreReference(string projectPath)
15 | {
16 | using var fs = new FileStream(projectPath, FileMode.Open, FileAccess.Read);
17 | using var sr = new StreamReader(fs);
18 |
19 | while (!sr.EndOfStream)
20 | {
21 | var line = sr.ReadLine();
22 | if (string.IsNullOrEmpty(line))
23 | {
24 | continue;
25 | }
26 |
27 | if (line.Contains("Microsoft.EntityFrameworkCore"))
28 | {
29 | return true;
30 | }
31 | }
32 |
33 | return false;
34 | }
35 | }
--------------------------------------------------------------------------------
/src/AbpDevTools/Services/RunnableProjectsProvider.cs:
--------------------------------------------------------------------------------
1 | using AbpDevTools.Configuration;
2 |
3 | namespace AbpDevTools.Services;
4 |
5 | [RegisterSingleton]
6 | public class RunnableProjectsProvider
7 | {
8 | public RunnableProjectsProvider(RunConfiguration runConfiguration)
9 | {
10 | runConfiguration.CleanObsolete();
11 | }
12 |
13 | public FileInfo[] GetRunnableProjects(string path)
14 | {
15 | return Directory.EnumerateFiles(path, "*.csproj", SearchOption.AllDirectories)
16 | .Where(DoesHaveProgramClass)
17 | .Select(x => new FileInfo(x))
18 | .ToArray();
19 | }
20 |
21 | private static bool DoesHaveProgramClass(string projectPath)
22 | {
23 | return File.Exists(
24 | Path.Combine(
25 | Path.GetDirectoryName(projectPath)!,
26 | "Program.cs"
27 | ));
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/AbpDevTools/Services/UpdateCheckResult.cs:
--------------------------------------------------------------------------------
1 | namespace AbpDevTools.Services;
2 | public class UpdateCheckResult
3 | {
4 | public UpdateCheckResult(
5 | bool updateAvailable,
6 | string currentVersion,
7 | string latestVersion,
8 | DateTime lastCheckDate)
9 | {
10 | UpdateAvailable = updateAvailable;
11 | CurrentVersion = currentVersion;
12 | LatestVersion = latestVersion;
13 | LastCheckDate = lastCheckDate;
14 | }
15 |
16 | public bool UpdateAvailable { get; }
17 | public string CurrentVersion { get; }
18 | public string LatestVersion { get; }
19 | public DateTime LastCheckDate { get; }
20 | }
21 |
--------------------------------------------------------------------------------
/src/AbpDevTools/Services/UpdateChecker.cs:
--------------------------------------------------------------------------------
1 | using AbpDevTools.Commands;
2 | using System.Net.Http.Json;
3 | using System.Text.Json;
4 |
5 | namespace AbpDevTools.Services;
6 |
7 | [RegisterTransient]
8 | public class UpdateChecker
9 | {
10 | public static string FolderPath => Path.Combine(
11 | Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
12 | "abpdev");
13 |
14 | public static string FilePath => Path.Combine(FolderPath, "updates.json");
15 |
16 | public async Task CheckAsync(bool force = false)
17 | {
18 | var data = await GetDataAsync();
19 |
20 | var currentVersion = typeof(UpdateCheckCommand).Assembly.GetName().Version;
21 |
22 | if (force || DateTime.Now >= data.NextCheck)
23 | {
24 | using var httpClient = new HttpClient();
25 |
26 | var nugetResponse = await httpClient
27 | .GetFromJsonAsync>("https://api.nuget.org/v3-flatcontainer/abpdevtools/index.json");
28 |
29 | var latestVersion = nugetResponse["versions"].Where(x => !x.Contains("-")).Last();
30 |
31 | data.LastChechLatestVersion = latestVersion;
32 | data.LastCheck = DateTime.Now;
33 | data.NextCheck = DateTime.Now.AddDays(1);
34 | await SaveDataAsync(data);
35 |
36 | return new UpdateCheckResult(
37 | currentVersion < Version.Parse(latestVersion),
38 | currentVersion.ToString(),
39 | latestVersion,
40 | DateTime.Now);
41 | }
42 |
43 | return new UpdateCheckResult(
44 | currentVersion < Version.Parse(data.LastChechLatestVersion),
45 | currentVersion.ToString(),
46 | data.LastChechLatestVersion,
47 | data.LastCheck);
48 | }
49 |
50 | private async Task GetDataAsync()
51 | {
52 | if (!File.Exists(FilePath))
53 | {
54 | await SaveDataAsync(new UpdatesData());
55 | }
56 |
57 | var json = await File.ReadAllTextAsync(FilePath);
58 | return JsonSerializer.Deserialize(json);
59 | }
60 |
61 | private async Task SaveDataAsync(UpdatesData data)
62 | {
63 | if (!Directory.Exists(FolderPath))
64 | {
65 | Directory.CreateDirectory(FolderPath);
66 | }
67 | await File.WriteAllTextAsync(FilePath, JsonSerializer.Serialize(data));
68 | }
69 |
70 | public class UpdatesData
71 | {
72 | public DateTime LastCheck { get; set; } = DateTime.Now;
73 | public DateTime NextCheck { get; set; } = DateTime.Now;
74 | public string LastChechLatestVersion { get; set; } = "1.0.0";
75 | }
76 | }
77 |
--------------------------------------------------------------------------------