mirror of
https://github.com/raven-pensieve/obsidian-ace-code-editor.git
synced 2026-07-22 05:48:56 +00:00
移除 copy-to-vault.mjs 脚本,简化项目构建流程。 更新 version-bump.mjs 使用 node: 前缀导入,并 修复 Beta 版本发布时的 versions.json 更新逻辑。 规范化 sync-i18n.mjs 代码风格,统一使用单引号 和标准缩进格式,提升代码可维护性。
304 lines
7.9 KiB
JavaScript
304 lines
7.9 KiB
JavaScript
// scripts/deploy.mjs
|
||
// 统一的部署脚本:支持多种部署模式
|
||
// 用法: node scripts/deploy.mjs [link|copy] [--vault-path=/path/to/vault]
|
||
|
||
import dotenv from "dotenv";
|
||
import fs from "fs-extra";
|
||
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);
|
||
}
|
||
|
||
dotenv.config({ path: envPath });
|
||
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 = fs.readJsonSync(manifestFile);
|
||
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 使用 junction,Linux/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();
|