mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-20 23:54:26 +08:00
feat.airapp sdk
This commit is contained in:
230
LanMountainDesktop.AirAppDevServer/AirAppDevServer.cs
Normal file
230
LanMountainDesktop.AirAppDevServer/AirAppDevServer.cs
Normal file
@@ -0,0 +1,230 @@
|
||||
using System.Diagnostics;
|
||||
using Microsoft.Build.Locator;
|
||||
using Microsoft.Build.Execution;
|
||||
|
||||
namespace LanMountainDesktop.AirAppDevServer;
|
||||
|
||||
/// <summary>
|
||||
/// AirApp 开发服务器
|
||||
/// 提供文件监视、自动编译、热重载功能
|
||||
/// </summary>
|
||||
public sealed class AirAppDevServer
|
||||
{
|
||||
private readonly string _projectPath;
|
||||
private readonly int _port;
|
||||
private readonly bool _verbose;
|
||||
private FileSystemWatcher? _watcher;
|
||||
private DateTime _lastBuildTime = DateTime.MinValue;
|
||||
private readonly object _buildLock = new();
|
||||
private bool _isBuilding;
|
||||
|
||||
public AirAppDevServer(string projectPath, int port, bool verbose)
|
||||
{
|
||||
_projectPath = Path.GetFullPath(projectPath);
|
||||
_port = port;
|
||||
_verbose = verbose;
|
||||
}
|
||||
|
||||
public Task StartAsync()
|
||||
{
|
||||
// 初始构建
|
||||
Console.WriteLine("🔨 初始构建中...");
|
||||
if (!BuildProject())
|
||||
{
|
||||
Console.WriteLine("❌ 初始构建失败");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
Console.WriteLine("✅ 初始构建成功");
|
||||
Console.WriteLine();
|
||||
|
||||
// 启动文件监视
|
||||
StartFileWatcher();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task StopAsync()
|
||||
{
|
||||
_watcher?.Dispose();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void StartFileWatcher()
|
||||
{
|
||||
_watcher = new FileSystemWatcher(_projectPath)
|
||||
{
|
||||
NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName,
|
||||
Filter = "*.*",
|
||||
IncludeSubdirectories = true,
|
||||
EnableRaisingEvents = true
|
||||
};
|
||||
|
||||
_watcher.Changed += OnFileChanged;
|
||||
_watcher.Created += OnFileChanged;
|
||||
_watcher.Deleted += OnFileChanged;
|
||||
_watcher.Renamed += OnFileRenamed;
|
||||
|
||||
Console.WriteLine("👁️ 文件监视已启动,等待更改...");
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
private void OnFileChanged(object sender, FileSystemEventArgs e)
|
||||
{
|
||||
// 忽略 bin、obj、.vs 等目录
|
||||
if (e.FullPath.Contains("\\bin\\") ||
|
||||
e.FullPath.Contains("\\obj\\") ||
|
||||
e.FullPath.Contains("\\.vs\\") ||
|
||||
e.FullPath.Contains("\\.git\\"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 只处理源代码文件
|
||||
var ext = Path.GetExtension(e.FullPath).ToLowerInvariant();
|
||||
if (ext != ".cs" && ext != ".axaml" && ext != ".json" && ext != ".csproj")
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 防止重复触发(文件保存时可能触发多次)
|
||||
var now = DateTime.Now;
|
||||
if ((now - _lastBuildTime).TotalMilliseconds < 500)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LogVerbose($"📝 检测到文件更改: {Path.GetFileName(e.FullPath)}");
|
||||
TriggerRebuild();
|
||||
}
|
||||
|
||||
private void OnFileRenamed(object sender, RenamedEventArgs e)
|
||||
{
|
||||
LogVerbose($"📝 检测到文件重命名: {Path.GetFileName(e.OldFullPath)} -> {Path.GetFileName(e.FullPath)}");
|
||||
TriggerRebuild();
|
||||
}
|
||||
|
||||
private void TriggerRebuild()
|
||||
{
|
||||
lock (_buildLock)
|
||||
{
|
||||
if (_isBuilding)
|
||||
{
|
||||
LogVerbose("⏳ 构建进行中,跳过此次触发");
|
||||
return;
|
||||
}
|
||||
|
||||
_isBuilding = true;
|
||||
}
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// 短暂延迟,让文件写入完成
|
||||
Thread.Sleep(300);
|
||||
|
||||
Console.WriteLine("🔄 重新构建中...");
|
||||
var success = BuildProject();
|
||||
|
||||
_lastBuildTime = DateTime.Now;
|
||||
|
||||
if (success)
|
||||
{
|
||||
Console.WriteLine($"✅ 重新构建成功 [{DateTime.Now:HH:mm:ss}]");
|
||||
Console.WriteLine("♻️ 热重载已生效");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"❌ 重新构建失败 [{DateTime.Now:HH:mm:ss}]");
|
||||
}
|
||||
Console.WriteLine();
|
||||
}
|
||||
finally
|
||||
{
|
||||
lock (_buildLock)
|
||||
{
|
||||
_isBuilding = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private bool BuildProject()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 查找项目文件
|
||||
var projectFile = FindProjectFile();
|
||||
if (projectFile == null)
|
||||
{
|
||||
Console.WriteLine("❌ 未找到项目文件 (.csproj)");
|
||||
return false;
|
||||
}
|
||||
|
||||
LogVerbose($"📄 项目文件: {Path.GetFileName(projectFile)}");
|
||||
|
||||
// 使用 dotnet build
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "dotnet",
|
||||
Arguments = $"build \"{projectFile}\" -c Debug --nologo",
|
||||
WorkingDirectory = Path.GetDirectoryName(projectFile),
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
using var process = Process.Start(startInfo);
|
||||
if (process == null)
|
||||
{
|
||||
Console.WriteLine("❌ 无法启动 dotnet build");
|
||||
return false;
|
||||
}
|
||||
|
||||
var output = process.StandardOutput.ReadToEnd();
|
||||
var error = process.StandardError.ReadToEnd();
|
||||
process.WaitForExit();
|
||||
|
||||
if (_verbose)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(output))
|
||||
{
|
||||
Console.WriteLine(output);
|
||||
}
|
||||
}
|
||||
|
||||
if (process.ExitCode != 0)
|
||||
{
|
||||
Console.WriteLine("❌ 构建错误:");
|
||||
Console.WriteLine(error);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"❌ 构建异常: {ex.Message}");
|
||||
if (_verbose)
|
||||
{
|
||||
Console.WriteLine(ex.StackTrace);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private string? FindProjectFile()
|
||||
{
|
||||
var files = Directory.GetFiles(_projectPath, "*.csproj", SearchOption.TopDirectoryOnly);
|
||||
return files.Length > 0 ? files[0] : null;
|
||||
}
|
||||
|
||||
private void LogVerbose(string message)
|
||||
{
|
||||
if (_verbose)
|
||||
{
|
||||
Console.WriteLine($"[VERBOSE] {message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
119
LanMountainDesktop.AirAppDevServer/AirAppPackager.cs
Normal file
119
LanMountainDesktop.AirAppDevServer/AirAppPackager.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
using System.Diagnostics;
|
||||
using System.IO.Compression;
|
||||
|
||||
namespace LanMountainDesktop.AirAppDevServer;
|
||||
|
||||
/// <summary>
|
||||
/// AirApp 打包工具
|
||||
/// 将 AirApp 项目打包为 .laapp 文件
|
||||
/// </summary>
|
||||
public sealed class AirAppPackager
|
||||
{
|
||||
private readonly string _projectPath;
|
||||
|
||||
public AirAppPackager(string projectPath)
|
||||
{
|
||||
_projectPath = Path.GetFullPath(projectPath);
|
||||
}
|
||||
|
||||
public async Task<string> PackageAsync(string? outputPath)
|
||||
{
|
||||
Console.WriteLine("🔨 构建项目...");
|
||||
if (!await BuildProjectAsync())
|
||||
{
|
||||
throw new InvalidOperationException("构建失败");
|
||||
}
|
||||
|
||||
var binPath = Path.Combine(_projectPath, "bin", "Release", "net10.0");
|
||||
if (!Directory.Exists(binPath))
|
||||
{
|
||||
binPath = Path.Combine(_projectPath, "bin", "Debug", "net10.0");
|
||||
if (!Directory.Exists(binPath))
|
||||
{
|
||||
throw new InvalidOperationException("未找到构建输出");
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine($"📁 输出目录: {binPath}");
|
||||
|
||||
// 确定输出文件名
|
||||
var projectName = Path.GetFileNameWithoutExtension(
|
||||
Directory.GetFiles(_projectPath, "*.csproj").FirstOrDefault() ?? "AirApp");
|
||||
|
||||
if (string.IsNullOrEmpty(outputPath))
|
||||
{
|
||||
outputPath = Path.Combine(binPath, $"{projectName}.laapp");
|
||||
}
|
||||
else
|
||||
{
|
||||
outputPath = Path.GetFullPath(outputPath);
|
||||
if (Directory.Exists(outputPath))
|
||||
{
|
||||
outputPath = Path.Combine(outputPath, $"{projectName}.laapp");
|
||||
}
|
||||
}
|
||||
|
||||
// 删除旧的包
|
||||
if (File.Exists(outputPath))
|
||||
{
|
||||
File.Delete(outputPath);
|
||||
}
|
||||
|
||||
Console.WriteLine($"📦 打包到: {outputPath}");
|
||||
|
||||
// 创建 ZIP 包
|
||||
using (var archive = ZipFile.Open(outputPath, ZipArchiveMode.Create))
|
||||
{
|
||||
var filesToPackage = Directory.GetFiles(binPath, "*.*", SearchOption.AllDirectories)
|
||||
.Where(f => !f.Contains(".pdb") && !f.EndsWith(".laapp"))
|
||||
.ToList();
|
||||
|
||||
Console.WriteLine($"📄 打包 {filesToPackage.Count} 个文件...");
|
||||
|
||||
foreach (var file in filesToPackage)
|
||||
{
|
||||
var relativePath = Path.GetRelativePath(binPath, file);
|
||||
archive.CreateEntryFromFile(file, relativePath);
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine($"✅ 包大小: {new FileInfo(outputPath).Length / 1024} KB");
|
||||
|
||||
return outputPath;
|
||||
}
|
||||
|
||||
private async Task<bool> BuildProjectAsync()
|
||||
{
|
||||
var projectFile = Directory.GetFiles(_projectPath, "*.csproj").FirstOrDefault();
|
||||
if (projectFile == null)
|
||||
{
|
||||
Console.WriteLine("❌ 未找到项目文件");
|
||||
return false;
|
||||
}
|
||||
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "dotnet",
|
||||
Arguments = $"build \"{projectFile}\" -c Release --nologo",
|
||||
WorkingDirectory = _projectPath,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
using var process = Process.Start(startInfo);
|
||||
if (process == null) return false;
|
||||
|
||||
await process.WaitForExitAsync();
|
||||
|
||||
if (process.ExitCode != 0)
|
||||
{
|
||||
var error = await process.StandardError.ReadToEndAsync();
|
||||
Console.WriteLine($"❌ 构建错误:\n{error}");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
129
LanMountainDesktop.AirAppDevServer/AirAppPreviewer.cs
Normal file
129
LanMountainDesktop.AirAppDevServer/AirAppPreviewer.cs
Normal file
@@ -0,0 +1,129 @@
|
||||
using System.Diagnostics;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace LanMountainDesktop.AirAppDevServer;
|
||||
|
||||
/// <summary>
|
||||
/// AirApp 预览工具
|
||||
/// 在独立窗口中预览组件或窗口,无需安装到宿主
|
||||
/// </summary>
|
||||
public sealed class AirAppPreviewer
|
||||
{
|
||||
private readonly string _projectPath;
|
||||
|
||||
public AirAppPreviewer(string projectPath)
|
||||
{
|
||||
_projectPath = Path.GetFullPath(projectPath);
|
||||
}
|
||||
|
||||
public async Task PreviewComponentAsync(string componentId)
|
||||
{
|
||||
Console.WriteLine($"🎨 预览组件: {componentId}");
|
||||
await LaunchPreviewAsync("component", componentId);
|
||||
}
|
||||
|
||||
public async Task PreviewWindowAsync(string windowId)
|
||||
{
|
||||
Console.WriteLine($"🪟 预览窗口: {windowId}");
|
||||
await LaunchPreviewAsync("window", windowId);
|
||||
}
|
||||
|
||||
public async Task PreviewAllAsync()
|
||||
{
|
||||
Console.WriteLine("📋 加载 AirApp 清单...");
|
||||
|
||||
var manifest = await LoadManifestAsync();
|
||||
if (manifest == null)
|
||||
{
|
||||
Console.WriteLine("❌ 未找到 airapp.json");
|
||||
return;
|
||||
}
|
||||
|
||||
Console.WriteLine($"✅ AirApp: {manifest.Name}");
|
||||
Console.WriteLine();
|
||||
|
||||
// 显示可用的组件和窗口
|
||||
if (manifest.Components?.Count > 0)
|
||||
{
|
||||
Console.WriteLine("📦 可用组件:");
|
||||
foreach (var comp in manifest.Components)
|
||||
{
|
||||
Console.WriteLine($" - {comp.Id}: {comp.Name}");
|
||||
}
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
if (manifest.Windows?.Count > 0)
|
||||
{
|
||||
Console.WriteLine("🪟 可用窗口:");
|
||||
foreach (var win in manifest.Windows)
|
||||
{
|
||||
Console.WriteLine($" - {win.Id}: {win.Name}");
|
||||
}
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
Console.WriteLine("使用以下命令预览:");
|
||||
Console.WriteLine(" airapp-dev preview --component <component-id>");
|
||||
Console.WriteLine(" airapp-dev preview --window <window-id>");
|
||||
}
|
||||
|
||||
private async Task LaunchPreviewAsync(string type, string id)
|
||||
{
|
||||
// 确保项目已构建
|
||||
var binPath = Path.Combine(_projectPath, "bin", "Debug", "net10.0");
|
||||
if (!Directory.Exists(binPath))
|
||||
{
|
||||
Console.WriteLine("❌ 未找到构建输出,请先运行: dotnet build");
|
||||
return;
|
||||
}
|
||||
|
||||
Console.WriteLine($"📁 输出路径: {binPath}");
|
||||
Console.WriteLine("🚀 启动预览窗口...");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("💡 提示: 关闭预览窗口以退出");
|
||||
Console.WriteLine();
|
||||
|
||||
// TODO: 这里需要启动一个预览宿主应用
|
||||
// 预览宿主会加载 AirApp 并显示指定的组件或窗口
|
||||
Console.WriteLine("⚠️ 预览功能需要配合 LanMountainDesktop 宿主运行");
|
||||
Console.WriteLine(" 暂时请使用: dotnet run --project LanMountainDesktop.csproj -- --debug-airapp <path>");
|
||||
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task<ManifestModel?> LoadManifestAsync()
|
||||
{
|
||||
var manifestPath = Path.Combine(_projectPath, "airapp.json");
|
||||
if (!File.Exists(manifestPath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var json = await File.ReadAllTextAsync(manifestPath);
|
||||
return JsonSerializer.Deserialize<ManifestModel>(json, new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
});
|
||||
}
|
||||
|
||||
private sealed class ManifestModel
|
||||
{
|
||||
public string Id { get; set; } = "";
|
||||
public string Name { get; set; } = "";
|
||||
public List<ComponentModel>? Components { get; set; }
|
||||
public List<WindowModel>? Windows { get; set; }
|
||||
}
|
||||
|
||||
private sealed class ComponentModel
|
||||
{
|
||||
public string Id { get; set; } = "";
|
||||
public string Name { get; set; } = "";
|
||||
}
|
||||
|
||||
private sealed class WindowModel
|
||||
{
|
||||
public string Id { get; set; } = "";
|
||||
public string Name { get; set; } = "";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<LangVersion>preview</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
|
||||
<PackageReference Include="Microsoft.Build.Locator" Version="1.7.8" />
|
||||
<PackageReference Include="Microsoft.Build" Version="17.11.4" />
|
||||
<PackageReference Include="Microsoft.Build.Framework" Version="17.11.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\LanMountainDesktop.AirAppSdk\LanMountainDesktop.AirAppSdk.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
149
LanMountainDesktop.AirAppDevServer/Program.cs
Normal file
149
LanMountainDesktop.AirAppDevServer/Program.cs
Normal file
@@ -0,0 +1,149 @@
|
||||
using System.CommandLine;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace LanMountainDesktop.AirAppDevServer;
|
||||
|
||||
/// <summary>
|
||||
/// AirApp 开发服务器主程序
|
||||
/// 提供热重载、实时预览等开发功能
|
||||
/// </summary>
|
||||
class Program
|
||||
{
|
||||
static async Task<int> Main(string[] args)
|
||||
{
|
||||
var rootCommand = new RootCommand("LanMountainDesktop AirApp 开发服务器");
|
||||
|
||||
// 开发模式命令
|
||||
var devCommand = new Command("dev", "启动开发服务器(支持热重载)");
|
||||
var projectPathOption = new Option<string>(
|
||||
aliases: new[] { "--project", "-p" },
|
||||
description: "AirApp 项目路径",
|
||||
getDefaultValue: () => Directory.GetCurrentDirectory());
|
||||
var portOption = new Option<int>(
|
||||
aliases: new[] { "--port" },
|
||||
description: "开发服务器端口",
|
||||
getDefaultValue: () => 5000);
|
||||
var verboseOption = new Option<bool>(
|
||||
aliases: new[] { "--verbose", "-v" },
|
||||
description: "显示详细日志");
|
||||
|
||||
devCommand.AddOption(projectPathOption);
|
||||
devCommand.AddOption(portOption);
|
||||
devCommand.AddOption(verboseOption);
|
||||
|
||||
devCommand.SetHandler(async (projectPath, port, verbose) =>
|
||||
{
|
||||
await RunDevServerAsync(projectPath, port, verbose);
|
||||
}, projectPathOption, portOption, verboseOption);
|
||||
|
||||
// 预览命令
|
||||
var previewCommand = new Command("preview", "预览 AirApp(无需安装到宿主)");
|
||||
var componentOption = new Option<string?>(
|
||||
aliases: new[] { "--component", "-c" },
|
||||
description: "要预览的组件 ID");
|
||||
var windowOption = new Option<string?>(
|
||||
aliases: new[] { "--window", "-w" },
|
||||
description: "要预览的窗口 ID");
|
||||
|
||||
previewCommand.AddOption(projectPathOption);
|
||||
previewCommand.AddOption(componentOption);
|
||||
previewCommand.AddOption(windowOption);
|
||||
|
||||
previewCommand.SetHandler(async (projectPath, component, window) =>
|
||||
{
|
||||
await RunPreviewAsync(projectPath, component, window);
|
||||
}, projectPathOption, componentOption, windowOption);
|
||||
|
||||
// 打包命令
|
||||
var packageCommand = new Command("package", "打包 AirApp 为 .laapp 文件");
|
||||
var outputOption = new Option<string?>(
|
||||
aliases: new[] { "--output", "-o" },
|
||||
description: "输出路径");
|
||||
|
||||
packageCommand.AddOption(projectPathOption);
|
||||
packageCommand.AddOption(outputOption);
|
||||
|
||||
packageCommand.SetHandler(async (projectPath, output) =>
|
||||
{
|
||||
await PackageAirAppAsync(projectPath, output);
|
||||
}, projectPathOption, outputOption);
|
||||
|
||||
rootCommand.AddCommand(devCommand);
|
||||
rootCommand.AddCommand(previewCommand);
|
||||
rootCommand.AddCommand(packageCommand);
|
||||
|
||||
return await rootCommand.InvokeAsync(args);
|
||||
}
|
||||
|
||||
static async Task RunDevServerAsync(string projectPath, int port, bool verbose)
|
||||
{
|
||||
Console.WriteLine("🚀 启动 AirApp 开发服务器...");
|
||||
Console.WriteLine($"📁 项目路径: {projectPath}");
|
||||
Console.WriteLine($"🔌 端口: {port}");
|
||||
Console.WriteLine();
|
||||
|
||||
var server = new AirAppDevServer(projectPath, port, verbose);
|
||||
await server.StartAsync();
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("✅ 开发服务器已启动");
|
||||
Console.WriteLine($"🌐 预览地址: http://localhost:{port}");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("按 Ctrl+C 停止服务器...");
|
||||
Console.WriteLine();
|
||||
|
||||
// 等待取消信号
|
||||
var cts = new CancellationTokenSource();
|
||||
Console.CancelKeyPress += (sender, e) =>
|
||||
{
|
||||
e.Cancel = true;
|
||||
cts.Cancel();
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
await Task.Delay(Timeout.Infinite, cts.Token);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("🛑 正在停止服务器...");
|
||||
}
|
||||
|
||||
await server.StopAsync();
|
||||
Console.WriteLine("✅ 服务器已停止");
|
||||
}
|
||||
|
||||
static async Task RunPreviewAsync(string projectPath, string? component, string? window)
|
||||
{
|
||||
Console.WriteLine("👁️ 启动 AirApp 预览...");
|
||||
Console.WriteLine($"📁 项目路径: {projectPath}");
|
||||
|
||||
var previewer = new AirAppPreviewer(projectPath);
|
||||
|
||||
if (!string.IsNullOrEmpty(component))
|
||||
{
|
||||
await previewer.PreviewComponentAsync(component);
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(window))
|
||||
{
|
||||
await previewer.PreviewWindowAsync(window);
|
||||
}
|
||||
else
|
||||
{
|
||||
await previewer.PreviewAllAsync();
|
||||
}
|
||||
}
|
||||
|
||||
static async Task PackageAirAppAsync(string projectPath, string? output)
|
||||
{
|
||||
Console.WriteLine("📦 打包 AirApp...");
|
||||
Console.WriteLine($"📁 项目路径: {projectPath}");
|
||||
|
||||
var packager = new AirAppPackager(projectPath);
|
||||
var outputPath = await packager.PackageAsync(output);
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine($"✅ 打包完成: {outputPath}");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user