@moon791017/neo-skills 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/GEMINI.md +115 -0
- package/README.md +155 -0
- package/bin/install-claude-skills.js +72 -0
- package/commands/neo/cd-app-service.toml +20 -0
- package/commands/neo/cd-iis.toml +20 -0
- package/commands/neo/ci-dotnet.toml +21 -0
- package/commands/neo/clarification.toml +48 -0
- package/commands/neo/code-review.toml +33 -0
- package/commands/neo/dotnet-gen-interface.toml +31 -0
- package/commands/neo/explain.toml +44 -0
- package/commands/neo/git-commit.toml +49 -0
- package/dist/hooks/secret-guard.js +2 -0
- package/dist/server.js +220 -0
- package/gemini-extension.json +15 -0
- package/package.json +39 -0
- package/skills/azure-pipelines/SKILL.md +45 -0
- package/skills/azure-pipelines/templates/build/build-dotnet.yml +92 -0
- package/skills/azure-pipelines/templates/deploy/deploy-app-service.yml +71 -0
- package/skills/azure-pipelines/templates/deploy/deploy-iis.yml +189 -0
- package/skills/azure-pipelines/templates/util/clean-artifact.yml +40 -0
- package/skills/azure-pipelines/templates/util/extract-artifact.yml +57 -0
- package/skills/azure-pipelines/templates/util/iis/iis-backup.yml +92 -0
- package/skills/azure-pipelines/templates/util/iis/iis-deploy-files.yml +112 -0
- package/skills/azure-pipelines/templates/util/iis/iis-manage-website.yml +112 -0
- package/skills/azure-pipelines/templates/util/iis/iis-rollback.yml +98 -0
- package/skills/azure-pipelines/templates/util/iis/iis-start-website.yml +89 -0
- package/skills/azure-pipelines/templates/util/iis/iis-stop-website.yml +80 -0
- package/skills/azure-pipelines/templates/util/iis/iis-task.yml +157 -0
- package/skills/azure-pipelines/templates/util/set-aspnetcore-env.yml +77 -0
- package/skills/clarification/SKILL.md +22 -0
- package/skills/code-review/SKILL.md +72 -0
- package/skills/csharp/SKILL.md +87 -0
- package/skills/csharp/reference/anti-patterns.md +142 -0
- package/skills/csharp/reference/coding-style.md +86 -0
- package/skills/csharp/reference/patterns.md +142 -0
- package/skills/csharp-interface-generator/SKILL.md +40 -0
- package/skills/dotnet/SKILL.md +41 -0
- package/skills/dotnet-ef-core/SKILL.md +78 -0
- package/skills/dotnet-ef-core/reference/anti-patterns.md +51 -0
- package/skills/dotnet-ef-core/reference/coding-style.md +42 -0
- package/skills/dotnet-ef-core/reference/patterns.md +53 -0
- package/skills/dotnet-minimal-apis/SKILL.md +78 -0
- package/skills/dotnet-minimal-apis/reference/anti-patterns.md +59 -0
- package/skills/dotnet-minimal-apis/reference/coding-style.md +54 -0
- package/skills/dotnet-minimal-apis/reference/patterns.md +68 -0
- package/skills/dotnet-mvc/SKILL.md +78 -0
- package/skills/dotnet-mvc/reference/anti-patterns.md +49 -0
- package/skills/dotnet-mvc/reference/coding-style.md +43 -0
- package/skills/dotnet-mvc/reference/patterns.md +56 -0
- package/skills/dotnet-webapi/SKILL.md +78 -0
- package/skills/dotnet-webapi/reference/anti-patterns.md +48 -0
- package/skills/dotnet-webapi/reference/coding-style.md +47 -0
- package/skills/dotnet-webapi/reference/patterns.md +52 -0
- package/skills/explain/SKILL.md +27 -0
- package/skills/git-commit/SKILL.md +84 -0
- package/skills/python/SKILL.md +61 -0
- package/skills/python/reference/anti-patterns.md +177 -0
- package/skills/python/reference/coding-style.md +92 -0
- package/skills/python/reference/patterns.md +112 -0
- package/skills/python-manager/SKILL.md +61 -0
- package/skills/start-plan/SKILL.md +29 -0
- package/skills/swift/SKILL.md +78 -0
- package/skills/swift/reference/anti-patterns.md +75 -0
- package/skills/swift/reference/coding-style.md +56 -0
- package/skills/swift/reference/patterns.md +94 -0
- package/skills/swift-ui/SKILL.md +76 -0
- package/skills/swift-ui/reference/anti-patterns.md +52 -0
- package/skills/swift-ui/reference/coding-style.md +46 -0
- package/skills/swift-ui/reference/patterns.md +87 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "neo-skills",
|
|
3
|
+
"description": "A universal capability extension for Gemini CLI",
|
|
4
|
+
"version": "0.20.0",
|
|
5
|
+
"mcpServers": {
|
|
6
|
+
"neo-skills": {
|
|
7
|
+
"command": "node",
|
|
8
|
+
"args": [
|
|
9
|
+
"${extensionPath}/dist/server.js"
|
|
10
|
+
]
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"hooks": "hooks/hooks.json",
|
|
14
|
+
"contextFileName": "GEMINI.md"
|
|
15
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@moon791017/neo-skills",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Neo Skills: A Universal Expert Agent Extension",
|
|
6
|
+
"bin": {
|
|
7
|
+
"neo-install-claude-skills": "./bin/install-claude-skills.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"bin",
|
|
12
|
+
"skills",
|
|
13
|
+
"commands",
|
|
14
|
+
"gemini-extension.json",
|
|
15
|
+
"GEMINI.md"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "bun run clean && bun run build:server && bun run build:hooks",
|
|
19
|
+
"build:server": "bun build src/server.ts --outdir dist --target node --minify",
|
|
20
|
+
"build:hooks": "bun build src/hooks/*.ts --outdir dist/hooks --target node --minify",
|
|
21
|
+
"install-deps": "npm install",
|
|
22
|
+
"clean": "rm -rf dist",
|
|
23
|
+
"start": "node dist/server.js",
|
|
24
|
+
"dev": "bun src/server.ts",
|
|
25
|
+
"typecheck": "tsc --noEmit",
|
|
26
|
+
"test": "echo \"No tests specified\" && exit 0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/bun": "latest",
|
|
30
|
+
"@types/node": "^25.5.0"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@modelcontextprotocol/sdk": "^1.28.0",
|
|
34
|
+
"axios": "^1.13.6",
|
|
35
|
+
"cheerio": "^1.2.0",
|
|
36
|
+
"toml": "^3.0.0",
|
|
37
|
+
"zod": "^4.3.6"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: azure-pipeline-architect
|
|
3
|
+
version: "1.0.0"
|
|
4
|
+
category: "DevOps"
|
|
5
|
+
description: "根據微軟官方文件與專案需求,設計並生成符合最新標準的 Azure Pipelines YAML 腳本。優先使用模組化範本 (Templates)。"
|
|
6
|
+
compatibility: "Supports .NET 6.0 up to .NET 10.0."
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Azure Pipeline Script Design Specifications
|
|
10
|
+
|
|
11
|
+
## Perceive
|
|
12
|
+
1. Access and retrieve official documentation at `https://learn.microsoft.com/en-us/azure/devops/pipelines/?view=azure-devops` to obtain the latest YAML syntax architecture, Task update notes, and security best practices.
|
|
13
|
+
2. Identify the application's development language (e.g., .NET, Java, Python, Node.js) and its specific version requirements.
|
|
14
|
+
3. Identify the target deployment platform (e.g., Azure App Service, Azure Kubernetes Service, Function App, or on-premises servers).
|
|
15
|
+
4. Detect the project source code structure to confirm build tools (e.g., Maven, Gradle, Npm, NuGet) and testing frameworks.
|
|
16
|
+
5. Read security and compliance requirements, including Static Application Security Testing (SAST), package vulnerability scanning, and container image scanning.
|
|
17
|
+
6. Confirm environment variable requirements, secret information sources (e.g., Azure Key Vault), and Service Connection permissions.
|
|
18
|
+
7. **Proactively scan the `skills/azure-pipelines/templates/` directory to identify existing reusable template resources. Includes:**
|
|
19
|
+
* **Build**: `build/build-dotnet.yml`
|
|
20
|
+
* **Deploy**: `deploy/deploy-app-service.yml`, `deploy/deploy-iis.yml`
|
|
21
|
+
* **Utils**: `util/clean-artifact.yml`, `util/extract-artifact.yml`, `util/iis/*.yml`, etc.
|
|
22
|
+
|
|
23
|
+
## Reason
|
|
24
|
+
1. Compare the latest versions in official documentation with existing configurations to determine if Task versions need updating (e.g., using Checkout@v1 vs. Checkout@v4).
|
|
25
|
+
2. Determine whether to adopt a multi-stage architecture based on project scale to achieve logical isolation of Build, Test, Staging, and Production.
|
|
26
|
+
3. Evaluate and design build caching strategies, optimizing dependency package folders to reduce execution time.
|
|
27
|
+
4. Design trigger mechanisms based on branching strategies, distinguishing trigger paths for Continuous Integration (CI) and Continuous Deployment (CD).
|
|
28
|
+
5. Determine the applicability of deployment strategies, such as Blue-Green, Canary, or Rolling Update.
|
|
29
|
+
6. Validate conditional execution syntax (Conditions) in Pipeline logic to ensure subsequent steps only execute on specific branches or after successful prerequisites.
|
|
30
|
+
7. **Prioritize using templates from `skills/azure-pipelines/templates/` to assemble the Pipeline, rather than writing raw YAML from scratch.**
|
|
31
|
+
* If the project is .NET and needs deployment to IIS, combine `build-dotnet.yml` and `deploy-iis.yml`.
|
|
32
|
+
* If artifact manipulation is required, prioritize using `util/extract-artifact.yml`.
|
|
33
|
+
|
|
34
|
+
## Act
|
|
35
|
+
1. Output global YAML configuration scripts that comply with the latest Azure DevOps Schema standards.
|
|
36
|
+
2. Provide comprehensive parameter definitions to increase the flexibility and reusability of Pipeline execution.
|
|
37
|
+
3. Generate configuration recommendations for Environments and Approvals and Checks.
|
|
38
|
+
4. Output a list of Task resource references used in the script, labeling version numbers to ensure execution environment consistency.
|
|
39
|
+
5. Provide preventive comments and explanations for common execution errors (e.g., insufficient permissions, dependency conflicts).
|
|
40
|
+
6. **Use `template` syntax to reference selected template files and correctly pass required parameters. For example:**
|
|
41
|
+
```yaml
|
|
42
|
+
- template: skills/azure-pipelines/templates/build/build-dotnet.yml
|
|
43
|
+
parameters:
|
|
44
|
+
buildConfiguration: 'Release'
|
|
45
|
+
```
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# 範本名稱: .NET CI Build Steps (Optimized with Caching)
|
|
2
|
+
# 說明: 此範本包含 .NET 專案持續整合所需的核心步驟,並已整合 NuGet 快取機制。
|
|
3
|
+
|
|
4
|
+
parameters:
|
|
5
|
+
- name: solution
|
|
6
|
+
type: string
|
|
7
|
+
- name: testSolution
|
|
8
|
+
type: string
|
|
9
|
+
default: ""
|
|
10
|
+
- name: buildPlatform
|
|
11
|
+
type: string
|
|
12
|
+
default: "Any CPU"
|
|
13
|
+
- name: buildConfiguration
|
|
14
|
+
type: string
|
|
15
|
+
default: "Release"
|
|
16
|
+
- name: dotnetSdkVersion
|
|
17
|
+
type: string
|
|
18
|
+
- name: nugetCachePath
|
|
19
|
+
type: string
|
|
20
|
+
default: "$(Pipeline.Workspace)/.nuget/packages"
|
|
21
|
+
- name: publishWebProjects
|
|
22
|
+
type: boolean
|
|
23
|
+
default: true
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
steps:
|
|
27
|
+
# 步驟 1: 設定 .NET 環境
|
|
28
|
+
- task: UseDotNet@2
|
|
29
|
+
displayName: "安裝 .NET SDK"
|
|
30
|
+
inputs:
|
|
31
|
+
packageType: "sdk"
|
|
32
|
+
version: "${{ parameters.dotnetSdkVersion }}"
|
|
33
|
+
|
|
34
|
+
# 步驟 2: NuGet 快取
|
|
35
|
+
- task: Cache@2
|
|
36
|
+
displayName: "快取 NuGet 套件"
|
|
37
|
+
inputs:
|
|
38
|
+
# 你的 Key 設定現在看起來很正確 (matches: 2 表示找到了 2 個專案檔)
|
|
39
|
+
key: 'nuget | "$(Agent.OS)" | **/*.csproj,!**/bin/**,!**/obj/**'
|
|
40
|
+
restoreKeys: |
|
|
41
|
+
nuget | "$(Agent.OS)"
|
|
42
|
+
path: "${{ parameters.nugetCachePath }}"
|
|
43
|
+
|
|
44
|
+
# 步驟 3: 還原專案相依性
|
|
45
|
+
# - env: NUGET_PACKAGES: 強制將還原路徑指向快取資料夾。
|
|
46
|
+
- task: DotNetCoreCLI@2
|
|
47
|
+
displayName: "還原 NuGet 套件"
|
|
48
|
+
inputs:
|
|
49
|
+
command: "restore"
|
|
50
|
+
projects: "${{ parameters.solution }}"
|
|
51
|
+
feedsToUse: "select"
|
|
52
|
+
env:
|
|
53
|
+
NUGET_PACKAGES: "${{ parameters.nugetCachePath }}"
|
|
54
|
+
|
|
55
|
+
# 步驟 4: 建置解決方案
|
|
56
|
+
# - noRestore: 因為上一步驟已還原,這裡加入 --no-restore 加快速度。
|
|
57
|
+
- task: DotNetCoreCLI@2
|
|
58
|
+
displayName: "建置專案"
|
|
59
|
+
inputs:
|
|
60
|
+
command: "build"
|
|
61
|
+
projects: "${{ parameters.solution }}"
|
|
62
|
+
arguments: "--configuration ${{ parameters.buildConfiguration }} --no-restore"
|
|
63
|
+
|
|
64
|
+
# 步驟 5: 如果testSolution是true,才執行單元測試
|
|
65
|
+
- ${{ if ne(parameters.testSolution, '') }}:
|
|
66
|
+
- task: DotNetCoreCLI@2
|
|
67
|
+
displayName: "執行測試"
|
|
68
|
+
inputs:
|
|
69
|
+
command: "test"
|
|
70
|
+
projects: "${{ parameters.testSolution }}"
|
|
71
|
+
# 加入 --no-build 與 --no-restore 避免重複編譯與還原,因為建置步驟已完成編譯
|
|
72
|
+
arguments: "--configuration ${{ parameters.buildConfiguration }} --no-build --no-restore"
|
|
73
|
+
|
|
74
|
+
# 步驟 6: 發佈應用程式
|
|
75
|
+
- task: DotNetCoreCLI@2
|
|
76
|
+
displayName: "產生發佈檔案"
|
|
77
|
+
inputs:
|
|
78
|
+
command: "publish"
|
|
79
|
+
publishWebProjects: ${{ parameters.publishWebProjects }}
|
|
80
|
+
projects: "${{ parameters.solution }}"
|
|
81
|
+
# 注意: Publish 通常需要重新建置以確保正確的 artifacts 結構,視專案設定而定,這裡保留標準行為
|
|
82
|
+
arguments: "--configuration ${{ parameters.buildConfiguration }} --output $(Build.ArtifactStagingDirectory) --no-restore"
|
|
83
|
+
# zipAfterPublish: false
|
|
84
|
+
# modifyOutputPath: false # 禁止自動建立專案名稱目錄,避免 Artifact 產生多餘的嵌套層級 (e.g. drop/Sample.WebSite/...)
|
|
85
|
+
|
|
86
|
+
# 步驟 7: 上傳發佈檔案
|
|
87
|
+
- task: PublishPipelineArtifact@1
|
|
88
|
+
displayName: "上傳發佈檔案"
|
|
89
|
+
inputs:
|
|
90
|
+
targetPath: "$(Build.ArtifactStagingDirectory)" # 必填 (要發佈的資料夾或檔案路徑。)
|
|
91
|
+
artifact: "drop" # 選填 (預設是drop)
|
|
92
|
+
publishLocation: "pipeline" # 選填 (預設是pipeline)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# ==============================================================================
|
|
2
|
+
# Template Name: Deploy to Azure App Service (deploy-app-service.yml)
|
|
3
|
+
# Description:
|
|
4
|
+
# This template encapsulates the standard process for deploying a website to Azure App Service.
|
|
5
|
+
# It includes two main steps:
|
|
6
|
+
# 1. Download Pipeline Artifact
|
|
7
|
+
# 2. Deploy to Azure App Service
|
|
8
|
+
# ==============================================================================
|
|
9
|
+
|
|
10
|
+
parameters:
|
|
11
|
+
# Azure Service Connection Name
|
|
12
|
+
- name: azureSubscription
|
|
13
|
+
type: string
|
|
14
|
+
|
|
15
|
+
# App Service Name
|
|
16
|
+
- name: webAppName
|
|
17
|
+
type: string
|
|
18
|
+
|
|
19
|
+
# App Service Type
|
|
20
|
+
- name: appType
|
|
21
|
+
type: string
|
|
22
|
+
default: "webApp"
|
|
23
|
+
values:
|
|
24
|
+
- "webApp" # Windows Web App
|
|
25
|
+
- "webAppLinux" # Linux Web App
|
|
26
|
+
|
|
27
|
+
# Source Pipeline Name (used for downloading Artifacts)
|
|
28
|
+
- name: sourcePipeline
|
|
29
|
+
type: string
|
|
30
|
+
default: "current"
|
|
31
|
+
|
|
32
|
+
# Name of the Artifact to download
|
|
33
|
+
- name: artifactName
|
|
34
|
+
type: string
|
|
35
|
+
default: "drop"
|
|
36
|
+
|
|
37
|
+
# Deployment package path (supports wildcards, e.g., $(Pipeline.Workspace)/drop/**/*.zip)
|
|
38
|
+
- name: packagePath
|
|
39
|
+
type: string
|
|
40
|
+
default: "$(Pipeline.Workspace)/drop/**/*.zip"
|
|
41
|
+
|
|
42
|
+
# Deployment slot name (default is production; if not production, it will try to deploy to that Slot)
|
|
43
|
+
- name: slotName
|
|
44
|
+
type: string
|
|
45
|
+
default: "production"
|
|
46
|
+
|
|
47
|
+
# Resource Group Name
|
|
48
|
+
- name: resourceGroupName
|
|
49
|
+
type: string
|
|
50
|
+
|
|
51
|
+
steps:
|
|
52
|
+
# ----------------------------------------------------------------------------
|
|
53
|
+
# Step 1: Download Artifact
|
|
54
|
+
# ----------------------------------------------------------------------------
|
|
55
|
+
- download: ${{ parameters.sourcePipeline }}
|
|
56
|
+
artifact: ${{ parameters.artifactName }}
|
|
57
|
+
displayName: "Download Website Publish Files"
|
|
58
|
+
|
|
59
|
+
# ----------------------------------------------------------------------------
|
|
60
|
+
# Step 2: Deploy to Azure App Service
|
|
61
|
+
# ----------------------------------------------------------------------------
|
|
62
|
+
- task: AzureWebApp@1
|
|
63
|
+
displayName: "Deploy to Azure App Service"
|
|
64
|
+
inputs:
|
|
65
|
+
azureSubscription: ${{ parameters.azureSubscription }} # Azure subscription service connection name
|
|
66
|
+
appType: ${{ parameters.appType }} # Web App type (webApp or webAppLinux)
|
|
67
|
+
appName: ${{ parameters.webAppName }} # Azure App Service name
|
|
68
|
+
package: ${{ parameters.packagePath }} # Deployment package path (.zip)
|
|
69
|
+
deployToSlotOrASE: ${{ ne(parameters.slotName, 'production') }} # Whether to deploy to Slot or App Service Environment
|
|
70
|
+
resourceGroupName: ${{ parameters.resourceGroupName }} # Resource group name (required when deploying to Slot)
|
|
71
|
+
slotName: ${{ parameters.slotName }} # Deployment Slot name (default is production)
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# ==============================================================================
|
|
2
|
+
# Template Name: Deploy to IIS on Machine Group (deploy-iis.yml)
|
|
3
|
+
# Description:
|
|
4
|
+
# This template encapsulates the standard process for deploying a website to On-Premises IIS.
|
|
5
|
+
# Executed using Machine Group Agents (Environment).
|
|
6
|
+
# Includes three main steps:
|
|
7
|
+
# 1. Download Artifact
|
|
8
|
+
# 2. Manage IIS Website and Application Pool (Task) - Handles site creation, binding, and AppPool settings
|
|
9
|
+
# 3. Deploy application physical files - Includes file cleanup and variable substitution
|
|
10
|
+
# ==============================================================================
|
|
11
|
+
|
|
12
|
+
parameters:
|
|
13
|
+
# Website Name (Display name in IIS)
|
|
14
|
+
- name: websiteName
|
|
15
|
+
type: string
|
|
16
|
+
|
|
17
|
+
# Website Physical Path (Absolute path on the server)
|
|
18
|
+
- name: websitePhysicalPath
|
|
19
|
+
type: string
|
|
20
|
+
|
|
21
|
+
# Site Binding Settings (JSON List)
|
|
22
|
+
# Format example: '[{"type":"http","port":80,"ip":"*","hostName":"sample.local"}]'
|
|
23
|
+
- name: bindings
|
|
24
|
+
type: string
|
|
25
|
+
default: "[]"
|
|
26
|
+
|
|
27
|
+
# Whether to create or update the Application Pool (AppPool)
|
|
28
|
+
- name: createOrUpdateAppPool
|
|
29
|
+
type: boolean
|
|
30
|
+
default: true
|
|
31
|
+
|
|
32
|
+
# Application Pool Name
|
|
33
|
+
- name: appPoolName
|
|
34
|
+
type: string
|
|
35
|
+
|
|
36
|
+
# AppPool .NET CLR Version (For .NET Core/5+, 'No Managed Code' is recommended)
|
|
37
|
+
- name: appPoolDotNetVersion
|
|
38
|
+
type: string
|
|
39
|
+
default: "No Managed Code"
|
|
40
|
+
values:
|
|
41
|
+
- "No Managed Code"
|
|
42
|
+
- "v4.0"
|
|
43
|
+
- "v2.0"
|
|
44
|
+
|
|
45
|
+
# ASP.NET Core Environment Variable (e.g., Development, Production)
|
|
46
|
+
- name: aspNetCoreEnvironment
|
|
47
|
+
type: string
|
|
48
|
+
default: "Production"
|
|
49
|
+
|
|
50
|
+
# Source Pipeline Name (used for downloading Artifacts)
|
|
51
|
+
- name: sourcePipeline
|
|
52
|
+
type: string
|
|
53
|
+
default: "current"
|
|
54
|
+
|
|
55
|
+
# Name of the Artifact to download
|
|
56
|
+
- name: artifactName
|
|
57
|
+
type: string
|
|
58
|
+
default: "drop"
|
|
59
|
+
|
|
60
|
+
# Whether to clean extra files in the destination folder (Recommended to keep environment clean, default true)
|
|
61
|
+
- name: cleanDestination
|
|
62
|
+
type: boolean
|
|
63
|
+
default: true
|
|
64
|
+
|
|
65
|
+
# Backup Path
|
|
66
|
+
- name: backupPath
|
|
67
|
+
type: string
|
|
68
|
+
|
|
69
|
+
# Backup Retention Count
|
|
70
|
+
- name: backupRetentionCount
|
|
71
|
+
type: number
|
|
72
|
+
default: 5
|
|
73
|
+
|
|
74
|
+
steps:
|
|
75
|
+
# ----------------------------------------------------------------------------
|
|
76
|
+
# Step 1: Download Artifact
|
|
77
|
+
# ----------------------------------------------------------------------------
|
|
78
|
+
- download: ${{ parameters.sourcePipeline }}
|
|
79
|
+
artifact: ${{ parameters.artifactName }}
|
|
80
|
+
displayName: "Download Website Publish Files"
|
|
81
|
+
|
|
82
|
+
# ----------------------------------------------------------------------------
|
|
83
|
+
# Step 2: Extract Artifact (if it is a zip)
|
|
84
|
+
# This is to allow subsequent web.config modification and JSON variable substitution to work
|
|
85
|
+
# ----------------------------------------------------------------------------
|
|
86
|
+
- template: ../util/extract-artifact.yml
|
|
87
|
+
parameters:
|
|
88
|
+
artifactName: ${{ parameters.artifactName }}
|
|
89
|
+
sourcePipeline: ${{ parameters.sourcePipeline }}
|
|
90
|
+
runCondition: "succeeded()"
|
|
91
|
+
|
|
92
|
+
# ----------------------------------------------------------------------------
|
|
93
|
+
# Step 3: Pre-modify web.config (Set Environment Variables)
|
|
94
|
+
# Handle web.config modification through a separate Template to keep the main file clean.
|
|
95
|
+
# ----------------------------------------------------------------------------
|
|
96
|
+
- template: ../util/set-aspnetcore-env.yml
|
|
97
|
+
parameters:
|
|
98
|
+
aspNetCoreEnvironment: ${{ parameters.aspNetCoreEnvironment }}
|
|
99
|
+
packagePath: "$(WebDeployPackagePath)"
|
|
100
|
+
# Execution condition: succeeded + environment variable is not empty
|
|
101
|
+
runCondition: "and(succeeded(), ne('${{ parameters.aspNetCoreEnvironment }}', ''))"
|
|
102
|
+
|
|
103
|
+
# ----------------------------------------------------------------------------
|
|
104
|
+
# Step 4: Manage IIS Website (Create/Update)
|
|
105
|
+
# Create website and assign AppPool. For .NET Core/5+ apps, AppPool is recommended to be 'No Managed Code'.
|
|
106
|
+
# Execute PowerShell script using external template
|
|
107
|
+
# ----------------------------------------------------------------------------
|
|
108
|
+
- template: ../util/iis/iis-task.yml
|
|
109
|
+
parameters:
|
|
110
|
+
command: manage
|
|
111
|
+
websiteName: ${{ parameters.websiteName }}
|
|
112
|
+
websitePhysicalPath: ${{ parameters.websitePhysicalPath }}
|
|
113
|
+
appPoolName: ${{ parameters.appPoolName }}
|
|
114
|
+
appPoolDotNetVersion: ${{ parameters.appPoolDotNetVersion }}
|
|
115
|
+
bindings: ${{ parameters.bindings }}
|
|
116
|
+
runCondition: "succeeded()"
|
|
117
|
+
|
|
118
|
+
# ----------------------------------------------------------------------------
|
|
119
|
+
# Step 5: Mark Deployment Start
|
|
120
|
+
# ----------------------------------------------------------------------------
|
|
121
|
+
- powershell: Write-Host "##vso[task.setvariable variable=_IISDeploymentStarted]true"
|
|
122
|
+
displayName: "Mark Deployment Start (Set Deployment Flag)"
|
|
123
|
+
# ----------------------------------------------------------------------------
|
|
124
|
+
# Step 6: Stop IIS Website
|
|
125
|
+
# ----------------------------------------------------------------------------
|
|
126
|
+
- template: ../util/iis/iis-task.yml
|
|
127
|
+
parameters:
|
|
128
|
+
command: stop
|
|
129
|
+
websiteName: ${{ parameters.websiteName }}
|
|
130
|
+
runCondition: "and(succeeded(), eq(variables['_IISDeploymentStarted'], 'true'))"
|
|
131
|
+
|
|
132
|
+
# ----------------------------------------------------------------------------
|
|
133
|
+
# Step 7: Backup Old Website Content
|
|
134
|
+
# ----------------------------------------------------------------------------
|
|
135
|
+
- template: ../util/iis/iis-task.yml
|
|
136
|
+
parameters:
|
|
137
|
+
command: backup
|
|
138
|
+
websitePhysicalPath: ${{ parameters.websitePhysicalPath }}
|
|
139
|
+
backupPath: ${{ parameters.backupPath }}
|
|
140
|
+
backupRetentionCount: ${{ parameters.backupRetentionCount }}
|
|
141
|
+
# Execution condition: succeeded + deployment started + backup path is not empty
|
|
142
|
+
runCondition: "and(succeeded(), eq(variables['_IISDeploymentStarted'], 'true'), ne('${{ parameters.backupPath }}', ''))"
|
|
143
|
+
|
|
144
|
+
# ----------------------------------------------------------------------------
|
|
145
|
+
# Step 8: Deploy Application Files
|
|
146
|
+
# Copy Artifact to destination path.
|
|
147
|
+
# Execute Copy-Item using external template
|
|
148
|
+
# ----------------------------------------------------------------------------
|
|
149
|
+
- template: ../util/iis/iis-task.yml
|
|
150
|
+
parameters:
|
|
151
|
+
command: deploy
|
|
152
|
+
sourcePath: "$(WebDeployPackagePath)"
|
|
153
|
+
destinationPath: ${{ parameters.websitePhysicalPath }}
|
|
154
|
+
cleanDestination: ${{ parameters.cleanDestination }}
|
|
155
|
+
# Execution condition: succeeded + deployment started
|
|
156
|
+
runCondition: "and(succeeded(), eq(variables['_IISDeploymentStarted'], 'true'))"
|
|
157
|
+
|
|
158
|
+
# ----------------------------------------------------------------------------
|
|
159
|
+
# Step 9: Start IIS Website
|
|
160
|
+
# Execute PowerShell script using external template
|
|
161
|
+
# ----------------------------------------------------------------------------
|
|
162
|
+
- template: ../util/iis/iis-task.yml
|
|
163
|
+
parameters:
|
|
164
|
+
command: start
|
|
165
|
+
websiteName: ${{ parameters.websiteName }}
|
|
166
|
+
# Execution condition: succeeded + deployment started
|
|
167
|
+
runCondition: "and(succeeded(), eq(variables['_IISDeploymentStarted'], 'true'))"
|
|
168
|
+
|
|
169
|
+
# ----------------------------------------------------------------------------
|
|
170
|
+
# Step 10: Rollback Mechanism
|
|
171
|
+
# Execute restore and restart when deployment fails (any of Step 6~9 fails)
|
|
172
|
+
# ----------------------------------------------------------------------------
|
|
173
|
+
- template: ../util/iis/iis-task.yml
|
|
174
|
+
parameters:
|
|
175
|
+
command: rollback
|
|
176
|
+
websiteName: ${{ parameters.websiteName }}
|
|
177
|
+
websitePhysicalPath: ${{ parameters.websitePhysicalPath }}
|
|
178
|
+
# Execution condition: failed + deployment started
|
|
179
|
+
runCondition: "and(failed(), eq(variables['_IISDeploymentStarted'], 'true'))"
|
|
180
|
+
|
|
181
|
+
# ----------------------------------------------------------------------------
|
|
182
|
+
# Step 9: Clean Temporary Files
|
|
183
|
+
# Execute cleanup regardless of deployment success or failure to release space (Avoid Artifact accumulation in local IIS azagent folder)
|
|
184
|
+
# ----------------------------------------------------------------------------
|
|
185
|
+
- template: ../util/clean-artifact.yml
|
|
186
|
+
parameters:
|
|
187
|
+
artifactName: ${{ parameters.artifactName }}
|
|
188
|
+
sourcePipeline: ${{ parameters.sourcePipeline }}
|
|
189
|
+
runCondition: "always()"
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# ==============================================================================
|
|
2
|
+
# Template Name: Clean Artifact (clean-artifact.yml)
|
|
3
|
+
# Description:
|
|
4
|
+
# Clean up Artifact temporary files downloaded to the Agent.
|
|
5
|
+
# Usually executed at the end of the deployment process to ensure disk space is released.
|
|
6
|
+
# Configured with condition: always() to ensure execution regardless of deployment success or failure.
|
|
7
|
+
# ==============================================================================
|
|
8
|
+
|
|
9
|
+
parameters:
|
|
10
|
+
- name: artifactName
|
|
11
|
+
type: string
|
|
12
|
+
- name: sourcePipeline
|
|
13
|
+
type: string
|
|
14
|
+
|
|
15
|
+
# Execution Condition
|
|
16
|
+
- name: runCondition
|
|
17
|
+
type: string
|
|
18
|
+
default: 'always()'
|
|
19
|
+
|
|
20
|
+
steps:
|
|
21
|
+
- powershell: |
|
|
22
|
+
$artifactName = "${{ parameters.artifactName }}"
|
|
23
|
+
$sourcePipeline = "${{ parameters.sourcePipeline }}"
|
|
24
|
+
|
|
25
|
+
# Determine Artifact Path
|
|
26
|
+
$artifactPath = "$(Pipeline.Workspace)/$artifactName"
|
|
27
|
+
if ($sourcePipeline -ne 'current') {
|
|
28
|
+
$artifactPath = "$(Pipeline.Workspace)/$sourcePipeline/$artifactName"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
Write-Host "Cleaning up artifact at: $artifactPath"
|
|
32
|
+
|
|
33
|
+
if (Test-Path $artifactPath) {
|
|
34
|
+
Remove-Item -Path $artifactPath -Recurse -Force -ErrorAction SilentlyContinue
|
|
35
|
+
Write-Host "Artifact cleaned up successfully."
|
|
36
|
+
} else {
|
|
37
|
+
Write-Host "Artifact path not found, nothing to clean."
|
|
38
|
+
}
|
|
39
|
+
displayName: 'Clean Artifact Temporary Files'
|
|
40
|
+
condition: ${{ parameters.runCondition }}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# ==============================================================================
|
|
2
|
+
# Template Name: Extract Artifact (extract-artifact.yml)
|
|
3
|
+
# Description:
|
|
4
|
+
# Check if the specified Artifact is a ZIP archive.
|
|
5
|
+
# If so, extract it to the 'extracted' subdirectory.
|
|
6
|
+
# Finally, set the variable 'WebDeployPackagePath' for subsequent deployment steps.
|
|
7
|
+
# ==============================================================================
|
|
8
|
+
|
|
9
|
+
parameters:
|
|
10
|
+
- name: artifactName
|
|
11
|
+
type: string
|
|
12
|
+
- name: sourcePipeline
|
|
13
|
+
type: string
|
|
14
|
+
default: 'current'
|
|
15
|
+
|
|
16
|
+
# Execution Condition
|
|
17
|
+
- name: runCondition
|
|
18
|
+
type: string
|
|
19
|
+
default: 'succeeded()'
|
|
20
|
+
|
|
21
|
+
steps:
|
|
22
|
+
- powershell: |
|
|
23
|
+
$artifactName = "${{ parameters.artifactName }}"
|
|
24
|
+
$sourcePipeline = "${{ parameters.sourcePipeline }}"
|
|
25
|
+
|
|
26
|
+
# Determine base path
|
|
27
|
+
$basePath = "$(Pipeline.Workspace)/$artifactName"
|
|
28
|
+
if ($sourcePipeline -ne 'current') {
|
|
29
|
+
$basePath = "$(Pipeline.Workspace)/$sourcePipeline/$artifactName"
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
Write-Host "Checking artifact at: $basePath"
|
|
33
|
+
$packagePath = $basePath
|
|
34
|
+
|
|
35
|
+
# Check for ZIP file
|
|
36
|
+
if (Test-Path $basePath) {
|
|
37
|
+
$zipFile = Get-ChildItem -Path $basePath -Filter "*.zip" | Select-Object -First 1
|
|
38
|
+
|
|
39
|
+
if ($zipFile) {
|
|
40
|
+
Write-Host "Found compressed artifact: $($zipFile.FullName)"
|
|
41
|
+
$extractPath = "$basePath/extracted"
|
|
42
|
+
|
|
43
|
+
Write-Host "Extracting to: $extractPath"
|
|
44
|
+
# Clean old extraction directory (if exists)
|
|
45
|
+
if (Test-Path $extractPath) { Remove-Item $extractPath -Recurse -Force }
|
|
46
|
+
|
|
47
|
+
Expand-Archive -Path $zipFile.FullName -DestinationPath $extractPath -Force
|
|
48
|
+
$packagePath = $extractPath
|
|
49
|
+
} else {
|
|
50
|
+
Write-Host "No zip file found. Assuming loose files."
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
Write-Host "Setting WebDeployPackagePath to: $packagePath"
|
|
55
|
+
Write-Host "##vso[task.setvariable variable=WebDeployPackagePath]$packagePath"
|
|
56
|
+
displayName: 'Check and Extract Artifact'
|
|
57
|
+
condition: ${{ parameters.runCondition }}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# ==============================================================================
|
|
2
|
+
# Template Name: IIS Website Backup (backup-iis.yml)
|
|
3
|
+
# Description:
|
|
4
|
+
# Compress and backup the contents of the specified website physical path.
|
|
5
|
+
# 1. Compress to zip (Filename: yyyy-MM-dd-HH-mm-ss.zip)
|
|
6
|
+
# 2. Save to backupPath
|
|
7
|
+
# 3. Keep the latest N backups based on backupRetentionCount
|
|
8
|
+
# ==============================================================================
|
|
9
|
+
|
|
10
|
+
parameters:
|
|
11
|
+
- name: websitePhysicalPath
|
|
12
|
+
type: string
|
|
13
|
+
- name: backupPath
|
|
14
|
+
type: string
|
|
15
|
+
- name: backupRetentionCount
|
|
16
|
+
type: number
|
|
17
|
+
default: 5
|
|
18
|
+
|
|
19
|
+
# Execution Condition
|
|
20
|
+
- name: runCondition
|
|
21
|
+
type: string
|
|
22
|
+
default: 'succeeded()'
|
|
23
|
+
|
|
24
|
+
steps:
|
|
25
|
+
- powershell: |
|
|
26
|
+
$srcRaw = "${{ parameters.websitePhysicalPath }}"
|
|
27
|
+
$destDirRaw = "${{ parameters.backupPath }}"
|
|
28
|
+
$limit = ${{ parameters.backupRetentionCount }}
|
|
29
|
+
|
|
30
|
+
# Expand Environment Variables (e.g. %SystemDrive% -> C:)
|
|
31
|
+
$src = [System.Environment]::ExpandEnvironmentVariables($srcRaw)
|
|
32
|
+
$destDir = [System.Environment]::ExpandEnvironmentVariables($destDirRaw)
|
|
33
|
+
|
|
34
|
+
Write-Host "Starting Backup Process..."
|
|
35
|
+
Write-Host "Source (Raw): $srcRaw"
|
|
36
|
+
Write-Host "Source (Expanded): $src"
|
|
37
|
+
Write-Host "Destination (Raw): $destDirRaw"
|
|
38
|
+
Write-Host "Destination (Expanded): $destDir"
|
|
39
|
+
Write-Host "Retention Count: $limit"
|
|
40
|
+
|
|
41
|
+
# 1. Check if source exists
|
|
42
|
+
if (-not (Test-Path $src)) {
|
|
43
|
+
Write-Warning "Source path '$src' does not exist. Skipping backup."
|
|
44
|
+
exit 0
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
# 2. Check if source is empty
|
|
48
|
+
if ((Get-ChildItem $src -Force).Count -eq 0) {
|
|
49
|
+
Write-Host "Source directory is empty. Nothing to backup."
|
|
50
|
+
exit 0
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# 3. Create backup directory (if not exists)
|
|
54
|
+
if (-not (Test-Path $destDir)) {
|
|
55
|
+
Write-Host "Backup directory does not exist. Creating: $destDir"
|
|
56
|
+
New-Item -ItemType Directory -Force -Path $destDir | Out-Null
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# 4. Execute Compression
|
|
60
|
+
$timestamp = Get-Date -Format "yyyy-MM-dd-HH-mm-ss"
|
|
61
|
+
$zipName = "$timestamp.zip"
|
|
62
|
+
$zipPath = Join-Path $destDir $zipName
|
|
63
|
+
|
|
64
|
+
Write-Host "Archiving content to: $zipPath"
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
Compress-Archive -Path "$src\*" -DestinationPath $zipPath -Force -ErrorAction Stop
|
|
68
|
+
Write-Host "Backup created successfully."
|
|
69
|
+
# Set variable for Rollback use
|
|
70
|
+
Write-Host "##vso[task.setvariable variable=_GeneratedBackupPath]$zipPath"
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
Write-Warning "Failed to create backup. Error: $_"
|
|
74
|
+
exit 0
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
# 5. Apply Retention Policy
|
|
78
|
+
Write-Host "Applying retention policy (Keep latest $limit)..."
|
|
79
|
+
$backups = Get-ChildItem -Path $destDir -Filter "*.zip" | Sort-Object CreationTime -Descending
|
|
80
|
+
|
|
81
|
+
if ($backups.Count -gt $limit) {
|
|
82
|
+
$toDelete = $backups | Select-Object -Skip $limit
|
|
83
|
+
foreach ($file in $toDelete) {
|
|
84
|
+
Write-Host "Deleting old backup: $($file.Name)"
|
|
85
|
+
Remove-Item $file.FullName -Force
|
|
86
|
+
}
|
|
87
|
+
} else {
|
|
88
|
+
Write-Host "Current backup count ($($backups.Count)) is within limit ($limit)."
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
displayName: 'Execute IIS Website Backup'
|
|
92
|
+
condition: ${{ parameters.runCondition }}
|