# Bài 9: Đóng gói Plugin

> **Packaging — Từ Source Code đến Sản phẩm có thể Phân phối**
>
> *Bạn đã xây dựng xong plugin. Giờ là lúc đóng gói và đưa đến tay người dùng một cách chuyên nghiệp.*
>
> **Tham chiếu triển khai Tool/MCP Server:** [revit-mcp-server](https://github.com/nguyenngocdue/revit-mcp-server)

***

## Mục lục

* [Giới thiệu bài học](#giới-thiệu-bài-học)
* [Tổng quan quy trình đóng gói](#tổng-quan-quy-trình-đóng-gói)
* [Build Release trong Visual Studio](#build-release-trong-visual-studio)
* [Tạo file .addin Manifest](#tạo-file-addin-manifest)
* [Cấu trúc thư mục đóng gói](#cấu-trúc-thư-mục-đóng-gói)
* [Script cài đặt tự động với PowerShell](#script-cài-đặt-tự-động-với-powershell)
* [Tạo README hướng dẫn sử dụng](#tạo-readme-hướng-dẫn-sử-dụng)
* [Versioning và cập nhật](#versioning-và-cập-nhật)
* [Phân phối qua GitHub Releases](#phân-phối-qua-github-releases)
* [Câu hỏi tự suy nghĩ](#câu-hỏi-tự-suy-nghĩ)
* [Tổng kết](#tổng-kết)
* [Điều hướng](#điều-hướng)

***

## Giới thiệu bài học

Trong các bài học trước, chúng ta đã xây dựng toàn bộ hệ thống **Revit Plugin + MCP Server + AI Client**. Plugin có thể nhận lệnh từ AI, thực thi Revit API, và trả kết quả về. Đây là một thành tựu kỹ thuật đáng kể.

Tuy nhiên, một plugin chỉ chạy được trên máy của bạn thì chưa phải là sản phẩm. Bài học này sẽ hướng dẫn bạn:

1. **Build bản Release** — Tối ưu hóa mã và loại bỏ thông tin debug
2. **Tạo file Manifest** — Khai báo plugin với Revit theo chuẩn Autodesk
3. **Tổ chức cấu trúc gói** — Đặt đúng vị trí các file cần thiết
4. **Viết script cài đặt** — Giúp người dùng cài đặt chỉ với một cú nhấp chuột
5. **Đánh số phiên bản** — Quản lý cập nhật một cách có hệ thống
6. **Phân phối qua GitHub** — Đưa plugin đến cộng đồng BIM

> **Mục tiêu:** Sau bài học này, bạn có thể đóng gói và phân phối plugin Revit của mình một cách chuyên nghiệp, đúng theo tiêu chuẩn của Autodesk App Store và cộng đồng open-source.

***

## Tổng quan quy trình đóng gói

Trước khi đi vào chi tiết, hãy hình dung toàn bộ quy trình từ source code đến tay người dùng.

### Sơ đồ Build Pipeline

```mermaid
flowchart TD
    A([Source Code\nC# + TypeScript]) --> B[Build Release\nVisual Studio]
    B --> C{Kiểm tra\nBuild thành công?}
    C -- Không --> D[Sửa lỗi\nBuild Errors]
    D --> B
    C -- Có --> E[Tạo .addin Manifest\nXML Config]
    E --> F[Tổ chức\nThư mục Gói]
    F --> G[Viết Install Script\nPowerShell]
    G --> H[Kiểm thử\nCài đặt trên máy mới]
    H --> I{Cài đặt\nthành công?}
    I -- Không --> J[Debug\nScript và Paths]
    J --> G
    I -- Có --> K[Tạo ZIP Package]
    K --> L[Đánh Tag Version\nGit]
    L --> M[GitHub Release\nUpload Assets]
    M --> N([Plugin sẵn sàng\nPhân phối])

    style A fill:#4A90D9,color:#fff
    style N fill:#27AE60,color:#fff
    style C fill:#F39C12,color:#fff
    style I fill:#F39C12,color:#fff
```

### Sơ đồ Luồng Cài đặt cho Người dùng

```mermaid
sequenceDiagram
    participant U as Người dùng
    participant GH as GitHub Releases
    participant PS as PowerShell Script
    participant FS as File System
    participant RV as Revit

    U->>GH: Tải file ZIP từ Releases
    U->>FS: Giải nén vào thư mục bất kỳ
    U->>PS: Chạy Install.ps1 (Run with PowerShell)
    PS->>FS: Kiểm tra phiên bản Revit đang cài
    PS->>FS: Sao chép DLL vào %APPDATA%\Autodesk\Revit\Addins\2024\
    PS->>FS: Sao chép file .addin manifest
    PS->>FS: Cài đặt Node.js dependencies (npm install)
    PS-->>U: Thông báo cài đặt thành công
    U->>RV: Khởi động lại Revit
    RV->>FS: Đọc file .addin manifest khi khởi động
    RV-->>U: Plugin xuất hiện trong ribbon DeepBIM
```

***

## Build Release trong Visual Studio

### Sự khác biệt giữa Debug và Release

Khi phát triển, chúng ta thường dùng chế độ **Debug** vì nó dễ kiểm tra lỗi. Tuy nhiên, khi phân phối, bạn **bắt buộc** phải build bằng chế độ **Release**.

| Thuộc tính       | Debug Build            | Release Build              |
| ---------------- | ---------------------- | -------------------------- |
| Kích thước DLL   | Lớn hơn (chứa symbols) | Nhỏ hơn (\~30–50%)         |
| Tốc độ thực thi  | Chậm hơn               | Nhanh hơn (tối ưu hóa JIT) |
| Thông tin debug  | Có (kèm file PDB)      | Không (tùy chọn)           |
| Khối `#if DEBUG` | Được biên dịch         | Bị bỏ qua                  |
| Assertions       | Bật                    | Tắt                        |
| Phù hợp          | Phát triển và kiểm thử | Phân phối sản phẩm         |

### Các bước Build Release

**Bước 1 — Chuyển Configuration sang Release:**

Trong Visual Studio, tìm thanh toolbar phía trên, click vào dropdown đang hiển thị `Debug` và chọn `Release`.

**Bước 2 — Kiểm tra cấu hình Project:**

Mở `Project Properties` → Tab `Build` và xác nhận các thiết lập sau:

```xml
<!-- Trong file .csproj -->
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
  <DebugType>none</DebugType>
  <DebugSymbols>false</DebugSymbols>
  <Optimize>true</Optimize>
  <DefineConstants></DefineConstants>
  <!-- QUAN TRỌNG: Revit chỉ chạy trên 64-bit -->
  <PlatformTarget>x64</PlatformTarget>
</PropertyGroup>
```

**Bước 3 — Cập nhật Assembly Information:**

Mở file `Properties/AssemblyInfo.cs` và điền đầy đủ thông tin:

```csharp
using System.Reflection;
using System.Runtime.InteropServices;

[assembly: AssemblyTitle("DeepBIM Revit MCP Plugin")]
[assembly: AssemblyDescription("Kết nối Revit với AI thông qua Model Context Protocol")]
[assembly: AssemblyCompany("DeepBIM")]
[assembly: AssemblyProduct("DeepBIM Revit MCP Plugin")]
[assembly: AssemblyCopyright("Copyright © DeepBIM 2024")]

// Đặt false để tránh xung đột với các assembly khác
[assembly: ComVisible(false)]

// Phiên bản theo định dạng SemVer: Major.Minor.Patch
// Major : Thay đổi lớn, phá vỡ tương thích (breaking changes)
// Minor : Tính năng mới, tương thích ngược
// Patch : Sửa lỗi nhỏ
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
```

**Bước 4 — Chạy Build:**

Nhấn `Ctrl + Shift + B` hoặc vào menu `Build` → `Build Solution`. Quan sát cửa sổ **Output** — đảm bảo không có lỗi `error` (cảnh báo `warning` có thể chấp nhận được).

**Bước 5 — Xác minh Output:**

```
Sau khi build thành công, file DLL sẽ nằm tại:
<ProjectRoot>\bin\Release\DeepBIM.RevitMCP.Plugin.dll

Lưu ý: Bản Release thương mại không nên kèm file .pdb.
```

> **Cảnh báo quan trọng:** Nếu plugin tham chiếu đến `RevitAPI.dll` và `RevitAPIUI.dll`, hãy đảm bảo thuộc tính `Copy Local` của chúng được đặt thành `false`. Những file này đã có sẵn trong thư mục cài đặt Revit và **tuyệt đối không được** đóng gói cùng plugin (vi phạm điều khoản Autodesk).

***

## Tạo file .addin Manifest

File `.addin` là cầu nối giữa plugin của bạn và Revit. Đây là file XML mà Revit đọc khi khởi động để biết cần tải plugin nào và tải từ đâu.

### Vị trí đặt file .addin

Revit tìm kiếm file `.addin` theo thứ tự ưu tiên:

| Loại cài đặt                           | Đường dẫn                                    |
| -------------------------------------- | -------------------------------------------- |
| Dành cho tất cả người dùng (cần Admin) | `C:\ProgramData\Autodesk\Revit\Addins\2024\` |
| Chỉ dành cho người dùng hiện tại       | `%APPDATA%\Autodesk\Revit\Addins\2024\`      |

> **Khuyến nghị:** Dùng `%APPDATA%` (per-user) để tránh cần quyền Administrator và dễ gỡ cài đặt hơn.

### Cấu trúc file .addin hoàn chỉnh

```xml
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!--
  DeepBIM Revit MCP Plugin — Manifest File
  Tham khảo: https://github.com/nguyenngocdue/deepbim-revit-mcp-plugin

  Revit đọc file này khi khởi động để tải plugin.
  Mỗi thẻ <AddIn> định nghĩa một thành phần riêng biệt.
-->
<RevitAddIns>

  <!--
    ================================================================
    EXTERNAL APPLICATION
    Class này implements IExternalApplication — chạy khi Revit
    khởi động và tắt. Đây là điểm vào chính để tạo ribbon UI.
    ================================================================
  -->
  <AddIn Type="Application">
    <Name>DeepBIM MCP Application</Name>

    <!--
      Assembly: Đường dẫn đến file DLL.
      Khuyến nghị dùng đường dẫn tuyệt đối để tránh nhầm lẫn.
      Script cài đặt sẽ tự động điền đường dẫn chính xác.
    -->
    <Assembly>
      %APPDATA%\Autodesk\Revit\Addins\2024\DeepBIM\DeepBIM.RevitMCP.Plugin.dll
    </Assembly>

    <!--
      FullClassName: Tên đầy đủ của class, bao gồm namespace.
    -->
    <FullClassName>
      DeepBIM.RevitMCP.Plugin.Application.MCPApplication
    </FullClassName>

    <!--
      ClientId: GUID duy nhất cho plugin.
      Tạo GUID mới: [System.Guid]::NewGuid() trong PowerShell
      KHÔNG BAO GIỜ dùng lại GUID của plugin khác.
    -->
    <ClientId>a1b2c3d4-e5f6-7890-abcd-ef1234567890</ClientId>

    <VendorId>DEEPBIM</VendorId>
    <VendorDescription>
      DeepBIM — BIM Intelligence Platform,
      https://github.com/nguyenngocdue/deepbim-revit-mcp-plugin
    </VendorDescription>
  </AddIn>

  <!--
    ================================================================
    EXTERNAL COMMAND — Khởi động MCP Server
    ================================================================
  -->
  <AddIn Type="Command">
    <Name>DeepBIM: Khởi động MCP Server</Name>
    <Assembly>
      %APPDATA%\Autodesk\Revit\Addins\2024\DeepBIM\DeepBIM.RevitMCP.Plugin.dll
    </Assembly>
    <FullClassName>
      DeepBIM.RevitMCP.Plugin.Commands.StartMCPServerCommand
    </FullClassName>
    <ClientId>b2c3d4e5-f6a7-8901-bcde-f12345678901</ClientId>
    <VendorId>DEEPBIM</VendorId>
    <VendorDescription>Khởi động MCP Server để nhận lệnh từ AI</VendorDescription>
    <Text>Khởi động MCP</Text>
    <ToolTip>Khởi động MCP Server để AI có thể điều khiển Revit</ToolTip>
    <LongDescription>
      Khởi động Model Context Protocol Server trên cổng 3000.
      Sau khi chạy lệnh này, AI Client (Claude, GPT, v.v.) có thể
      kết nối và điều khiển Revit thông qua các công cụ BIM.
    </LongDescription>
    <Image>
      %APPDATA%\Autodesk\Revit\Addins\2024\DeepBIM\Resources\Icons\start_mcp_16.png
    </Image>
    <LargeImage>
      %APPDATA%\Autodesk\Revit\Addins\2024\DeepBIM\Resources\Icons\start_mcp_32.png
    </LargeImage>
    <Discipline>Any</Discipline>
    <NewSessionId>SimpleSingle</NewSessionId>
    <AllowLoadIntoExistingSession>true</AllowLoadIntoExistingSession>
  </AddIn>

  <!--
    ================================================================
    EXTERNAL COMMAND — Dừng MCP Server
    ================================================================
  -->
  <AddIn Type="Command">
    <Name>DeepBIM: Dừng MCP Server</Name>
    <Assembly>
      %APPDATA%\Autodesk\Revit\Addins\2024\DeepBIM\DeepBIM.RevitMCP.Plugin.dll
    </Assembly>
    <FullClassName>
      DeepBIM.RevitMCP.Plugin.Commands.StopMCPServerCommand
    </FullClassName>
    <ClientId>c3d4e5f6-a7b8-9012-cdef-123456789012</ClientId>
    <VendorId>DEEPBIM</VendorId>
    <VendorDescription>Dừng MCP Server</VendorDescription>
    <Text>Dừng MCP</Text>
    <ToolTip>Dừng MCP Server đang chạy</ToolTip>
    <Image>
      %APPDATA%\Autodesk\Revit\Addins\2024\DeepBIM\Resources\Icons\stop_mcp_16.png
    </Image>
    <LargeImage>
      %APPDATA%\Autodesk\Revit\Addins\2024\DeepBIM\Resources\Icons\stop_mcp_32.png
    </LargeImage>
    <Discipline>Any</Discipline>
  </AddIn>

</RevitAddIns>
```

### Template Manifest với Placeholder

Vì đường dẫn `%APPDATA%` khác nhau trên mỗi máy, script cài đặt sẽ thay thế placeholder bằng đường dẫn thực tế. Chúng ta lưu file template với biến `{{INSTALL_PATH}}`:

```xml
<!-- File: DeepBIM.RevitMCP.addin.template -->
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<RevitAddIns>
  <AddIn Type="Application">
    <Name>DeepBIM MCP Application</Name>
    <!-- {{INSTALL_PATH}} được script thay thế khi cài đặt -->
    <Assembly>{{INSTALL_PATH}}\DeepBIM.RevitMCP.Plugin.dll</Assembly>
    <FullClassName>DeepBIM.RevitMCP.Plugin.Application.MCPApplication</FullClassName>
    <ClientId>a1b2c3d4-e5f6-7890-abcd-ef1234567890</ClientId>
    <VendorId>DEEPBIM</VendorId>
    <VendorDescription>DeepBIM — BIM Intelligence Platform</VendorDescription>
  </AddIn>
</RevitAddIns>
```

***

## Cấu trúc thư mục đóng gói

Một gói phân phối tốt cần có cấu trúc rõ ràng, dễ hiểu, và "tự giải thích" — người dùng nhìn vào là biết file nào làm gì.

### Sơ đồ Package Structure

```mermaid
graph TD
    ROOT["DeepBIM-RevitMCP-v1.0.0/\n(Gói phân phối)"]

    ROOT --> INSTALL["Install.ps1\nScript cài đặt tự động"]
    ROOT --> UNINSTALL["Uninstall.ps1\nScript gỡ cài đặt"]
    ROOT --> README_F["README.md\nHướng dẫn người dùng cuối"]
    ROOT --> CHANGELOG_F["CHANGELOG.md\nLịch sử các phiên bản"]
    ROOT --> LICENSE_F["LICENSE\nGiấy phép MIT"]
    ROOT --> TMPL["DeepBIM.RevitMCP.addin.template\nTemplate manifest XML"]

    ROOT --> PLUGIN_D["Plugin/\nAssembly và cấu hình"]
    PLUGIN_D --> DLL_F["DeepBIM.RevitMCP.Plugin.dll"]
    PLUGIN_D --> JSON_F["Newtonsoft.Json.dll\n(nếu chưa có trong GAC)"]
    PLUGIN_D --> CFG_F["plugin.config.json"]

    ROOT --> MCP_D["MCPServer/\nNode.js MCP Server"]
    MCP_D --> PKG_F["package.json"]
    MCP_D --> LOCK_F["package-lock.json"]
    MCP_D --> DIST_D["dist/\nTypeScript đã biên dịch"]

    ROOT --> RES_D["Resources/\nTài nguyên UI"]
    RES_D --> ICONS_D["Icons/\nPNG 16x16 và 32x32"]

    ROOT --> DOCS_D["Docs/\nTài liệu bổ sung"]
    DOCS_D --> QS_F["QuickStart.pdf"]
    DOCS_D --> SS_D["Screenshots/"]

    style ROOT fill:#2C3E50,color:#fff
    style PLUGIN_D fill:#2980B9,color:#fff
    style MCP_D fill:#27AE60,color:#fff
    style RES_D fill:#8E44AD,color:#fff
    style DOCS_D fill:#E67E22,color:#fff
```

### Cây thư mục thực tế

```
DeepBIM-RevitMCP-v1.0.0/
│
├── Install.ps1                           # Script cài đặt chính
├── Uninstall.ps1                         # Script gỡ cài đặt
├── README.md                             # Hướng dẫn cài đặt và sử dụng
├── CHANGELOG.md                          # Lịch sử các phiên bản
├── LICENSE                               # Giấy phép MIT
├── DeepBIM.RevitMCP.addin.template       # Template manifest
│
├── Plugin/
│   ├── DeepBIM.RevitMCP.Plugin.dll       # Assembly chính (~150 KB)
│   ├── Newtonsoft.Json.dll               # JSON library
│   └── plugin.config.json                # Cấu hình mặc định
│
├── MCPServer/
│   ├── package.json                      # npm manifest
│   ├── package-lock.json                 # Khóa phiên bản dependencies
│   └── dist/
│       ├── index.js                      # Entry point đã biên dịch
│       ├── tools/
│       │   ├── getWalls.js
│       │   ├── createWall.js
│       │   ├── getRooms.js
│       │   └── createFloor.js
│       └── utils/
│           └── revitConnection.js
│
├── Resources/
│   └── Icons/
│       ├── start_mcp_16.png              # Icon 16×16 cho ribbon
│       ├── start_mcp_32.png              # Icon 32×32 cho ribbon
│       ├── stop_mcp_16.png
│       └── stop_mcp_32.png
│
└── Docs/
    ├── QuickStart.pdf                    # Hướng dẫn nhanh 1 trang
    └── Screenshots/
        ├── ribbon_panel.png
        ├── mcp_connected.png
        └── ai_controlling_revit.png
```

### File cấu hình `plugin.config.json`

```json
{
  "$schema": "https://deepbim.io/schemas/revit-mcp-config.json",
  "version": "1.0.0",
  "pluginName": "DeepBIM Revit MCP Plugin",
  "mcpServer": {
    "host": "localhost",
    "port": 3000,
    "autoStart": false,
    "logLevel": "info",
    "logPath": "%APPDATA%\\DeepBIM\\Logs"
  },
  "revit": {
    "supportedVersions": ["2022", "2023", "2024", "2025"],
    "transactionMode": "automatic",
    "failureHandling": "rollbackOnError"
  },
  "ui": {
    "showStatusBar": true,
    "ribbonPanelName": "DeepBIM MCP",
    "ribbonTabName": "DeepBIM"
  }
}
```

***

## Script cài đặt tự động với PowerShell

Mục tiêu: người dùng chỉ cần **chuột phải → Run with PowerShell** là plugin được cài đặt hoàn chỉnh, không cần thao tác thủ công nào.

### Script Install.ps1 hoàn chỉnh

```powershell
#Requires -Version 5.1
<#
.SYNOPSIS
    Script cài đặt tự động cho DeepBIM Revit MCP Plugin.

.DESCRIPTION
    Script này thực hiện các bước sau:
    1. Kiểm tra Node.js và PowerShell đủ phiên bản
    2. Phát hiện tất cả phiên bản Revit đang cài trên máy
    3. Sao chép các file plugin vào đúng vị trí
    4. Tạo file .addin manifest với đường dẫn chính xác
    5. Cài đặt Node.js dependencies cho MCP Server

.PARAMETER RevitVersion
    Chỉ định phiên bản Revit cụ thể (ví dụ: 2024).
    Nếu không chỉ định, script sẽ cài đặt cho tất cả phiên bản tìm thấy.

.PARAMETER AllUsers
    Cài đặt cho tất cả người dùng (yêu cầu quyền Administrator).
    Mặc định: cài đặt cho người dùng hiện tại.

.PARAMETER DryRun
    Chỉ mô phỏng quá trình, không thực sự sao chép file.
    Hữu ích để kiểm tra trước khi cài đặt thật sự.

.NOTES
    Tác giả  : DeepBIM Team
    Phiên bản: 1.0.0
    Yêu cầu  : PowerShell 5.1+, Node.js 18+
    Tham khảo: https://github.com/nguyenngocdue/deepbim-revit-mcp-plugin
#>

[CmdletBinding()]
param(
    [Parameter(HelpMessage = "Phiên bản Revit muốn cài (2022/2023/2024/2025)")]
    [ValidateSet("2022", "2023", "2024", "2025")]
    [string]$RevitVersion = "",

    [Parameter(HelpMessage = "Cài cho tất cả người dùng, cần quyền Admin")]
    [switch]$AllUsers = $false,

    [Parameter(HelpMessage = "Mô phỏng, không sao chép file thật sự")]
    [switch]$DryRun = $false
)

# ============================================================================
# CẤU HÌNH TOÀN CỤC
# ============================================================================
$Script:PluginName    = "DeepBIM Revit MCP Plugin"
$Script:PluginVersion = "1.0.0"
$Script:VendorFolder  = "DeepBIM"
$Script:ManifestName  = "DeepBIM.RevitMCP.addin"
$Script:ScriptDir     = Split-Path -Parent $MyInvocation.MyCommand.Path

# ============================================================================
# HÀM TIỆN ÍCH — OUTPUT CÓ MÀU SẮC
# ============================================================================

function Write-Header([string]$Msg) {
    Write-Host ""
    Write-Host ("=" * 65) -ForegroundColor Magenta
    Write-Host "  $Msg" -ForegroundColor Magenta
    Write-Host ("=" * 65) -ForegroundColor Magenta
}

function Write-Step([int]$N, [string]$Msg) {
    Write-Host ""
    Write-Host "[$N] $Msg" -ForegroundColor Cyan
}

function Write-OK([string]$Msg)   { Write-Host "    [OK]      $Msg" -ForegroundColor Green  }
function Write-Warn([string]$Msg) { Write-Host "    [CANH BAO] $Msg" -ForegroundColor Yellow }
function Write-Err([string]$Msg)  { Write-Host "    [LOI]     $Msg" -ForegroundColor Red    }

# ============================================================================
# BƯỚC 1: KIỂM TRA ĐIỀU KIỆN TIÊN QUYẾT
# ============================================================================

function Test-Prerequisites {
    Write-Step 1 "Kiểm tra điều kiện tiên quyết..."
    $ok = $true

    # Kiểm tra phiên bản PowerShell
    if ($PSVersionTable.PSVersion.Major -lt 5) {
        Write-Err "Yêu cầu PowerShell 5.1+. Hiện tại: $($PSVersionTable.PSVersion)"
        $ok = $false
    } else {
        Write-OK "PowerShell $($PSVersionTable.PSVersion) — Đạt yêu cầu"
    }

    # Kiểm tra Node.js
    try {
        $nodeVer = (& node --version 2>&1)
        if ($LASTEXITCODE -eq 0) {
            Write-OK "Node.js $nodeVer — Đã cài đặt"
        } else { throw }
    } catch {
        Write-Warn "Node.js chưa được cài đặt. MCP Server sẽ không hoạt động."
        Write-Warn "Tải Node.js tại: https://nodejs.org (yêu cầu v18 trở lên)"
        $ok = $false
    }

    return $ok
}

# ============================================================================
# BƯỚC 2: PHÁT HIỆN PHIÊN BẢN REVIT ĐÃ CÀI
# ============================================================================

function Find-RevitVersions {
    Write-Step 2 "Tìm kiếm phiên bản Revit đang cài trên máy..."

    $found = @()
    $searchPaths = @(
        "C:\Program Files\Autodesk",
        "C:\Program Files (x86)\Autodesk"
    )

    foreach ($base in $searchPaths) {
        if (-not (Test-Path $base)) { continue }
        Get-ChildItem $base -Directory |
            Where-Object { $_.Name -match "^Revit 20\d\d$" } |
            ForEach-Object {
                $year = $_.Name -replace "^Revit ", ""
                $found += $year
                Write-OK "Phát hiện: Revit $year tại $($_.FullName)"
            }
    }

    if ($found.Count -eq 0) {
        Write-Err "Không tìm thấy Revit nào trên máy này."
        Write-Err "Hãy cài đặt Autodesk Revit trước khi chạy script."
        exit 1
    }

    return $found
}

# ============================================================================
# BƯỚC 3 & 4: SAO CHÉP FILE PLUGIN VÀ TẠO MANIFEST
# ============================================================================

function Install-ForVersion([string]$Version) {
    Write-Step 3 "Cài đặt cho Revit $Version..."

    # Xác định đường dẫn đích
    $installPath = if ($AllUsers) {
        "$env:ProgramData\Autodesk\Revit\Addins\$Version\$($Script:VendorFolder)"
    } else {
        "$env:APPDATA\Autodesk\Revit\Addins\$Version\$($Script:VendorFolder)"
    }

    $manifestDir  = Split-Path $installPath -Parent
    $manifestPath = "$manifestDir\$($Script:ManifestName)"

    Write-OK "Thư mục cài đặt: $installPath"

    if (-not $DryRun) {
        New-Item -ItemType Directory -Path $installPath              -Force | Out-Null
        New-Item -ItemType Directory -Path "$installPath\Resources\Icons" -Force | Out-Null
    }

    # --- Sao chép file Plugin ---
    $pluginSrc = Join-Path $Script:ScriptDir "Plugin"
    $resSrc    = Join-Path $Script:ScriptDir "Resources"

    $copyList = @(
        @{ S = "$pluginSrc\DeepBIM.RevitMCP.Plugin.dll"; D = "$installPath\DeepBIM.RevitMCP.Plugin.dll" },
        @{ S = "$pluginSrc\plugin.config.json";           D = "$installPath\plugin.config.json" },
        @{ S = "$resSrc\Icons\start_mcp_16.png";          D = "$installPath\Resources\Icons\start_mcp_16.png" },
        @{ S = "$resSrc\Icons\start_mcp_32.png";          D = "$installPath\Resources\Icons\start_mcp_32.png" },
        @{ S = "$resSrc\Icons\stop_mcp_16.png";           D = "$installPath\Resources\Icons\stop_mcp_16.png" },
        @{ S = "$resSrc\Icons\stop_mcp_32.png";           D = "$installPath\Resources\Icons\stop_mcp_32.png" }
    )

    # Thêm Newtonsoft nếu tồn tại
    $nj = "$pluginSrc\Newtonsoft.Json.dll"
    if (Test-Path $nj) {
        $copyList += @{ S = $nj; D = "$installPath\Newtonsoft.Json.dll" }
    }

    foreach ($item in $copyList) {
        if (Test-Path $item.S) {
            if (-not $DryRun) { Copy-Item $item.S $item.D -Force }
            Write-OK "Sao chép: $(Split-Path -Leaf $item.S)"
        } else {
            Write-Warn "Bỏ qua (không tìm thấy): $(Split-Path -Leaf $item.S)"
        }
    }

    # --- Tạo file .addin manifest từ template ---
    Write-Step 4 "Tạo .addin manifest cho Revit $Version..."

    $templatePath = Join-Path $Script:ScriptDir "DeepBIM.RevitMCP.addin.template"
    if (-not (Test-Path $templatePath)) {
        Write-Err "Không tìm thấy template: $templatePath"
        return
    }

    $content = (Get-Content $templatePath -Raw) -replace "\{\{INSTALL_PATH\}\}", $installPath

    if (-not $DryRun) {
        New-Item -ItemType Directory -Path $manifestDir -Force | Out-Null
        $content | Set-Content -Path $manifestPath -Encoding UTF8
    }

    Write-OK "Manifest: $manifestPath"
}

# ============================================================================
# BƯỚC 5: CÀI ĐẶT MCP SERVER DEPENDENCIES
# ============================================================================

function Install-MCPDependencies {
    Write-Step 5 "Cài đặt Node.js dependencies cho MCP Server..."

    $mcpDir = Join-Path $Script:ScriptDir "MCPServer"
    if (-not (Test-Path $mcpDir)) {
        Write-Warn "Không tìm thấy thư mục MCPServer — bỏ qua bước này."
        return
    }

    if ($DryRun) {
        Write-OK "[DryRun] Sẽ chạy: npm install --production trong $mcpDir"
        return
    }

    Push-Location $mcpDir
    try {
        Write-Host "    Đang chạy npm install --production..." -ForegroundColor DarkGray
        & npm install --production --prefer-offline 2>&1 |
            ForEach-Object { Write-Host "    $_" -ForegroundColor DarkGray }

        if ($LASTEXITCODE -eq 0) {
            Write-OK "npm install hoàn thành"
        } else {
            Write-Err "npm install thất bại. Kiểm tra kết nối internet và Node.js version."
        }
    } finally {
        Pop-Location
    }
}

# ============================================================================
# CHƯƠNG TRÌNH CHÍNH
# ============================================================================

Write-Header "$($Script:PluginName) v$($Script:PluginVersion) — Trình cài đặt"

if ($DryRun) {
    Write-Warn "CHE DO DRY RUN — Không có file nào được sao chép thực sự."
}

if (-not (Test-Prerequisites)) {
    Write-Host ""
    Read-Host "Nhấn Enter để thoát"
    exit 1
}

$detectedVersions = Find-RevitVersions
$targetVersions   = if ($RevitVersion) { @($RevitVersion) } else { $detectedVersions }

foreach ($v in $targetVersions) {
    Install-ForVersion -Version $v
}

Install-MCPDependencies

# ============================================================================
# KẾT QUẢ VÀ HƯỚNG DẪN TIẾP THEO
# ============================================================================

Write-Header "Cài đặt hoàn tất!"
Write-Host ""
Write-Host "  Các bước tiếp theo:" -ForegroundColor Cyan
Write-Host "  1. Khởi động lại Revit" -ForegroundColor White
Write-Host "  2. Tìm tab 'DeepBIM' trong ribbon" -ForegroundColor White
Write-Host "  3. Click 'Khởi động MCP' để bật server" -ForegroundColor White
Write-Host "  4. Kết nối AI Client — xem README.md để biết thêm chi tiết" -ForegroundColor White
Write-Host ""
Write-Host "  Tài liệu: https://github.com/nguyenngocdue/deepbim-revit-mcp-plugin" -ForegroundColor Cyan
Write-Host ""

if (-not $DryRun) { Read-Host "Nhấn Enter để đóng" }
```

### Script Uninstall.ps1

```powershell
#Requires -Version 5.1
<#
.SYNOPSIS
    Script gỡ cài đặt DeepBIM Revit MCP Plugin.

.PARAMETER AllUsers
    Gỡ cài đặt ở vị trí all-users thay vì per-user.

.PARAMETER KeepConfig
    Giữ lại file cấu hình plugin.config.json, chỉ xóa DLL.
#>

[CmdletBinding()]
param(
    [switch]$AllUsers   = $false,
    [switch]$KeepConfig = $false
)

$VendorFolder = "DeepBIM"
$ManifestName = "DeepBIM.RevitMCP.addin"

Write-Host "Gỡ cài đặt DeepBIM Revit MCP Plugin..." -ForegroundColor Yellow

foreach ($ver in @("2022", "2023", "2024", "2025")) {
    $base = if ($AllUsers) {
        "$env:ProgramData\Autodesk\Revit\Addins\$ver"
    } else {
        "$env:APPDATA\Autodesk\Revit\Addins\$ver"
    }

    # Xóa file manifest
    $manifest = "$base\$ManifestName"
    if (Test-Path $manifest) {
        Remove-Item $manifest -Force
        Write-Host "  [OK] Đã xóa: $manifest" -ForegroundColor Green
    }

    # Xóa thư mục plugin
    $pluginDir = "$base\$VendorFolder"
    if (Test-Path $pluginDir) {
        if ($KeepConfig) {
            Get-ChildItem "$pluginDir" -Filter "*.dll" | Remove-Item -Force
            Write-Host "  [OK] Đã xóa DLL, giữ lại cấu hình: $pluginDir" -ForegroundColor Green
        } else {
            Remove-Item $pluginDir -Recurse -Force
            Write-Host "  [OK] Đã xóa thư mục: $pluginDir" -ForegroundColor Green
        }
    }
}

Write-Host ""
Write-Host "Gỡ cài đặt hoàn tất. Vui lòng khởi động lại Revit." -ForegroundColor Green
Read-Host "Nhấn Enter để đóng"
```

***

## Tạo README hướng dẫn sử dụng

File README trong gói phân phối phải được viết cho **người dùng cuối**, không phải developer. Ngôn ngữ phải đơn giản, rõ ràng, và không giả định người đọc biết lập trình.

### Cấu trúc README.md mẫu

```markdown
# DeepBIM Revit MCP Plugin v1.0.0

Kết nối Revit với AI (Claude, GPT, v.v.) thông qua Model Context Protocol.

## Yêu cầu hệ thống

| Thành phần     | Phiên bản tối thiểu          |
|----------------|------------------------------|
| Autodesk Revit | 2022, 2023, 2024, hoặc 2025  |
| Windows        | 10 / 11 (64-bit)             |
| Node.js        | 18.0 trở lên                 |
| .NET Framework | 4.8 trở lên                  |
| RAM            | 8 GB (khuyến nghị 16 GB)     |

## Cài đặt nhanh (3 bước)

**Bước 1:** Tải file ZIP mới nhất từ
[GitHub Releases](https://github.com/nguyenngocdue/deepbim-revit-mcp-plugin/releases)

**Bước 2:** Giải nén và chuột phải vào `Install.ps1`
→ chọn **"Run with PowerShell"**

**Bước 3:** Khởi động lại Revit
→ Tìm tab **"DeepBIM"** trong ribbon

## Sử dụng

1. Mở Revit và tải một dự án bất kỳ
2. Vào tab **DeepBIM** → Click **"Khởi động MCP"**
3. Thanh trạng thái hiển thị: `MCP Server: Đang chạy — cổng 3000`
4. Mở AI Client (Claude Desktop, Cursor...) và ra lệnh bằng tiếng tự nhiên

### Ví dụ lệnh có thể dùng với AI

```

"Liệt kê tất cả tường trong tầng 1" "Tạo tường dài 5 mét từ điểm (0, 0, 0) theo trục X" "Cho tôi biết diện tích sàn của tất cả các phòng" "Tạo sàn bê tông dày 200mm tại cao độ 3.0m"

```

## Gỡ cài đặt

Chuột phải vào `Uninstall.ps1` → **"Run with PowerShell"**

## Xử lý sự cố thường gặp

| Triệu chứng | Nguyên nhân có thể | Giải pháp |
|---|---|---|
| Tab DeepBIM không xuất hiện | File .addin sai đường dẫn | Chạy lại `Install.ps1` |
| "MCP Server không khởi động được" | Node.js chưa cài | Cài Node.js 18+ từ nodejs.org |
| Plugin bị Revit cảnh báo bảo mật | Windows chặn file từ internet | Chuột phải DLL → Properties → Unblock |

## Hỗ trợ

- Báo lỗi và đóng góp: https://github.com/nguyenngocdue/deepbim-revit-mcp-plugin/issues
- Tài liệu kỹ thuật đầy đủ: Xem thư mục `Docs/`
```

***

## Versioning và cập nhật

### Nguyên tắc Semantic Versioning (SemVer)

Dự án sử dụng định dạng `MAJOR.MINOR.PATCH`:

```
v1.2.3
│ │ └── PATCH : Sửa lỗi, không thay đổi API
│ └──── MINOR : Thêm tính năng mới, tương thích ngược
└────── MAJOR : Thay đổi lớn, có thể phá vỡ tương thích cũ
```

**Ví dụ áp dụng thực tế:**

| Phiên bản | Lý do tăng version                                           |
| --------- | ------------------------------------------------------------ |
| `v1.0.0`  | Phát hành chính thức lần đầu                                 |
| `v1.0.1`  | Sửa lỗi crash khi mở Revit 2022 với file RVT cũ              |
| `v1.1.0`  | Thêm tool `get_rooms` và `create_floor` mới                  |
| `v2.0.0`  | Đổi giao tiếp từ TCP socket sang WebSocket (breaking change) |

### File CHANGELOG.md

```markdown
# Lịch sử thay đổi

Tất cả các thay đổi đáng chú ý đều được ghi lại tại đây.
Định dạng theo [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

---

## [Chưa phát hành]

### Dự kiến thêm
- Tool `modify_element_parameter` để chỉnh sửa thuộc tính phần tử
- Hỗ trợ Revit 2026

---

## [1.0.0] — 2024-11-15

### Thêm mới
- Tool `get_walls` : Lấy danh sách tường trong model
- Tool `create_wall` : Tạo tường mới theo tọa độ XYZ
- Tool `get_rooms` : Lấy danh sách phòng và diện tích
- Tool `create_floor` : Tạo sàn từ danh sách điểm boundary
- MCP Server trên Node.js/TypeScript lắng nghe cổng 3000
- Revit Plugin với ribbon tab "DeepBIM"
- Script cài đặt PowerShell tự động phát hiện phiên bản Revit
- Hỗ trợ Revit 2022, 2023, 2024, 2025

### Đã sửa
- N/A (phát hành đầu tiên)

---

## [0.9.0-beta] — 2024-10-20

### Thêm mới
- Phiên bản beta nội bộ
- Tool `get_walls` và `create_wall` cơ bản

### Đã biết
- Chưa ổn định khi Revit mở nhiều document cùng lúc
- Chưa có script cài đặt tự động
```

### Tự động hóa Versioning trong MSBuild

```xml
<!-- Trong file .csproj — tự động tính build number theo ngày -->
<PropertyGroup>
  <VersionBase>1.0.0</VersionBase>
  <!--
    BuildNumber = số ngày kể từ 2024-01-01.
    Mỗi ngày build sẽ ra số khác nhau, dễ truy vết.
  -->
  <BuildNumber>
    $([System.DateTime]::Now
      .Subtract($([System.DateTime]::Parse('2024-01-01'))).Days)
  </BuildNumber>
  <AssemblyVersion>$(VersionBase).$(BuildNumber)</AssemblyVersion>
  <FileVersion>$(VersionBase).$(BuildNumber)</FileVersion>
  <InformationalVersion>$(VersionBase)-build.$(BuildNumber)</InformationalVersion>
</PropertyGroup>
```

***

## Phân phối qua GitHub Releases

GitHub Releases là nền tảng phân phối hoàn toàn miễn phí, tích hợp chặt chẽ với Git workflow và được cộng đồng developer quen thuộc.

### Sơ đồ luồng tạo Release

```mermaid
gitGraph
   commit id: "feat: them get_rooms tool"
   commit id: "feat: them create_floor tool"
   commit id: "fix: sua loi encoding UTF-8"
   commit id: "docs: cap nhat README"
   commit id: "chore: release v1.0.0" tag: "v1.0.0"
   branch hotfix/revit2022
   checkout hotfix/revit2022
   commit id: "fix: crash khi mo Revit 2022"
   checkout main
   merge hotfix/revit2022 id: "Merge hotfix" tag: "v1.0.1"
   commit id: "feat: them modify_parameter tool"
   commit id: "chore: release v1.1.0" tag: "v1.1.0"
```

### Bước 1 — Script cập nhật version

```powershell
# scripts/update-version.ps1
param([Parameter(Mandatory)][string]$NewVersion)

# Cập nhật AssemblyInfo.cs
$path    = ".\src\Properties\AssemblyInfo.cs"
$content = Get-Content $path -Raw
$content = $content -replace 'AssemblyVersion\("[\d\.]+"\)',
                              "AssemblyVersion(`"$NewVersion.0`")"
$content = $content -replace 'AssemblyFileVersion\("[\d\.]+"\)',
                              "AssemblyFileVersion(`"$NewVersion.0`")"
$content = $content -replace 'AssemblyInformationalVersion\("[\d\.\-\w]+"\)',
                              "AssemblyInformationalVersion(`"$NewVersion`")"
Set-Content $path $content -Encoding UTF8

# Cập nhật package.json của MCP Server
$pkgPath = ".\MCPServer\package.json"
$pkg     = Get-Content $pkgPath -Raw | ConvertFrom-Json
$pkg.version = $NewVersion
$pkg | ConvertTo-Json -Depth 10 | Set-Content $pkgPath -Encoding UTF8

Write-Host "Da cap nhat version len v$NewVersion" -ForegroundColor Green
```

### Bước 2 — Script build và đóng gói

```powershell
# scripts/build-and-package.ps1
param([Parameter(Mandatory)][string]$Version)

$OutputDir = ".\releases\DeepBIM-RevitMCP-v$Version"
$ZipPath   = ".\releases\DeepBIM-RevitMCP-v$Version.zip"

Write-Host "Build Release v$Version..." -ForegroundColor Cyan

# Build C# assembly
& msbuild .\src\DeepBIM.RevitMCP.sln `
    /p:Configuration=Release `
    /p:Platform="Any CPU" `
    /p:OutputPath="..\$OutputDir\Plugin\" `
    /t:Clean,Build /v:minimal

if ($LASTEXITCODE -ne 0) { Write-Error "Build that bai!"; exit 1 }

# Build TypeScript MCP Server
Push-Location .\MCPServer
& npm run build
if ($LASTEXITCODE -ne 0) { Write-Error "npm build that bai!"; exit 1 }
Pop-Location

# Tổ chức cấu trúc gói
$items = @(
    @{ S = ".\MCPServer\dist";                 D = "$OutputDir\MCPServer\dist" },
    @{ S = ".\MCPServer\package.json";         D = "$OutputDir\MCPServer\package.json" },
    @{ S = ".\MCPServer\package-lock.json";    D = "$OutputDir\MCPServer\package-lock.json" },
    @{ S = ".\Resources";                      D = "$OutputDir\Resources" },
    @{ S = ".\Install.ps1";                    D = "$OutputDir\Install.ps1" },
    @{ S = ".\Uninstall.ps1";                  D = "$OutputDir\Uninstall.ps1" },
    @{ S = ".\README.md";                      D = "$OutputDir\README.md" },
    @{ S = ".\CHANGELOG.md";                   D = "$OutputDir\CHANGELOG.md" },
    @{ S = ".\LICENSE";                        D = "$OutputDir\LICENSE" },
    @{ S = ".\DeepBIM.RevitMCP.addin.template"; D = "$OutputDir\DeepBIM.RevitMCP.addin.template" }
)

foreach ($i in $items) {
    if (Test-Path $i.S) {
        $dest = Split-Path $i.D -Parent
        New-Item -ItemType Directory -Path $dest -Force | Out-Null
        Copy-Item $i.S $i.D -Recurse -Force
    }
}

# Nén thành ZIP
if (Test-Path $ZipPath) { Remove-Item $ZipPath -Force }
Compress-Archive -Path "$OutputDir\*" -DestinationPath $ZipPath -Force

$sizeMB = [math]::Round((Get-Item $ZipPath).Length / 1MB, 2)
Write-Host "Hoan thanh: $ZipPath ($sizeMB MB)" -ForegroundColor Green
```

### Bước 3 — Tạo Git tag và đẩy lên GitHub

```bash
# Commit tất cả thay đổi liên quan đến release
git add src/Properties/AssemblyInfo.cs MCPServer/package.json CHANGELOG.md
git commit -m "chore: release v1.0.0

- Cập nhật AssemblyInfo.cs lên 1.0.0.0
- Cập nhật package.json lên 1.0.0
- Cập nhật CHANGELOG.md với danh sách tính năng"

# Tạo annotated tag (quan trọng: -a tạo object tag đầy đủ)
git tag -a v1.0.0 -m "Phát hành DeepBIM Revit MCP Plugin v1.0.0

Tính năng:
- Kết nối Revit với AI qua MCP Protocol
- 4 công cụ BIM: get_walls, create_wall, get_rooms, create_floor
- Script cài đặt PowerShell tự động
- Hỗ trợ Revit 2022 đến 2025"

# Đẩy code và tag lên GitHub
git push origin main
git push origin v1.0.0
```

### Bước 4 — Tạo GitHub Release bằng gh CLI

```bash
# gh CLI: https://cli.github.com — cài đặt và đăng nhập trước

gh release create v1.0.0 \
  ./releases/DeepBIM-RevitMCP-v1.0.0.zip \
  --title "DeepBIM Revit MCP Plugin v1.0.0" \
  --notes-file RELEASE_NOTES.md \
  --latest
```

### Tự động hóa toàn bộ với GitHub Actions

```yaml
# .github/workflows/release.yml
name: Build và Tạo GitHub Release

on:
  push:
    tags:
      - 'v*.*.*'    # Kích hoạt khi push tag như v1.0.0, v1.1.0, ...

jobs:
  build-and-release:
    runs-on: windows-latest

    steps:
      - name: Checkout source code
        uses: actions/checkout@v4

      - name: Thiết lập Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
          cache-dependency-path: MCPServer/package-lock.json

      - name: Lấy version number từ Git tag
        id: version
        run: |
          $ver = "${{ github.ref_name }}" -replace "^v", ""
          echo "VALUE=$ver" >> $env:GITHUB_OUTPUT
        shell: powershell

      - name: Build C# Plugin (Release config)
        run: |
          msbuild src/DeepBIM.RevitMCP.sln `
            /p:Configuration=Release `
            /p:Platform="Any CPU" `
            /t:Clean,Build `
            /v:minimal
        shell: powershell

      - name: Build TypeScript MCP Server
        run: |
          cd MCPServer
          npm ci
          npm run build
        shell: bash

      - name: Đóng gói thành ZIP
        run: |
          .\scripts\build-and-package.ps1 `
            -Version "${{ steps.version.outputs.VALUE }}"
        shell: powershell

      - name: Tạo GitHub Release và upload ZIP
        uses: softprops/action-gh-release@v2
        with:
          files: |
            releases/DeepBIM-RevitMCP-v${{ steps.version.outputs.VALUE }}.zip
          generate_release_notes: true
          make_latest: true
          prerelease: >-
            ${{ contains(github.ref_name, '-beta') ||
                contains(github.ref_name, '-alpha') ||
                contains(github.ref_name, '-rc') }}
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```

***

## Câu hỏi tự suy nghĩ

<details>

<summary><strong>Câu 1:</strong> Tại sao không nên đóng gói <code>RevitAPI.dll</code> và <code>RevitAPIUI.dll</code> cùng với plugin?</summary>

**Trả lời:**

Có ba lý do chính:

**1. Xung đột phiên bản:** `RevitAPI.dll` phiên bản 2024 khác hoàn toàn với phiên bản 2025. Nếu bạn đóng gói file của Revit 2024, plugin sẽ không tải được trong Revit 2025 vì .NET runtime sẽ phát hiện sai version và từ chối tải.

**2. Lãng phí dung lượng không cần thiết:** Mỗi `RevitAPI.dll` nặng khoảng 15–20 MB. File này đã có sẵn trong thư mục cài đặt Revit trên mọi máy của người dùng — việc đóng gói thêm chỉ làm tăng kích thước gói tải xuống mà không mang lại giá trị gì.

**3. Vi phạm điều khoản sử dụng của Autodesk:** Autodesk cấm phân phối `RevitAPI.dll` trong các gói thứ ba. Plugin phải tham chiếu đến file DLL trong thư mục cài đặt Revit.

**Cách thiết lập đúng trong `.csproj`:**

```xml
<Reference Include="RevitAPI">
  <HintPath>C:\Program Files\Autodesk\Revit 2024\RevitAPI.dll</HintPath>
  <!-- Private = false nghĩa là Copy Local = false -->
  <Private>False</Private>
</Reference>
```

</details>

<details>

<summary><strong>Câu 2:</strong> Sự khác biệt giữa cài đặt per-user (<code>%APPDATA%</code>) và all-users (<code>%ProgramData%</code>) là gì? Khi nào nên dùng cái nào?</summary>

**Trả lời:**

| Tiêu chí            | Per-User (`%APPDATA%`)               | All-Users (`%ProgramData%`)                  |
| ------------------- | ------------------------------------ | -------------------------------------------- |
| Quyền cài đặt       | Không cần Administrator              | Bắt buộc cần Administrator                   |
| Phạm vi             | Chỉ tài khoản Windows đang dùng      | Tất cả người dùng trên máy tính đó           |
| Dễ gỡ cài đặt       | Rất dễ, không cần Admin              | Cần Admin để gỡ                              |
| Phù hợp             | Cá nhân, developer, open-source      | IT Admin, doanh nghiệp, triển khai tập trung |
| Ảnh hưởng đến Revit | Chỉ khi đăng nhập bằng tài khoản này | Mọi user đều thấy plugin trong ribbon        |

**Khuyến nghị theo tình huống:**

* **Phân phối mở / cộng đồng:** Dùng per-user — giảm ma sát khi cài đặt, không cần hỏi xin quyền Admin.
* **Triển khai doanh nghiệp (MSI package, SCCM, Group Policy):** Dùng all-users để quản lý tập trung trên toàn bộ máy tính trong tổ chức.

</details>

<details>

<summary><strong>Câu 3:</strong> Tại sao cần dùng annotated tag (<code>git tag -a</code>) thay vì lightweight tag (<code>git tag</code>) khi tạo release?</summary>

**Trả lời:**

**Lightweight tag** chỉ là một con trỏ trực tiếp đến commit hash, không có thông tin gì thêm:

```bash
git tag v1.0.0          # Lightweight — alias đơn giản của commit
```

**Annotated tag** là một Git object độc lập, có đầy đủ metadata:

* Tên người tạo tag (tagger name & email)
* Ngày giờ tạo tag (có thể khác ngày commit)
* Message mô tả nội dung release
* Có thể được ký số bằng GPG để xác thực

```bash
git tag -a v1.0.0 -m "Release v1.0.0"   # Annotated — object đầy đủ
```

**Tại sao quan trọng khi dùng GitHub:**

* GitHub chỉ hiển thị annotated tags dưới dạng "Releases" trong giao diện chính
* `gh release create` và GitHub Actions hoạt động đáng tin cậy hơn với annotated tags
* Lệnh `git describe` — dùng để tạo tên version tự động — chỉ nhận dạng annotated tags theo mặc định

**Kết luận:** Luôn dùng annotated tags (`git tag -a`) cho releases chính thức.

</details>

<details>

<summary><strong>Câu 4:</strong> Làm thế nào để plugin tự kiểm tra và thông báo cho người dùng khi có phiên bản mới?</summary>

**Trả lời:**

Bạn có thể thêm tính năng "kiểm tra cập nhật" trong `IExternalApplication.OnStartup()`, chạy trong background để không làm chậm khởi động Revit:

```csharp
// Trong MCPApplication.cs
public Result OnStartup(UIControlledApplication application)
{
    // Đăng ký ribbon, commands...
    RegisterRibbon(application);

    // Kiểm tra cập nhật trong background — không block UI thread
    Task.Run(async () =>
    {
        await Task.Delay(TimeSpan.FromSeconds(5)); // Chờ Revit ổn định
        try { await CheckForUpdatesAsync(); }
        catch { /* Bỏ qua nếu không có mạng hoặc GitHub không phản hồi */ }
    });

    return Result.Succeeded;
}

private static async Task CheckForUpdatesAsync()
{
    const string ApiUrl =
        "https://api.github.com/repos/nguyenngocdue/" +
        "deepbim-revit-mcp-plugin/releases/latest";

    using var http = new HttpClient();
    // GitHub API yêu cầu User-Agent header
    http.DefaultRequestHeaders.Add("User-Agent", "DeepBIM-RevitMCP");

    var json    = await http.GetStringAsync(ApiUrl);
    var release = JsonConvert.DeserializeObject<dynamic>(json);

    string latest  = ((string)release.tag_name).TrimStart('v');
    string current = Assembly.GetExecutingAssembly()
                             .GetName().Version.ToString(3);

    if (new Version(latest) > new Version(current))
    {
        string url = (string)release.html_url;
        // Hiển thị thông báo — cần chạy trên UI thread
        Application.Current.Dispatcher.Invoke(() =>
        {
            MessageBox.Show(
                $"Có phiên bản mới: v{latest}\n" +
                $"Phiên bản hiện tại: v{current}\n\n" +
                $"Tải xuống tại:\n{url}",
                "DeepBIM MCP — Cập nhật mới",
                MessageBoxButton.OK,
                MessageBoxImage.Information
            );
        });
    }
}
```

Lưu ý quan trọng: Luôn chạy trong background thread và xử lý exception để không làm crash Revit nếu mạng bị lỗi hoặc GitHub tạm thời không khả dụng.

</details>

<details>

<summary><strong>Câu 5:</strong> Nên đặt thư mục <code>node_modules</code> vào trong gói phân phối không? Tại sao?</summary>

**Trả lời:**

**Không bao giờ nên đóng gói `node_modules`.** Đây là quy tắc bất di bất dịch trong phân phối Node.js:

**Lý do KHÔNG đóng gói:**

1. **Kích thước khổng lồ:** Một dự án Node.js đơn giản có thể có `node_modules` lên đến 200–500 MB, trong khi code thực sự chỉ vài chục KB.
2. **Không tương thích đa nền tảng:** Một số package (như `fsevents`, `node-gyp`) biên dịch file binary riêng cho từng hệ điều hành và kiến trúc CPU. File `.node` trên Windows amd64 sẽ không chạy được trên máy khác.
3. **Lỗ hổng bảo mật:** `node_modules` đã đóng gói sẽ không được cập nhật khi có security patch, trong khi `npm install` luôn kéo về phiên bản mới nhất phù hợp với constraints trong `package.json`.

**Cách đúng — để npm cài đặt tại máy người dùng:**

```powershell
# Trong Install.ps1 — sau khi sao chép MCPServer/
Push-Location "$installPath\MCPServer"
# --prefer-offline: dùng npm cache nếu có, tăng tốc đáng kể
& npm install --production --prefer-offline
Pop-Location
```

Tệp `package-lock.json` đảm bảo `npm install` cài **chính xác** cùng một phiên bản dependency trên mọi máy, đảm bảo tính tái lập (reproducibility).

</details>

***

## Tổng kết

Trong bài học này, chúng ta đã đi qua toàn bộ quy trình đóng gói và phân phối một Revit plugin chuyên nghiệp:

```mermaid
mindmap
  root((Đóng gói\nPlugin))
    Build Release
      Cấu hình Release trong VS
      Tối ưu hóa assembly
      Xác minh output DLL
    Manifest .addin
      Khai báo plugin với Revit
      Per-user vs All-users
      Template với placeholder
    Cấu trúc thư mục
      Plugin DLL
      MCP Server dist
      Resources và Icons
      Scripts và Docs
    PowerShell Script
      Phát hiện phiên bản Revit
      Sao chép file đúng vị trí
      Cài npm dependencies
      Chế độ Dry-run
    Versioning
      Semantic Versioning SemVer
      Cập nhật AssemblyInfo
      Viết CHANGELOG
      Annotated Git tags
    GitHub Releases
      Build và đóng gói ZIP
      Tạo annotated tag
      gh CLI release create
      GitHub Actions CI/CD
```

### Những điểm quan trọng cần ghi nhớ

| Hạng mục      | Quy tắc                                                        |
| ------------- | -------------------------------------------------------------- |
| Build         | Luôn dùng Release config khi phân phối — không dùng Debug      |
| RevitAPI.dll  | Không bao giờ đóng gói — vi phạm điều khoản của Autodesk       |
| Cài đặt       | Per-user (`%APPDATA%`) dễ hơn cho open-source, không cần Admin |
| node\_modules | Không đóng gói — để `npm install` cài sau khi giải nén         |
| Versioning    | Dùng SemVer, annotated tags, cập nhật CHANGELOG mỗi release    |
| CI/CD         | GitHub Actions giúp tự động hóa toàn bộ từ git tag đến Release |

***

## Điều hướng

***

[← Bài 8: Mở rộng — Viết thêm Tool](/revit-mcp-ai/phan-4-nang-cao-and-hoan-thien/bai-8.md)    |    [Bài 10: Tổng kết khóa học →](/revit-mcp-ai/phan-4-nang-cao-and-hoan-thien/bai-10.md)

***

*Thuộc khóa học **Revit API x MCP x AI — Từ Zero đến Plugin hoàn chỉnh*** *Tham khảo dự án thực tế:* [*deepbim-revit-mcp-plugin*](https://github.com/nguyenngocdue/deepbim-revit-mcp-plugin) *Tham chiếu phần Tool/MCP Server:* [*revit-mcp-server*](https://github.com/nguyenngocdue/revit-mcp-server)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://deepbim.gitbook.io/revit-mcp-ai/phan-4-nang-cao-and-hoan-thien/bai-9.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
