raven-pensieve_obsidian-ace.../scripts/deploy.mjs
RavenHogWarts a095d31176
refactor: 优化解决自动审查 (#112)
* refactor: 移除未使用的依赖项

移除项目中未使用的依赖项,包括 dotenv、
builtin-modules、fs-extra、jsonfile 和
universalify。这些依赖项不再被代码使用,
移除它们可以减少项目体积并简化依赖管理。

* refactor: 迁移 ESLint 配置到新版本

将 ESLint 配置迁移到最新的扁平化配置格式,
使用 defineConfig 和 globalIgnores 简化配置
结构。更新 TypeScript 解析器为 typescript-eslint
包,提取 Obsidian 全局变量为常量以提高可维护
性。调整测试环境从 Jest 改为 Mocha,并添加
React 全局变量支持。

* feat: 添加编辑器样式和调整大小光标

添加隐藏的字体测试元素样式用于测量字体
尺寸。添加调整大小状态样式以在拖动时显示
正确的光标和禁用文本选择。这些样式改进
了编辑器的用户交互体验。

* feat: 使用全局对象调用计时和动画函数

将所有 setTimeout、requestAnimationFrame 和
document.createElement 调用改为使用全局对象
(window 或 activeDocument) 来调用。这样做是为了
提高代码的可测试性和兼容性,使得在测试环境中能够
更容易地 mock 这些全局函数,同时确保在不同的执行
上下文中都能正确地访问这些 API。

* feat: 使用 activeDocument 替换全局 document

将所有全局 document 引用替换为 activeDocument,以支持
多文档环境。这包括事件监听器、DOM 操作和样式管理。

主要改动:
- Minimap 和 useResize 中的事件监听使用 activeDocument
- 将 useResize 中的内联样式改为 CSS 类名管理
- isFontAvailable 函数改用 Canvas API 替代 DOM 测试
- AceService 和 Select 组件中的 DOM 查询使用
  activeDocument
- 使用 window.requestAnimationFrame 替代全局引用

这些改动提高了代码的可移植性和多文档支持能力。

* feat: 更新设置视图的显示文本

修改 SettingsView 组件中 getDisplayText 方法返回的
文本内容,将显示文本从 "ACE SettingTab" 更新为
"Ace settingtab",以保持与应用命名规范的一致性。

* style: 修复代码格式和样式问题

修复多个文件中的代码格式问题,包括函数参数和
依赖数组末尾的逗号缺失。优化 CSS 选择器特异性
以确保 Ace 搜索框样式正确应用,并移除不必要的
!important 声明。这些改动提高了代码的一致性
和可维护性。

* feat: 移除 ace-builds esm-resolver 导入

移除了 ace-builds 的 esm-resolver 导入语句,因为该模块
在当前环境中不需要自动处理模块路径。同时简化了
AceService 构造函数,移除了不必要的注释说明。这些
改动使代码更加简洁,减少了不必要的依赖。
2026-05-20 00:36:28 +08:00

305 lines
7.9 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// scripts/deploy.mjs
// 统一的部署脚本:支持多种部署模式
// 用法: node scripts/deploy.mjs [link|copy] [--vault-path=/path/to/vault]
import * as fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const projectRoot = path.resolve(__dirname, "..");
const distDir = path.join(projectRoot, "dist");
const manifestPath = path.join(projectRoot, "manifest.json");
const envPath = path.join(projectRoot, ".env");
const backupDir = path.join(projectRoot, ".backup");
// ==================== 参数解析 ====================
const args = process.argv.slice(2);
const deployMode = args[0] || "link"; // link | copy
const customVaultPath = args
.find((arg) => arg.startsWith("--vault-path="))
?.split("=")[1];
// ==================== 工具函数 ====================
const log = {
success: (msg) => console.log(`${msg}`),
error: (msg) => console.error(`${msg}`),
info: (msg) => console.log(` ${msg}`),
warn: (msg) => console.warn(`⚠️ ${msg}`),
};
/**
* 检查并加载环境配置
*/
function loadVaultPath() {
if (customVaultPath) {
return path.resolve(customVaultPath);
}
if (!fs.existsSync(envPath)) {
log.warn(".env 文件不存在,跳过部署");
process.exit(0);
}
const vaultPath = process.env.VAULT_PATH;
if (!vaultPath) {
log.error(
"未设置 VAULT_PATH请在 .env 文件中设置或使用 --vault-path 参数",
);
process.exit(1);
}
return path.resolve(vaultPath);
}
/**
* 读取插件ID
*/
function getPluginId() {
// 优先从 dist/manifest.json 读取(构建后)
const distManifestPath = path.join(distDir, "manifest.json");
const sourceManifestPath = manifestPath;
const manifestFile = fs.existsSync(distManifestPath)
? distManifestPath
: sourceManifestPath;
if (!fs.existsSync(manifestFile)) {
log.error("找不到 manifest.json 文件");
process.exit(1);
}
try {
const manifest = JSON.parse(fs.readFileSync(manifestFile, "utf8"));
if (!manifest.id) {
throw new Error("manifest.json 中没有 id 字段");
}
return manifest.id;
} catch (error) {
log.error(`读取 manifest.json 失败: ${error.message}`);
process.exit(1);
}
}
/**
* 确保 dist 目录和必要文件存在
*/
function ensureDistReady() {
if (!fs.existsSync(distDir)) {
fs.mkdirSync(distDir, { recursive: true });
}
// 确保 .hotreload 文件存在(用于 Obsidian 热重载)
const hotreloadPath = path.join(distDir, ".hotreload");
if (!fs.existsSync(hotreloadPath)) {
fs.writeFileSync(hotreloadPath, "");
}
}
/**
* 备份 data.json 到独立备份目录
*/
function backupDataJson(targetPluginDir) {
const dataJsonPath = path.join(targetPluginDir, "data.json");
if (fs.existsSync(dataJsonPath)) {
fs.mkdirSync(backupDir, { recursive: true });
const backupPath = path.join(backupDir, "data.json");
fs.copyFileSync(dataJsonPath, backupPath);
log.info("已备份 data.json 到 .backup/");
return true;
}
return false;
}
/**
* 递归复制目录
*/
function copyDirRecursive(src, dest) {
fs.mkdirSync(dest, { recursive: true });
const entries = fs.readdirSync(src, { withFileTypes: true });
for (const entry of entries) {
const srcPath = path.join(src, entry.name);
const destPath = path.join(dest, entry.name);
if (entry.isDirectory()) {
copyDirRecursive(srcPath, destPath);
} else {
fs.copyFileSync(srcPath, destPath);
}
}
}
// ==================== 部署模式 ====================
/**
* 模式1: link - 创建软链接(开发模式)
* Windows 使用 junctionLinux/Mac 使用 symlink
*/
async function deployLink(vaultPath, pluginId) {
const targetPluginDir = path.join(
vaultPath,
".obsidian",
"plugins",
pluginId,
);
// 检查是否指向同一位置(避免循环链接)
if (path.resolve(targetPluginDir) === path.resolve(distDir)) {
log.info("目标路径与 dist 目录相同,跳过链接");
process.exit(0);
}
// 处理已存在的目标路径
if (fs.existsSync(targetPluginDir)) {
const stats = fs.lstatSync(targetPluginDir);
if (stats.isSymbolicLink()) {
const linkTarget = fs.readlinkSync(targetPluginDir);
const resolvedLink = path.resolve(
path.dirname(targetPluginDir),
linkTarget,
);
if (resolvedLink === path.resolve(distDir)) {
log.success(`软链接已存在: dist → ${pluginId}`);
return;
}
} else if (stats.isDirectory()) {
// 备份 data.json 到独立备份目录
backupDataJson(targetPluginDir);
}
// 删除旧的路径
fs.rmSync(targetPluginDir, { recursive: true, force: true });
}
// 确保父目录存在
fs.mkdirSync(path.dirname(targetPluginDir), { recursive: true });
// 创建软链接
try {
const linkType = process.platform === "win32" ? "junction" : "dir";
fs.symlinkSync(distDir, targetPluginDir, linkType);
log.success(`软链接已创建: dist → ${pluginId}`);
// 自动从备份恢复 data.json 到 dist/
const backupPath = path.join(backupDir, "data.json");
if (fs.existsSync(backupPath)) {
const distDataJsonPath = path.join(distDir, "data.json");
fs.copyFileSync(backupPath, distDataJsonPath);
log.success("已从 .backup/ 恢复 data.json 到 dist/");
}
} catch (error) {
log.error(`创建软链接失败: ${error.message}`);
process.exit(1);
}
}
/**
* 模式2: copy - 完整复制(生产构建)
* 复制整个 dist 目录,保护用户的 data.json
*/
async function deployCopy(vaultPath, pluginId) {
const targetPluginDir = path.join(
vaultPath,
".obsidian",
"plugins",
pluginId,
);
// 检查是否指向同一位置
if (path.resolve(targetPluginDir) === path.resolve(distDir)) {
log.info("目标路径与 dist 目录相同,跳过复制");
process.exit(0);
}
// 确保 dist 目录存在
if (!fs.existsSync(distDir)) {
log.error("dist 目录不存在,请先运行构建命令");
process.exit(1);
}
// 保护 data.json双重保护机制
// 1. 内存临时保存(用于立即恢复)
// 2. 备份到 .backup/ (用于意外情况恢复)
let dataJsonContent = null;
const dataJsonPath = path.join(targetPluginDir, "data.json");
if (fs.existsSync(dataJsonPath)) {
try {
dataJsonContent = fs.readFileSync(dataJsonPath, "utf8");
log.info("已保存现有的 data.json");
// 同时备份到 .backup/ 作为安全保障
fs.mkdirSync(backupDir, { recursive: true });
fs.writeFileSync(
path.join(backupDir, "data.json"),
dataJsonContent,
);
log.info("已备份到 .backup/data.json");
} catch (error) {
log.warn(`读取 data.json 失败: ${error.message}`);
}
}
// 删除旧的目标目录
if (fs.existsSync(targetPluginDir)) {
fs.rmSync(targetPluginDir, { recursive: true, force: true });
}
// 复制整个 dist 目录
try {
copyDirRecursive(distDir, targetPluginDir);
const fileCount = fs.readdirSync(distDir).length;
log.success(`已复制 ${fileCount} 个文件到 ${pluginId}`);
} catch (error) {
log.error(`复制失败: ${error.message}`);
process.exit(1);
}
// 恢复 data.json
if (dataJsonContent) {
try {
fs.writeFileSync(
path.join(targetPluginDir, "data.json"),
dataJsonContent,
);
log.success("已恢复 data.json");
} catch (error) {
log.error(`恢复 data.json 失败: ${error.message}`);
}
}
}
// ==================== 主函数 ====================
async function main() {
try {
log.info(`部署模式: ${deployMode}`);
ensureDistReady();
const vaultPath = loadVaultPath();
const pluginId = getPluginId();
log.info(`目标 Vault: ${vaultPath}`);
log.info(`插件 ID: ${pluginId}`);
switch (deployMode) {
case "link":
await deployLink(vaultPath, pluginId);
break;
case "copy":
await deployCopy(vaultPath, pluginId);
break;
default:
log.error(`未知的部署模式: ${deployMode}`);
log.info("支持的模式: link | copy");
process.exit(1);
}
} catch (error) {
log.error(`部署失败: ${error.message}`);
process.exit(1);
}
}
main();