momo-ai 1.0.74 → 1.0.75
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/package.json +1 -1
- package/src/executor/executeDocs.js +129 -316
package/package.json
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const { execSync } = require('child_process');
|
|
2
|
+
const crypto = require('crypto');
|
|
2
3
|
const fs = require('fs');
|
|
3
4
|
const fsAsync = require('fs').promises;
|
|
4
5
|
const path = require('path');
|
|
@@ -21,103 +22,80 @@ const _getObsidianConfigPath = () => {
|
|
|
21
22
|
return path.join(xdgConfig, 'obsidian', 'obsidian.json');
|
|
22
23
|
};
|
|
23
24
|
|
|
25
|
+
/**
|
|
26
|
+
* 根据绝对路径生成确定性 vault ID(sha256 前 16 位 hex)
|
|
27
|
+
* 同一路径永远得到相同 ID,避免随机 ID 导致 Vault Not Found
|
|
28
|
+
* @param {string} absPath vault 绝对路径
|
|
29
|
+
* @returns {string}
|
|
30
|
+
*/
|
|
31
|
+
const _deriveVaultId = (absPath) => {
|
|
32
|
+
return crypto.createHash('sha256').update(absPath).digest('hex').slice(0, 16);
|
|
33
|
+
};
|
|
34
|
+
|
|
24
35
|
/**
|
|
25
36
|
* 检查 Obsidian 是否已安装
|
|
26
|
-
* @returns {
|
|
37
|
+
* @returns {boolean}
|
|
27
38
|
*/
|
|
28
|
-
const _isObsidianInstalled =
|
|
39
|
+
const _isObsidianInstalled = () => {
|
|
29
40
|
const platform = os.platform();
|
|
30
|
-
|
|
41
|
+
|
|
31
42
|
if (platform === 'darwin') {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
} else if (platform === 'win32') {
|
|
36
|
-
// Windows: 检查常见安装路径或注册表
|
|
43
|
+
return fs.existsSync('/Applications/Obsidian.app');
|
|
44
|
+
}
|
|
45
|
+
if (platform === 'win32') {
|
|
37
46
|
const localAppData = process.env.LOCALAPPDATA || '';
|
|
38
47
|
const programFiles = process.env.PROGRAMFILES || 'C:\\Program Files';
|
|
39
|
-
|
|
48
|
+
return [
|
|
40
49
|
path.join(localAppData, 'Obsidian', 'Obsidian.exe'),
|
|
41
|
-
path.join(programFiles, 'Obsidian', 'Obsidian.exe')
|
|
42
|
-
];
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
50
|
+
path.join(programFiles, 'Obsidian', 'Obsidian.exe'),
|
|
51
|
+
].some(p => fs.existsSync(p));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Linux
|
|
55
|
+
try {
|
|
56
|
+
execSync('which obsidian', { stdio: 'pipe' });
|
|
57
|
+
return true;
|
|
58
|
+
} catch {
|
|
59
|
+
if (fs.existsSync('/snap/bin/obsidian')) return true;
|
|
46
60
|
try {
|
|
47
|
-
execSync('
|
|
61
|
+
execSync('flatpak list | grep -i obsidian', { stdio: 'pipe' });
|
|
48
62
|
return true;
|
|
49
63
|
} catch {
|
|
50
|
-
|
|
51
|
-
const snapPath = '/snap/bin/obsidian';
|
|
52
|
-
const flatpakCheck = () => {
|
|
53
|
-
try {
|
|
54
|
-
execSync('flatpak list | grep -i obsidian', { stdio: 'pipe' });
|
|
55
|
-
return true;
|
|
56
|
-
} catch {
|
|
57
|
-
return false;
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
return fs.existsSync(snapPath) || flatpakCheck();
|
|
64
|
+
return false;
|
|
61
65
|
}
|
|
62
66
|
}
|
|
63
67
|
};
|
|
64
68
|
|
|
65
69
|
/**
|
|
66
|
-
* 检查指定 vault
|
|
67
|
-
* @param {string}
|
|
70
|
+
* 检查指定 vault 是否正在运行(open: true)
|
|
71
|
+
* @param {string} vaultId 确定性 vault ID
|
|
68
72
|
* @returns {Promise<boolean>}
|
|
69
73
|
*/
|
|
70
|
-
const _isVaultRunning = async (
|
|
74
|
+
const _isVaultRunning = async (vaultId) => {
|
|
71
75
|
const configPath = _getObsidianConfigPath();
|
|
72
|
-
|
|
73
76
|
try {
|
|
74
|
-
if (!fs.existsSync(configPath))
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const content = await fsAsync.readFile(configPath, 'utf8');
|
|
79
|
-
const config = JSON.parse(content);
|
|
80
|
-
|
|
81
|
-
// 检查是否有 vault 的 open 状态为 true 且路径匹配
|
|
82
|
-
const openVault = Object.values(config.vaults || {}).find(
|
|
83
|
-
vault => vault.path === vaultPath && vault.open === true
|
|
84
|
-
);
|
|
85
|
-
|
|
86
|
-
return !!openVault;
|
|
77
|
+
if (!fs.existsSync(configPath)) return false;
|
|
78
|
+
const config = JSON.parse(await fsAsync.readFile(configPath, 'utf8'));
|
|
79
|
+
const vault = (config.vaults || {})[vaultId];
|
|
80
|
+
return vault ? vault.open === true : false;
|
|
87
81
|
} catch {
|
|
88
82
|
return false;
|
|
89
83
|
}
|
|
90
84
|
};
|
|
91
85
|
|
|
92
86
|
/**
|
|
93
|
-
* 关闭指定 vault
|
|
94
|
-
*
|
|
95
|
-
* @param {string} vaultPath vault 路径
|
|
87
|
+
* 关闭指定 vault(将 open 设为 false)
|
|
88
|
+
* @param {string} vaultId 确定性 vault ID
|
|
96
89
|
* @returns {Promise<void>}
|
|
97
90
|
*/
|
|
98
|
-
const _closeVault = async (
|
|
91
|
+
const _closeVault = async (vaultId) => {
|
|
99
92
|
const configPath = _getObsidianConfigPath();
|
|
100
|
-
|
|
101
93
|
try {
|
|
102
|
-
if (!fs.existsSync(configPath))
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const config = JSON.parse(content);
|
|
108
|
-
|
|
109
|
-
// 找到对应的 vault 并设置 open 为 false
|
|
110
|
-
let updated = false;
|
|
111
|
-
for (const [id, vault] of Object.entries(config.vaults || {})) {
|
|
112
|
-
if (vault.path === vaultPath && vault.open === true) {
|
|
113
|
-
config.vaults[id].open = false;
|
|
114
|
-
updated = true;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (updated) {
|
|
119
|
-
await fsAsync.writeFile(configPath, JSON.stringify(config), 'utf8');
|
|
120
|
-
// 等待配置生效
|
|
94
|
+
if (!fs.existsSync(configPath)) return;
|
|
95
|
+
const config = JSON.parse(await fsAsync.readFile(configPath, 'utf8'));
|
|
96
|
+
if (config.vaults && config.vaults[vaultId] && config.vaults[vaultId].open) {
|
|
97
|
+
const updatedVaults = { ...config.vaults, [vaultId]: { ...config.vaults[vaultId], open: false } };
|
|
98
|
+
await fsAsync.writeFile(configPath, JSON.stringify({ ...config, vaults: updatedVaults }), 'utf8');
|
|
121
99
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
122
100
|
}
|
|
123
101
|
} catch (error) {
|
|
@@ -126,64 +104,43 @@ const _closeVault = async (vaultPath) => {
|
|
|
126
104
|
};
|
|
127
105
|
|
|
128
106
|
/**
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
const chars = '0123456789abcdef';
|
|
134
|
-
let id = '';
|
|
135
|
-
for (let i = 0; i < 16; i++) {
|
|
136
|
-
id += chars[Math.floor(Math.random() * chars.length)];
|
|
137
|
-
}
|
|
138
|
-
return id;
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* 注册 vault 到 Obsidian 配置
|
|
143
|
-
* @param {string} vaultPath vault 路径
|
|
144
|
-
* @returns {Promise<string|null>} 返回 vault ID,失败返回 null
|
|
107
|
+
* 注册 vault 到 obsidian.json,使用确定性 ID。
|
|
108
|
+
* 若已存在同路径的旧随机 ID 记录,先删除旧记录再以确定性 ID 写入。
|
|
109
|
+
* @param {string} vaultPath vault 绝对路径
|
|
110
|
+
* @returns {Promise<string|null>} 确定性 vault ID,失败返回 null
|
|
145
111
|
*/
|
|
146
112
|
const _registerVaultToObsidian = async (vaultPath) => {
|
|
147
113
|
const configPath = _getObsidianConfigPath();
|
|
148
|
-
|
|
114
|
+
const vaultId = _deriveVaultId(vaultPath);
|
|
115
|
+
|
|
149
116
|
try {
|
|
150
117
|
let config = { vaults: {}, frame: 'custom' };
|
|
151
|
-
|
|
152
|
-
// 读取现有配置
|
|
153
118
|
if (fs.existsSync(configPath)) {
|
|
154
|
-
|
|
155
|
-
config = JSON.parse(content);
|
|
119
|
+
config = JSON.parse(await fsAsync.readFile(configPath, 'utf8'));
|
|
156
120
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
// 已存在,更新 ts 和 open 状态
|
|
166
|
-
[vaultId] = existingVault;
|
|
167
|
-
config.vaults[vaultId].ts = Date.now();
|
|
168
|
-
config.vaults[vaultId].open = true;
|
|
169
|
-
} else {
|
|
170
|
-
// 新 vault,生成 ID 并添加
|
|
171
|
-
vaultId = _generateVaultId();
|
|
172
|
-
config.vaults[vaultId] = {
|
|
173
|
-
path: vaultPath,
|
|
174
|
-
ts: Date.now(),
|
|
175
|
-
open: true
|
|
176
|
-
};
|
|
121
|
+
if (!config.vaults) config.vaults = {};
|
|
122
|
+
|
|
123
|
+
// 删除所有同路径的旧记录(key 可能是随机 ID)
|
|
124
|
+
const updatedVaults = {};
|
|
125
|
+
for (const [id, vault] of Object.entries(config.vaults)) {
|
|
126
|
+
if (vault.path !== vaultPath) {
|
|
127
|
+
updatedVaults[id] = vault;
|
|
128
|
+
}
|
|
177
129
|
}
|
|
178
|
-
|
|
179
|
-
//
|
|
130
|
+
|
|
131
|
+
// 写入确定性 ID 记录
|
|
132
|
+
updatedVaults[vaultId] = {
|
|
133
|
+
path: vaultPath,
|
|
134
|
+
ts: Date.now(),
|
|
135
|
+
open: true,
|
|
136
|
+
};
|
|
137
|
+
|
|
180
138
|
const configDir = path.dirname(configPath);
|
|
181
139
|
if (!fs.existsSync(configDir)) {
|
|
182
140
|
await fsAsync.mkdir(configDir, { recursive: true });
|
|
183
141
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
await fsAsync.writeFile(configPath, JSON.stringify(config), 'utf8');
|
|
142
|
+
|
|
143
|
+
await fsAsync.writeFile(configPath, JSON.stringify({ ...config, vaults: updatedVaults }), 'utf8');
|
|
187
144
|
return vaultId;
|
|
188
145
|
} catch (error) {
|
|
189
146
|
Ec.warn(`⚠ 无法注册 vault 到配置: ${error.message}`);
|
|
@@ -193,28 +150,20 @@ const _registerVaultToObsidian = async (vaultPath) => {
|
|
|
193
150
|
|
|
194
151
|
const _removeVaultRegistration = async (vaultPath) => {
|
|
195
152
|
const configPath = _getObsidianConfigPath();
|
|
196
|
-
|
|
197
153
|
try {
|
|
198
|
-
if (!fs.existsSync(configPath))
|
|
199
|
-
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
const content = await fsAsync.readFile(configPath, 'utf8');
|
|
203
|
-
const config = JSON.parse(content);
|
|
154
|
+
if (!fs.existsSync(configPath)) return false;
|
|
155
|
+
const config = JSON.parse(await fsAsync.readFile(configPath, 'utf8'));
|
|
204
156
|
let updated = false;
|
|
205
|
-
|
|
157
|
+
const updatedVaults = {};
|
|
206
158
|
for (const [id, vault] of Object.entries(config.vaults || {})) {
|
|
207
159
|
if (vault.path === vaultPath) {
|
|
208
|
-
delete config.vaults[id];
|
|
209
160
|
updated = true;
|
|
161
|
+
} else {
|
|
162
|
+
updatedVaults[id] = vault;
|
|
210
163
|
}
|
|
211
164
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
return false;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
await fsAsync.writeFile(configPath, JSON.stringify(config), 'utf8');
|
|
165
|
+
if (!updated) return false;
|
|
166
|
+
await fsAsync.writeFile(configPath, JSON.stringify({ ...config, vaults: updatedVaults }), 'utf8');
|
|
218
167
|
return true;
|
|
219
168
|
} catch (error) {
|
|
220
169
|
Ec.warn(`⚠ 清理 vault 注册失败: ${error.message}`);
|
|
@@ -224,12 +173,8 @@ const _removeVaultRegistration = async (vaultPath) => {
|
|
|
224
173
|
|
|
225
174
|
const _removeLocalObsidianConfig = async (targetDir) => {
|
|
226
175
|
const obsidianConfigPath = path.join(targetDir, '.obsidian');
|
|
227
|
-
|
|
228
176
|
try {
|
|
229
|
-
if (!fs.existsSync(obsidianConfigPath))
|
|
230
|
-
return false;
|
|
231
|
-
}
|
|
232
|
-
|
|
177
|
+
if (!fs.existsSync(obsidianConfigPath)) return false;
|
|
233
178
|
await fsAsync.rm(obsidianConfigPath, { recursive: true, force: true });
|
|
234
179
|
return true;
|
|
235
180
|
} catch (error) {
|
|
@@ -241,134 +186,34 @@ const _removeLocalObsidianConfig = async (targetDir) => {
|
|
|
241
186
|
const _resetVaultConfig = async (targetDir) => {
|
|
242
187
|
Ec.waiting(`正在清理 Obsidian 配置: ${targetDir.cyan}...`);
|
|
243
188
|
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
}
|
|
189
|
+
const vaultId = _deriveVaultId(targetDir);
|
|
190
|
+
const isRunning = await _isVaultRunning(vaultId);
|
|
191
|
+
if (isRunning) await _closeVault(vaultId);
|
|
248
192
|
|
|
249
193
|
const removedVault = await _removeVaultRegistration(targetDir);
|
|
250
194
|
const removedConfig = await _removeLocalObsidianConfig(targetDir);
|
|
251
195
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
} else {
|
|
255
|
-
Ec.info('✓ 未发现需要移除的 vault 注册');
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
if (removedConfig) {
|
|
259
|
-
Ec.info('✓ 已删除目标目录下的 .obsidian 配置');
|
|
260
|
-
} else {
|
|
261
|
-
Ec.info('✓ 目标目录下不存在 .obsidian 配置');
|
|
262
|
-
}
|
|
263
|
-
|
|
196
|
+
Ec.info(removedVault ? '✓ 已移除 Obsidian 中的 vault 注册' : '✓ 未发现需要移除的 vault 注册');
|
|
197
|
+
Ec.info(removedConfig ? '✓ 已删除目标目录下的 .obsidian 配置' : '✓ 目标目录下不存在 .obsidian 配置');
|
|
264
198
|
Ec.info('✅ Obsidian 配置清理完成');
|
|
265
199
|
};
|
|
266
200
|
|
|
267
201
|
/**
|
|
268
|
-
*
|
|
269
|
-
*
|
|
270
|
-
* @param {string}
|
|
271
|
-
* @
|
|
272
|
-
* @returns {Promise<void>}
|
|
202
|
+
* 通过 Obsidian URL scheme 打开 vault
|
|
203
|
+
* 使用确定性 ID,保证每次打开的 ID 与注册时一致
|
|
204
|
+
* @param {string} vaultId 确定性 vault ID
|
|
205
|
+
* @returns {void}
|
|
273
206
|
*/
|
|
274
|
-
const _openWithObsidian =
|
|
207
|
+
const _openWithObsidian = (vaultId) => {
|
|
208
|
+
const url = `obsidian://open?vault=${vaultId}`;
|
|
275
209
|
const platform = os.platform();
|
|
276
210
|
|
|
277
211
|
if (platform === 'darwin') {
|
|
278
|
-
|
|
279
|
-
const python3Path = '/opt/miniconda3/bin/python3';
|
|
280
|
-
return new Promise((resolve, reject) => {
|
|
281
|
-
const child = spawn(python3Path, ['-m', 'obsidian_cli', 'open', targetDir], {
|
|
282
|
-
stdio: 'ignore'
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
child.on('error', (error) => {
|
|
286
|
-
reject(new Error(`启动 Obsidian 失败: ${error.message}`));
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
child.on('close', (code) => {
|
|
290
|
-
if (code === 0) {
|
|
291
|
-
setTimeout(() => resolve(), 1000);
|
|
292
|
-
} else {
|
|
293
|
-
reject(new Error(`启动 Obsidian 失败,退出码: ${code}`));
|
|
294
|
-
}
|
|
295
|
-
});
|
|
296
|
-
});
|
|
212
|
+
execSync(`open "${url}"`, { stdio: 'ignore' });
|
|
297
213
|
} else if (platform === 'win32') {
|
|
298
|
-
|
|
299
|
-
const localAppData = process.env.LOCALAPPDATA || '';
|
|
300
|
-
const programFiles = process.env.PROGRAMFILES || 'C:\\Program Files';
|
|
301
|
-
const possiblePaths = [
|
|
302
|
-
path.join(localAppData, 'Obsidian', 'Obsidian.exe'),
|
|
303
|
-
path.join(programFiles, 'Obsidian', 'Obsidian.exe')
|
|
304
|
-
];
|
|
305
|
-
|
|
306
|
-
const obsidianExe = possiblePaths.find(p => fs.existsSync(p));
|
|
307
|
-
if (!obsidianExe) {
|
|
308
|
-
throw new Error('找不到 Obsidian 可执行文件');
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
return new Promise((resolve, reject) => {
|
|
312
|
-
const child = spawn(obsidianExe, [targetDir], {
|
|
313
|
-
detached: true,
|
|
314
|
-
stdio: 'ignore'
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
child.on('error', (error) => {
|
|
318
|
-
reject(new Error(`启动 Obsidian 失败: ${error.message}`));
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
child.unref();
|
|
322
|
-
|
|
323
|
-
setTimeout(() => {
|
|
324
|
-
resolve();
|
|
325
|
-
}, 500);
|
|
326
|
-
});
|
|
214
|
+
execSync(`start "" "${url}"`, { stdio: 'ignore', shell: true });
|
|
327
215
|
} else {
|
|
328
|
-
|
|
329
|
-
const snapPath = '/snap/bin/obsidian';
|
|
330
|
-
const flatpakPath = 'flatpak run md.obsidian.Obsidian';
|
|
331
|
-
let obsidianCmd = 'obsidian';
|
|
332
|
-
let args = [targetDir];
|
|
333
|
-
let useShell = false;
|
|
334
|
-
|
|
335
|
-
// 检查 snap 安装
|
|
336
|
-
if (fs.existsSync(snapPath)) {
|
|
337
|
-
obsidianCmd = snapPath;
|
|
338
|
-
} else {
|
|
339
|
-
// 尝试 flatpak
|
|
340
|
-
try {
|
|
341
|
-
execSync('flatpak list | grep -i obsidian', { stdio: 'pipe' });
|
|
342
|
-
obsidianCmd = flatpakPath;
|
|
343
|
-
useShell = true;
|
|
344
|
-
args = [targetDir];
|
|
345
|
-
} catch {
|
|
346
|
-
// 使用默认命令
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
return new Promise((resolve, reject) => {
|
|
351
|
-
const spawnOptions = {
|
|
352
|
-
detached: true,
|
|
353
|
-
stdio: 'ignore'
|
|
354
|
-
};
|
|
355
|
-
|
|
356
|
-
if (useShell) {
|
|
357
|
-
spawnOptions.shell = true;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
const child = spawn(obsidianCmd, args, spawnOptions);
|
|
361
|
-
|
|
362
|
-
child.on('error', (error) => {
|
|
363
|
-
reject(new Error(`启动 Obsidian 失败: ${error.message}`));
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
child.unref();
|
|
367
|
-
|
|
368
|
-
setTimeout(() => {
|
|
369
|
-
resolve();
|
|
370
|
-
}, 500);
|
|
371
|
-
});
|
|
216
|
+
execSync(`xdg-open "${url}"`, { stdio: 'ignore' });
|
|
372
217
|
}
|
|
373
218
|
};
|
|
374
219
|
|
|
@@ -377,38 +222,34 @@ const _openWithObsidian = async (targetDir, vaultId = null) => {
|
|
|
377
222
|
* @returns {string}
|
|
378
223
|
*/
|
|
379
224
|
const _getOsName = () => {
|
|
380
|
-
|
|
381
|
-
switch (platform) {
|
|
225
|
+
switch (os.platform()) {
|
|
382
226
|
case 'darwin': return 'macOS';
|
|
383
227
|
case 'win32': return 'Windows';
|
|
384
228
|
case 'linux': return 'Linux';
|
|
385
|
-
default: return platform;
|
|
229
|
+
default: return os.platform();
|
|
386
230
|
}
|
|
387
231
|
};
|
|
388
232
|
|
|
389
233
|
/**
|
|
390
|
-
* 初始化 .obsidian
|
|
391
|
-
* 从模板目录复制默认配置
|
|
234
|
+
* 初始化 .obsidian 目录(从模板复制)
|
|
392
235
|
* @param {string} targetDir 目标目录
|
|
393
|
-
* @returns {Promise<boolean>}
|
|
236
|
+
* @returns {Promise<boolean>}
|
|
394
237
|
*/
|
|
395
238
|
const _initObsidianConfig = async (targetDir) => {
|
|
396
239
|
const obsidianSourcePath = path.resolve(__dirname, '../_template/LAIN/.obsidian');
|
|
397
240
|
const obsidianTargetPath = path.join(targetDir, '.obsidian');
|
|
398
|
-
|
|
399
|
-
// 检查模板目录是否存在
|
|
241
|
+
|
|
400
242
|
if (!fs.existsSync(obsidianSourcePath)) {
|
|
401
243
|
Ec.warn(`⚠ 模板目录不存在: ${obsidianSourcePath}`);
|
|
402
244
|
return false;
|
|
403
245
|
}
|
|
404
|
-
|
|
246
|
+
|
|
405
247
|
try {
|
|
406
248
|
const stat = await fsAsync.stat(obsidianSourcePath);
|
|
407
249
|
if (!stat.isDirectory()) {
|
|
408
250
|
Ec.warn(`⚠ 模板路径不是目录: ${obsidianSourcePath}`);
|
|
409
251
|
return false;
|
|
410
252
|
}
|
|
411
|
-
|
|
412
253
|
Ec.waiting('正在初始化 .obsidian 配置...');
|
|
413
254
|
await copyDir(obsidianSourcePath, obsidianTargetPath);
|
|
414
255
|
Ec.info('✓ 已创建 .obsidian 配置目录');
|
|
@@ -420,38 +261,27 @@ const _initObsidianConfig = async (targetDir) => {
|
|
|
420
261
|
};
|
|
421
262
|
|
|
422
263
|
/**
|
|
423
|
-
*
|
|
424
|
-
* 添加 .obsidian/workspace.json 到忽略列表
|
|
264
|
+
* 更新 .gitignore,添加 .obsidian/workspace.json
|
|
425
265
|
* @param {string} targetDir 目标目录
|
|
426
266
|
* @returns {Promise<void>}
|
|
427
267
|
*/
|
|
428
268
|
const _updateGitignore = async (targetDir) => {
|
|
429
269
|
const gitignorePath = path.join(targetDir, '.gitignore');
|
|
430
270
|
const ignoreEntry = '.obsidian/workspace.json';
|
|
431
|
-
|
|
271
|
+
|
|
432
272
|
try {
|
|
433
|
-
let
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
// 检查是否已经包含该条目
|
|
441
|
-
if (gitignoreContent.includes(ignoreEntry)) {
|
|
273
|
+
let content = fs.existsSync(gitignorePath)
|
|
274
|
+
? await fsAsync.readFile(gitignorePath, 'utf8')
|
|
275
|
+
: '';
|
|
276
|
+
|
|
277
|
+
if (content.includes(ignoreEntry)) {
|
|
442
278
|
Ec.info('✓ .gitignore 已包含 .obsidian/workspace.json');
|
|
443
279
|
return;
|
|
444
280
|
}
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
gitignoreContent += `${ignoreEntry}\n`;
|
|
452
|
-
|
|
453
|
-
// 写入 .gitignore 文件
|
|
454
|
-
await fsAsync.writeFile(gitignorePath, gitignoreContent, 'utf8');
|
|
281
|
+
|
|
282
|
+
if (content && !content.endsWith('\n')) content += '\n';
|
|
283
|
+
content += `${ignoreEntry}\n`;
|
|
284
|
+
await fsAsync.writeFile(gitignorePath, content, 'utf8');
|
|
455
285
|
Ec.info('✓ 已添加 .obsidian/workspace.json 到 .gitignore');
|
|
456
286
|
} catch (error) {
|
|
457
287
|
Ec.warn(`⚠ 更新 .gitignore 失败: ${error.message}`);
|
|
@@ -460,7 +290,6 @@ const _updateGitignore = async (targetDir) => {
|
|
|
460
290
|
|
|
461
291
|
module.exports = async () => {
|
|
462
292
|
try {
|
|
463
|
-
// 1. 解析 -d/-r 参数,获取目标目录
|
|
464
293
|
const dirArg = parseOptional('dir', 'd');
|
|
465
294
|
const removeArg = parseOptional('remove', 'r');
|
|
466
295
|
|
|
@@ -479,15 +308,12 @@ module.exports = async () => {
|
|
|
479
308
|
Ec.info(`📁 目标目录: ${targetDir.cyan}`);
|
|
480
309
|
Ec.info(`💻 操作系统: ${_getOsName().cyan}`);
|
|
481
310
|
|
|
482
|
-
// 2. 检查目标目录是否存在
|
|
483
311
|
if (!fs.existsSync(targetDir)) {
|
|
484
312
|
Ec.error(`❌ 目录不存在: ${targetDir}`);
|
|
485
313
|
process.exit(1);
|
|
486
314
|
}
|
|
487
315
|
|
|
488
|
-
|
|
489
|
-
const stat = fs.statSync(targetDir);
|
|
490
|
-
if (!stat.isDirectory()) {
|
|
316
|
+
if (!fs.statSync(targetDir).isDirectory()) {
|
|
491
317
|
Ec.error(`❌ 路径不是目录: ${targetDir}`);
|
|
492
318
|
process.exit(1);
|
|
493
319
|
}
|
|
@@ -498,11 +324,10 @@ module.exports = async () => {
|
|
|
498
324
|
process.exit(0);
|
|
499
325
|
}
|
|
500
326
|
|
|
501
|
-
//
|
|
327
|
+
// 检查 .obsidian 配置,不存在则初始化
|
|
502
328
|
const obsidianConfigPath = path.join(targetDir, '.obsidian');
|
|
503
329
|
if (!fs.existsSync(obsidianConfigPath)) {
|
|
504
330
|
Ec.warn(`⚠ 目录中不存在 .obsidian 配置,正在自动初始化...`);
|
|
505
|
-
|
|
506
331
|
const initSuccess = await _initObsidianConfig(targetDir);
|
|
507
332
|
if (!initSuccess) {
|
|
508
333
|
Ec.error('❌ 无法初始化 .obsidian 配置');
|
|
@@ -516,14 +341,9 @@ module.exports = async () => {
|
|
|
516
341
|
Ec.info('✓ 检测到 .obsidian 配置');
|
|
517
342
|
}
|
|
518
343
|
|
|
519
|
-
// 4.5. 更新 .gitignore,添加 .obsidian/workspace.json
|
|
520
344
|
await _updateGitignore(targetDir);
|
|
521
345
|
|
|
522
|
-
|
|
523
|
-
Ec.waiting('正在检查 Obsidian 安装状态...');
|
|
524
|
-
const obsidianInstalled = await _isObsidianInstalled();
|
|
525
|
-
|
|
526
|
-
if (!obsidianInstalled) {
|
|
346
|
+
if (!_isObsidianInstalled()) {
|
|
527
347
|
Ec.error('❌ 未检测到 Obsidian 安装');
|
|
528
348
|
console.log('');
|
|
529
349
|
Ec.error('请先安装 Obsidian:');
|
|
@@ -534,32 +354,28 @@ module.exports = async () => {
|
|
|
534
354
|
|
|
535
355
|
Ec.info('✓ Obsidian 已安装');
|
|
536
356
|
|
|
537
|
-
//
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
357
|
+
// 派生确定性 ID 并注册(自动替换旧随机 ID 记录)
|
|
358
|
+
Ec.waiting('正在注册 vault...');
|
|
359
|
+
const vaultId = await _registerVaultToObsidian(targetDir);
|
|
360
|
+
if (!vaultId) {
|
|
361
|
+
Ec.error('❌ Vault 注册失败,无法打开');
|
|
362
|
+
process.exit(1);
|
|
542
363
|
}
|
|
364
|
+
Ec.info(`✓ Vault 已注册 (ID: ${vaultId})`);
|
|
543
365
|
|
|
544
|
-
//
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
} else {
|
|
550
|
-
Ec.warn('⚠ Vault 注册失败,将尝试直接打开');
|
|
366
|
+
// 若 vault 正在运行,先关闭再重新打开
|
|
367
|
+
const isRunning = await _isVaultRunning(vaultId);
|
|
368
|
+
if (isRunning) {
|
|
369
|
+
Ec.waiting('检测到该 vault 正在运行,正在关闭以重新加载...');
|
|
370
|
+
await _closeVault(vaultId);
|
|
551
371
|
}
|
|
552
372
|
|
|
553
|
-
// 8. 使用 Obsidian 打开目录
|
|
554
373
|
Ec.waiting(`正在使用 Obsidian 打开: ${targetDir.cyan}...`);
|
|
555
|
-
|
|
556
374
|
try {
|
|
557
|
-
|
|
375
|
+
_openWithObsidian(vaultId);
|
|
558
376
|
console.log('');
|
|
559
|
-
Ec.info(
|
|
560
|
-
|
|
561
|
-
Ec.info(`💡 提示: 该 vault 现在会出现在 Obsidian 的本地仓库列表中`);
|
|
562
|
-
}
|
|
377
|
+
Ec.info('✅ 已成功打开 Obsidian');
|
|
378
|
+
Ec.info('💡 提示: 该 vault 现在会出现在 Obsidian 的本地仓库列表中');
|
|
563
379
|
console.log('');
|
|
564
380
|
} catch (error) {
|
|
565
381
|
console.log('');
|
|
@@ -569,10 +385,7 @@ module.exports = async () => {
|
|
|
569
385
|
console.log('');
|
|
570
386
|
}
|
|
571
387
|
|
|
572
|
-
|
|
573
|
-
setTimeout(() => {
|
|
574
|
-
process.exit(0);
|
|
575
|
-
}, 1000);
|
|
388
|
+
setTimeout(() => process.exit(0), 1000);
|
|
576
389
|
|
|
577
390
|
} catch (error) {
|
|
578
391
|
Ec.error(`❌ 执行失败: ${error.message}`);
|