im-nobsidian 0.3.1 → 0.4.1
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/dist/index.js +772 -250
- package/dist/index.js.map +1 -1
- package/package.json +2 -3
package/dist/index.js
CHANGED
|
@@ -2,11 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { createRequire } from "module";
|
|
5
|
-
import { Command as
|
|
5
|
+
import { Command as Command12 } from "commander";
|
|
6
6
|
|
|
7
7
|
// src/commands/init.ts
|
|
8
8
|
import { Command } from "commander";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
ConfigManager,
|
|
11
|
+
NOTION_TOKEN_PREFIX,
|
|
12
|
+
NotionClient,
|
|
13
|
+
notionIdFromInput
|
|
14
|
+
} from "@im-nobsidian/core";
|
|
10
15
|
import { input, confirm } from "@inquirer/prompts";
|
|
11
16
|
import ora from "ora";
|
|
12
17
|
import chalk2 from "chalk";
|
|
@@ -17,6 +22,7 @@ var icons = {
|
|
|
17
22
|
create: chalk.green("+"),
|
|
18
23
|
update: chalk.yellow("~"),
|
|
19
24
|
delete: chalk.red("-"),
|
|
25
|
+
move: chalk.cyan("\u2192"),
|
|
20
26
|
success: chalk.green("\u2713"),
|
|
21
27
|
fail: chalk.red("\u2715"),
|
|
22
28
|
conflict: chalk.magenta("!"),
|
|
@@ -42,31 +48,72 @@ function operationLabel(op) {
|
|
|
42
48
|
return chalk.yellow("updated");
|
|
43
49
|
case "delete":
|
|
44
50
|
return chalk.red("deleted");
|
|
51
|
+
case "move":
|
|
52
|
+
return chalk.cyan("moved");
|
|
45
53
|
}
|
|
46
54
|
}
|
|
47
|
-
function summary(created, updated, deleted) {
|
|
55
|
+
function summary(created, updated, deleted, moved = 0) {
|
|
48
56
|
const parts = [];
|
|
49
57
|
if (created > 0) parts.push(chalk.green(`${created} created`));
|
|
50
58
|
if (updated > 0) parts.push(chalk.yellow(`${updated} updated`));
|
|
59
|
+
if (moved > 0) parts.push(chalk.cyan(`${moved} moved`));
|
|
51
60
|
if (deleted > 0) parts.push(chalk.red(`${deleted} deleted`));
|
|
52
61
|
if (parts.length === 0) parts.push(chalk.dim("no changes"));
|
|
53
62
|
return parts.join(" ");
|
|
54
63
|
}
|
|
64
|
+
function completionHeader(label, failedCount) {
|
|
65
|
+
return failedCount > 0 ? header(chalk.yellow(`${label} finished with errors`)) : header(chalk.green(`${label} complete`));
|
|
66
|
+
}
|
|
67
|
+
function failedCountText(count) {
|
|
68
|
+
return count > 0 ? chalk.red(`${count} failed`) : chalk.dim("0 failed");
|
|
69
|
+
}
|
|
55
70
|
function duration(ms) {
|
|
56
71
|
return chalk.dim(`Done in ${(ms / 1e3).toFixed(1)}s`);
|
|
57
72
|
}
|
|
73
|
+
function localDateTime(iso) {
|
|
74
|
+
const date = new Date(iso);
|
|
75
|
+
if (Number.isNaN(date.getTime())) return iso;
|
|
76
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
77
|
+
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
|
|
78
|
+
}
|
|
79
|
+
var FULL_SCAN_REASON = {
|
|
80
|
+
first: "first pull",
|
|
81
|
+
forced: "--force",
|
|
82
|
+
"database-mode": "database mode",
|
|
83
|
+
"every-pull": "every pull",
|
|
84
|
+
due: "scheduled"
|
|
85
|
+
};
|
|
86
|
+
function remoteScanLines(scan) {
|
|
87
|
+
if (scan.kind === "full") {
|
|
88
|
+
return [dimText(scan.reason ? `Full scan (${FULL_SCAN_REASON[scan.reason]})` : "Full scan")];
|
|
89
|
+
}
|
|
90
|
+
const skipped = scan.skippedDatabases ?? 0;
|
|
91
|
+
const skippedText = skipped > 0 ? ` \xB7 ${skipped} unchanged ${skipped === 1 ? "database" : "databases"} skipped` : "";
|
|
92
|
+
const lines = [dimText(`Changes-only scan${skippedText}`)];
|
|
93
|
+
if (scan.deletionsDeferred) lines.push(remoteDeletionHint(scan));
|
|
94
|
+
return lines;
|
|
95
|
+
}
|
|
96
|
+
function remoteDeletionHint(scan) {
|
|
97
|
+
const when = scan.nextFullAt ? `the next full scan (${localDateTime(scan.nextFullAt)})` : "the next pull (full scan due)";
|
|
98
|
+
return `${dimText(`Deletions in Notion apply at ${when} \u2014 or run`)} ${chalk.cyan("nobsi pull --force")}`;
|
|
99
|
+
}
|
|
100
|
+
function lastFullScanText(lastFullScanAt) {
|
|
101
|
+
return lastFullScanAt ? localDateTime(lastFullScanAt) : chalk.yellow("never");
|
|
102
|
+
}
|
|
58
103
|
function progress(current, total) {
|
|
59
104
|
return chalk.dim(`[${current}/${total}]`);
|
|
60
105
|
}
|
|
61
106
|
function dimText(text) {
|
|
62
107
|
return chalk.dim(text);
|
|
63
108
|
}
|
|
64
|
-
function failedItem(path, error) {
|
|
65
|
-
|
|
109
|
+
function failedItem(path, error, direction) {
|
|
110
|
+
const where = `${direction ? `${direction} ` : ""}${path || "/"}`;
|
|
111
|
+
console.log(` ${icons.fail} ${where}: ${chalk.red(error)}`);
|
|
66
112
|
}
|
|
67
113
|
|
|
68
114
|
// src/commands/init.ts
|
|
69
|
-
var
|
|
115
|
+
var ROOT_PAGE_UNREADABLE = "\uB8E8\uD2B8 \uD398\uC774\uC9C0\uC5D0\uC11C Notion \uD398\uC774\uC9C0 ID(32\uC790\uB9AC)\uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4 \u2014 \uD398\uC774\uC9C0 \uC8FC\uC18C\uB098 ID \uB97C \uB123\uC73C\uC138\uC694.";
|
|
116
|
+
var initCommand = new Command("init").description("Im-Nobsidian \uCD08\uAE30 \uC124\uC815").option("--token <token>", "Notion Integration Token").option("--root-page-id <id>", "\uB8E8\uD2B8 \uD398\uC774\uC9C0 ID \uB610\uB294 \uD398\uC774\uC9C0 \uC8FC\uC18C").option("--non-interactive", "\uBE44\uB300\uD654\uD615 \uBAA8\uB4DC").action(async (options) => {
|
|
70
117
|
const cwd = process.cwd();
|
|
71
118
|
const configManager = new ConfigManager(cwd);
|
|
72
119
|
console.log(`
|
|
@@ -78,8 +125,14 @@ ${header(chalk2.cyan(" Im-Nobsidian Setup"))}`);
|
|
|
78
125
|
process.exitCode = 1;
|
|
79
126
|
return;
|
|
80
127
|
}
|
|
81
|
-
if (!options.token.startsWith(
|
|
82
|
-
console.error(
|
|
128
|
+
if (!options.token.startsWith(NOTION_TOKEN_PREFIX)) {
|
|
129
|
+
console.error(`\uD1A0\uD070\uC740 ${NOTION_TOKEN_PREFIX}\uC73C\uB85C \uC2DC\uC791\uD574\uC57C \uD569\uB2C8\uB2E4.`);
|
|
130
|
+
process.exitCode = 1;
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const rootPageId = notionIdFromInput(options.rootPageId);
|
|
134
|
+
if (rootPageId === null) {
|
|
135
|
+
console.error(ROOT_PAGE_UNREADABLE);
|
|
83
136
|
process.exitCode = 1;
|
|
84
137
|
return;
|
|
85
138
|
}
|
|
@@ -89,10 +142,9 @@ ${header(chalk2.cyan(" Im-Nobsidian Setup"))}`);
|
|
|
89
142
|
await client.search({ pageSize: 1, filter: { property: "object", value: "page" } });
|
|
90
143
|
spinner2.stop();
|
|
91
144
|
console.log(` ${icons.success} Token validated`);
|
|
92
|
-
await configManager.init({ token: options.token, rootPageId
|
|
145
|
+
await configManager.init({ token: options.token, rootPageId });
|
|
93
146
|
console.log(`
|
|
94
147
|
${icons.success} Config saved to ${dimText(configManager.configPath)}`);
|
|
95
|
-
console.log(` ${icons.success} State DB initialized`);
|
|
96
148
|
console.log(
|
|
97
149
|
`
|
|
98
150
|
${header(chalk2.green("Ready!"))} Run ${chalk2.cyan("nobsi sync")} to start syncing.`
|
|
@@ -114,7 +166,7 @@ ${header(chalk2.cyan(" Im-Nobsidian Setup"))}`);
|
|
|
114
166
|
}
|
|
115
167
|
const token = await input({
|
|
116
168
|
message: "Notion Integration Token:",
|
|
117
|
-
validate: (v) => v.startsWith(
|
|
169
|
+
validate: (v) => v.startsWith(NOTION_TOKEN_PREFIX) || `${NOTION_TOKEN_PREFIX}\uC73C\uB85C \uC2DC\uC791\uD558\uB294 \uD1A0\uD070\uC744 \uC785\uB825\uD558\uC138\uC694`
|
|
118
170
|
});
|
|
119
171
|
const spinner = ora("Notion \uC5F0\uACB0 \uD655\uC778 \uC911...").start();
|
|
120
172
|
try {
|
|
@@ -138,14 +190,15 @@ ${header(chalk2.cyan(" Im-Nobsidian Setup"))}`);
|
|
|
138
190
|
const title = extractPageTitle(page);
|
|
139
191
|
console.log(` ${dimText(`${i + 1}.`)} ${title}`);
|
|
140
192
|
}
|
|
141
|
-
const
|
|
142
|
-
message: "\uB8E8\uD2B8 \uD398\uC774\uC9C0 ID (\uC704\uC5D0\uC11C \uC120\uD0DD\uD558\uAC70\uB098 \uC9C1\uC811 \uC785\uB825):",
|
|
143
|
-
default: result.results[0]?.id
|
|
193
|
+
const rootPageInput = await input({
|
|
194
|
+
message: "\uB8E8\uD2B8 \uD398\uC774\uC9C0 ID \uB610\uB294 \uC8FC\uC18C (\uC704\uC5D0\uC11C \uC120\uD0DD\uD558\uAC70\uB098 \uC9C1\uC811 \uC785\uB825):",
|
|
195
|
+
default: result.results[0]?.id,
|
|
196
|
+
validate: (value) => notionIdFromInput(value) !== null || ROOT_PAGE_UNREADABLE
|
|
144
197
|
});
|
|
198
|
+
const rootPageId = notionIdFromInput(rootPageInput);
|
|
145
199
|
await configManager.init({ token, rootPageId });
|
|
146
200
|
console.log(`
|
|
147
201
|
${icons.success} Config saved to ${dimText(configManager.configPath)}`);
|
|
148
|
-
console.log(` ${icons.success} State DB initialized`);
|
|
149
202
|
console.log(
|
|
150
203
|
`
|
|
151
204
|
${header(chalk2.green("Ready!"))} Run ${chalk2.cyan("nobsi sync")} to start syncing.`
|
|
@@ -177,8 +230,51 @@ import {
|
|
|
177
230
|
SyncOrchestrator,
|
|
178
231
|
NodeVaultFS
|
|
179
232
|
} from "@im-nobsidian/core";
|
|
180
|
-
|
|
181
|
-
|
|
233
|
+
|
|
234
|
+
// src/utils/json-output.ts
|
|
235
|
+
import { setLogger } from "@im-nobsidian/core";
|
|
236
|
+
function enableJsonMode() {
|
|
237
|
+
const toStderr = (message, ...args) => console.error(message, ...args);
|
|
238
|
+
setLogger({ warn: toStderr, error: toStderr, info: toStderr, debug: () => {
|
|
239
|
+
} });
|
|
240
|
+
}
|
|
241
|
+
function pushJson(result) {
|
|
242
|
+
return {
|
|
243
|
+
created: result.created,
|
|
244
|
+
updated: result.updated,
|
|
245
|
+
deleted: result.deleted,
|
|
246
|
+
moved: result.moved,
|
|
247
|
+
churn: result.created + result.updated + result.deleted + result.moved,
|
|
248
|
+
failed: result.failed,
|
|
249
|
+
durationMs: result.duration
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
function pullJson(result) {
|
|
253
|
+
return {
|
|
254
|
+
created: result.created,
|
|
255
|
+
updated: result.updated,
|
|
256
|
+
deleted: result.deleted,
|
|
257
|
+
restored: result.restored,
|
|
258
|
+
churn: result.created + result.updated + result.deleted + result.restored,
|
|
259
|
+
conflicts: result.conflicts.length,
|
|
260
|
+
failed: result.failed,
|
|
261
|
+
writtenPaths: result.writtenPaths.length,
|
|
262
|
+
images: result.imageCount,
|
|
263
|
+
files: result.fileCount,
|
|
264
|
+
links: result.linkCount,
|
|
265
|
+
durationMs: result.duration,
|
|
266
|
+
remoteScan: result.remoteScan ?? null
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
function printJson(value) {
|
|
270
|
+
process.stdout.write(`${JSON.stringify(value)}
|
|
271
|
+
`);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// src/commands/push.ts
|
|
275
|
+
var pushCommand = new Command2("push").description("\uB85C\uCEEC \uBCC0\uACBD\uC0AC\uD56D\uC744 Notion\uC5D0 \uBC18\uC601").option("--dry-run", "\uC2E4\uC81C \uBC18\uC601 \uC5C6\uC774 \uBCC0\uACBD\uC0AC\uD56D\uB9CC \uD45C\uC2DC").option("-p, --path <paths...>", "\uD2B9\uC815 \uACBD\uB85C\uB9CC push").option("--json", "\uACB0\uACFC\uB97C JSON \uD55C \uC904\uB85C \uCD9C\uB825 (\uC790\uB3D9\uD654\uC6A9 \u2014 \uB85C\uADF8\uB294 stderr)").action(async (options) => {
|
|
276
|
+
const json = options.json === true;
|
|
277
|
+
if (json) enableJsonMode();
|
|
182
278
|
const cwd = process.cwd();
|
|
183
279
|
const configManager = new ConfigManager2(cwd);
|
|
184
280
|
const config = await configManager.load();
|
|
@@ -187,31 +283,40 @@ var pushCommand = new Command2("push").description("\uB85C\uCEEC \uBCC0\uACBD\uC
|
|
|
187
283
|
const client = NotionClient2.fromConfig(config);
|
|
188
284
|
const vaultFs = new NodeVaultFS(cwd, config.paths);
|
|
189
285
|
const orchestrator = new SyncOrchestrator(config, stateDb, client, vaultFs);
|
|
190
|
-
|
|
286
|
+
if (!json) {
|
|
287
|
+
console.log(`
|
|
191
288
|
${header(" Pushing to Notion...")}`);
|
|
192
|
-
|
|
193
|
-
|
|
289
|
+
if (options.dryRun) console.log(dimText(" (dry-run mode)"));
|
|
290
|
+
console.log("");
|
|
291
|
+
}
|
|
194
292
|
const result = await orchestrator.push({
|
|
195
293
|
dryRun: options.dryRun,
|
|
196
294
|
paths: options.path,
|
|
197
|
-
onProgress: (current, total, item) => {
|
|
295
|
+
onProgress: json ? void 0 : (current, total, item) => {
|
|
198
296
|
const icon = operationIcon(item.operation);
|
|
199
297
|
const label = operationLabel(item.operation);
|
|
200
298
|
const prog = progress(current, total);
|
|
201
299
|
console.log(` ${icon} ${item.path} ${prog} ${label}`);
|
|
202
300
|
}
|
|
203
301
|
});
|
|
302
|
+
if (json) {
|
|
303
|
+
printJson(pushJson(result));
|
|
304
|
+
if (result.failed.length > 0) process.exitCode = 1;
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
const failedCount = result.failed.length;
|
|
204
308
|
console.log(`
|
|
205
|
-
${
|
|
309
|
+
${completionHeader("Push", failedCount)}`);
|
|
206
310
|
console.log(
|
|
207
|
-
` ${summary(result.created, result.updated, result.deleted)} ${
|
|
311
|
+
` ${summary(result.created, result.updated, result.deleted, result.moved)} ${failedCountText(failedCount)}`
|
|
208
312
|
);
|
|
209
313
|
console.log(` ${duration(result.duration)}`);
|
|
210
|
-
if (
|
|
314
|
+
if (failedCount > 0) {
|
|
211
315
|
console.log("");
|
|
212
316
|
for (const f of result.failed) {
|
|
213
317
|
failedItem(f.path, f.error);
|
|
214
318
|
}
|
|
319
|
+
process.exitCode = 1;
|
|
215
320
|
}
|
|
216
321
|
} finally {
|
|
217
322
|
stateDb.close();
|
|
@@ -219,7 +324,7 @@ ${header(" Pushing to Notion...")}`);
|
|
|
219
324
|
});
|
|
220
325
|
|
|
221
326
|
// src/commands/pull.ts
|
|
222
|
-
import { Command as Command3 } from "commander";
|
|
327
|
+
import { Command as Command3, Option } from "commander";
|
|
223
328
|
import {
|
|
224
329
|
ConfigManager as ConfigManager3,
|
|
225
330
|
StateDB as StateDB2,
|
|
@@ -227,11 +332,18 @@ import {
|
|
|
227
332
|
SyncOrchestrator as SyncOrchestrator2,
|
|
228
333
|
NodeVaultFS as NodeVaultFS2
|
|
229
334
|
} from "@im-nobsidian/core";
|
|
230
|
-
import
|
|
335
|
+
import chalk3 from "chalk";
|
|
231
336
|
var pullCommand = new Command3("pull").description("Notion \uBCC0\uACBD\uC0AC\uD56D\uC744 \uB85C\uCEEC\uC5D0 \uBC18\uC601").option("--dry-run", "\uC2E4\uC81C \uBC18\uC601 \uC5C6\uC774 \uBCC0\uACBD\uC0AC\uD56D\uB9CC \uD45C\uC2DC").option("-p, --path <paths...>", "\uD2B9\uC815 \uACBD\uB85C\uB9CC pull").option(
|
|
232
337
|
"--force",
|
|
233
338
|
"\uC99D\uBD84 \uAC10\uC9C0\uB97C \uAC74\uB108\uB6F0\uACE0 \uC804\uCCB4 \uC2A4\uCE94 (search \uC778\uB371\uC2F1 \uC9C0\uC5F0\uC73C\uB85C \uB204\uB77D\uB41C \uC2E0\uADDC \uD398\uC774\uC9C0\xB7DB \uBCF5\uAD6C)"
|
|
234
|
-
).
|
|
339
|
+
).addOption(
|
|
340
|
+
new Option(
|
|
341
|
+
"--rerender",
|
|
342
|
+
"Notion \uC5D0\uC11C \uBC14\uB00C\uC9C0 \uC54A\uC740 \uBC1B\uC544 \uB454 \uB178\uD2B8\uB3C4 \uC9C0\uAE08 \uBCC0\uD658\uAE30\uB85C \uB2E4\uC2DC \uADF8\uB9BC (\uBCFC\uD2B8\uC5D0\uC11C \uACE0\uCE5C \uB178\uD2B8\uB294 \uADF8\uB300\uB85C)"
|
|
343
|
+
).conflicts("dryRun")
|
|
344
|
+
).option("--json", "\uACB0\uACFC\uB97C JSON \uD55C \uC904\uB85C \uCD9C\uB825 (\uC790\uB3D9\uD654\uC6A9 \u2014 \uB85C\uADF8\uB294 stderr)").action(async (options) => {
|
|
345
|
+
const json = options.json === true;
|
|
346
|
+
if (json) enableJsonMode();
|
|
235
347
|
const cwd = process.cwd();
|
|
236
348
|
const configManager = new ConfigManager3(cwd);
|
|
237
349
|
const config = await configManager.load();
|
|
@@ -240,50 +352,69 @@ var pullCommand = new Command3("pull").description("Notion \uBCC0\uACBD\uC0AC\uD
|
|
|
240
352
|
const client = NotionClient3.fromConfig(config);
|
|
241
353
|
const vaultFs = new NodeVaultFS2(cwd, config.paths);
|
|
242
354
|
const orchestrator = new SyncOrchestrator2(config, stateDb, client, vaultFs);
|
|
243
|
-
|
|
355
|
+
if (!json) {
|
|
356
|
+
console.log(`
|
|
244
357
|
${header(" Pulling from Notion...")}`);
|
|
245
|
-
|
|
246
|
-
|
|
358
|
+
if (options.dryRun) console.log(dimText(" (dry-run mode)"));
|
|
359
|
+
console.log("");
|
|
360
|
+
}
|
|
247
361
|
const result = await orchestrator.pull({
|
|
248
362
|
dryRun: options.dryRun,
|
|
249
363
|
paths: options.path,
|
|
250
364
|
force: options.force,
|
|
251
|
-
|
|
365
|
+
rerender: options.rerender,
|
|
366
|
+
onProgress: json ? void 0 : (current, total, item) => {
|
|
367
|
+
if (item.unchanged) return;
|
|
252
368
|
const icon = operationIcon(item.operation);
|
|
253
369
|
const label = operationLabel(item.operation);
|
|
254
370
|
const prog = progress(current, total);
|
|
255
371
|
console.log(` ${icon} ${item.path} ${prog} ${label}`);
|
|
256
372
|
}
|
|
257
373
|
});
|
|
374
|
+
if (json) {
|
|
375
|
+
printJson(pullJson(result));
|
|
376
|
+
if (result.failed.length > 0) process.exitCode = 1;
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
258
379
|
if (result.imageCount > 0) {
|
|
259
380
|
console.log(
|
|
260
381
|
`
|
|
261
|
-
${icons.success} ${result.imageCount} images \u2192 ${dimText(
|
|
382
|
+
${icons.success} ${result.imageCount} images \u2192 ${dimText(`${config.paths.attachments}/`)}`
|
|
262
383
|
);
|
|
263
384
|
}
|
|
264
385
|
if (result.fileCount > 0) {
|
|
265
|
-
console.log(
|
|
386
|
+
console.log(
|
|
387
|
+
` ${icons.success} ${result.fileCount} files \u2192 ${dimText(`${config.paths.attachments}/`)}`
|
|
388
|
+
);
|
|
266
389
|
}
|
|
267
390
|
if (result.linkCount > 0) {
|
|
268
391
|
console.log(` ${icons.success} ${result.linkCount} links resolved`);
|
|
269
392
|
}
|
|
393
|
+
const failedCount = result.failed.length;
|
|
270
394
|
console.log(`
|
|
271
|
-
${
|
|
395
|
+
${completionHeader("Pull", failedCount)}`);
|
|
272
396
|
console.log(
|
|
273
|
-
` ${summary(result.created, result.updated, result.deleted)} ${
|
|
397
|
+
` ${summary(result.created, result.updated, result.deleted)} ${failedCountText(failedCount)}`
|
|
274
398
|
);
|
|
399
|
+
if (result.restored > 0) {
|
|
400
|
+
console.log(` ${icons.success} ${chalk3.yellow(`${result.restored} restored`)}`);
|
|
401
|
+
}
|
|
275
402
|
console.log(` ${duration(result.duration)}`);
|
|
403
|
+
if (result.remoteScan) {
|
|
404
|
+
for (const line of remoteScanLines(result.remoteScan)) console.log(` ${line}`);
|
|
405
|
+
}
|
|
276
406
|
if (result.conflicts.length > 0) {
|
|
277
407
|
console.log(
|
|
278
408
|
`
|
|
279
|
-
${icons.conflict} ${
|
|
409
|
+
${icons.conflict} ${chalk3.magenta(`${result.conflicts.length} conflicts`)} \u2014 run ${chalk3.cyan("nobsi resolve")}`
|
|
280
410
|
);
|
|
281
411
|
}
|
|
282
|
-
if (
|
|
412
|
+
if (failedCount > 0) {
|
|
283
413
|
console.log("");
|
|
284
414
|
for (const f of result.failed) {
|
|
285
415
|
failedItem(f.path, f.error);
|
|
286
416
|
}
|
|
417
|
+
process.exitCode = 1;
|
|
287
418
|
}
|
|
288
419
|
} finally {
|
|
289
420
|
stateDb.close();
|
|
@@ -299,8 +430,12 @@ import {
|
|
|
299
430
|
SyncOrchestrator as SyncOrchestrator3,
|
|
300
431
|
NodeVaultFS as NodeVaultFS3
|
|
301
432
|
} from "@im-nobsidian/core";
|
|
302
|
-
import
|
|
303
|
-
var syncCommand = new Command4("sync").description("\uC591\uBC29\uD5A5 \uB3D9\uAE30\uD654 (Pull \u2192 Push)").option("--dry-run", "\uC2E4\uC81C \uBC18\uC601 \uC5C6\uC774 \uBCC0\uACBD\uC0AC\uD56D\uB9CC \uD45C\uC2DC").action(async (options) => {
|
|
433
|
+
import chalk4 from "chalk";
|
|
434
|
+
var syncCommand = new Command4("sync").description("\uC591\uBC29\uD5A5 \uB3D9\uAE30\uD654 (Pull \u2192 Push)").option("--dry-run", "\uC2E4\uC81C \uBC18\uC601 \uC5C6\uC774 \uBCC0\uACBD\uC0AC\uD56D\uB9CC \uD45C\uC2DC").option("--json", "\uACB0\uACFC\uB97C JSON \uD55C \uC904\uB85C \uCD9C\uB825 (\uC790\uB3D9\uD654\uC6A9 \u2014 \uB85C\uADF8\uB294 stderr)").action(async (options) => {
|
|
435
|
+
const json = options.json === true;
|
|
436
|
+
if (json) enableJsonMode();
|
|
437
|
+
const log = json ? () => {
|
|
438
|
+
} : (line) => console.log(line);
|
|
304
439
|
const cwd = process.cwd();
|
|
305
440
|
const configManager = new ConfigManager4(cwd);
|
|
306
441
|
const config = await configManager.load();
|
|
@@ -309,52 +444,77 @@ var syncCommand = new Command4("sync").description("\uC591\uBC29\uD5A5 \uB3D9\uA
|
|
|
309
444
|
const client = NotionClient4.fromConfig(config);
|
|
310
445
|
const vaultFs = new NodeVaultFS3(cwd, config.paths);
|
|
311
446
|
const orchestrator = new SyncOrchestrator3(config, stateDb, client, vaultFs);
|
|
312
|
-
|
|
447
|
+
log(`
|
|
313
448
|
${header(" Bidirectional Sync")}`);
|
|
314
|
-
|
|
315
|
-
if (options.dryRun)
|
|
316
|
-
|
|
317
|
-
${header(
|
|
318
|
-
|
|
449
|
+
log(` ${separator()}`);
|
|
450
|
+
if (options.dryRun) log(dimText(" (dry-run mode)"));
|
|
451
|
+
log(`
|
|
452
|
+
${header(chalk4.blue("\u25BC Pull"))} ${dimText("(Notion \u2192 Obsidian)")}`);
|
|
453
|
+
let pushShown = false;
|
|
454
|
+
const showPush = () => {
|
|
455
|
+
if (pushShown) return;
|
|
456
|
+
pushShown = true;
|
|
457
|
+
log(`
|
|
458
|
+
${header(chalk4.magenta("\u25B2 Push"))} ${dimText("(Obsidian \u2192 Notion)")}`);
|
|
459
|
+
};
|
|
460
|
+
const result = await orchestrator.sync({
|
|
319
461
|
dryRun: options.dryRun,
|
|
320
462
|
onProgress: (_current, _total, item) => {
|
|
463
|
+
if (item.direction === "push") showPush();
|
|
464
|
+
if (item.unchanged) return;
|
|
321
465
|
const icon = operationIcon(item.operation);
|
|
322
466
|
const label = operationLabel(item.operation);
|
|
323
|
-
|
|
467
|
+
log(` ${icon} ${item.path} ${label}`);
|
|
324
468
|
}
|
|
325
469
|
});
|
|
470
|
+
showPush();
|
|
471
|
+
const { pull: pullResult, push: pushResult } = result;
|
|
472
|
+
if (json) {
|
|
473
|
+
const pull = pullJson(pullResult);
|
|
474
|
+
const push = pushJson(pushResult);
|
|
475
|
+
printJson({
|
|
476
|
+
pull,
|
|
477
|
+
push,
|
|
478
|
+
churn: pull.churn + push.churn,
|
|
479
|
+
conflicts: pull.conflicts,
|
|
480
|
+
durationMs: result.duration
|
|
481
|
+
});
|
|
482
|
+
if (pullResult.failed.length + pushResult.failed.length > 0) process.exitCode = 1;
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
const pullFailed = pullResult.failed.length;
|
|
486
|
+
const pushFailed = pushResult.failed.length;
|
|
487
|
+
console.log(`
|
|
488
|
+
${completionHeader("Sync", pullFailed + pushFailed)}`);
|
|
489
|
+
console.log(
|
|
490
|
+
` ${chalk4.blue("Pull:")} ${summary(pullResult.created, pullResult.updated, pullResult.deleted)}` + // 사라졌던 파일이 되살아난 경우에만 덧붙인다 — 평상시 요약을 어지럽히지 않는다.
|
|
491
|
+
(pullResult.restored > 0 ? ` ${chalk4.yellow(`${pullResult.restored} restored`)}` : "") + (pullFailed > 0 ? ` ${chalk4.red(`${pullFailed} failed`)}` : "")
|
|
492
|
+
);
|
|
326
493
|
if (pullResult.imageCount > 0) {
|
|
327
494
|
console.log(` ${icons.success} ${pullResult.imageCount} images downloaded`);
|
|
328
495
|
}
|
|
329
496
|
if (pullResult.linkCount > 0) {
|
|
330
497
|
console.log(` ${icons.success} ${pullResult.linkCount} links resolved`);
|
|
331
498
|
}
|
|
332
|
-
console.log(`
|
|
333
|
-
${header(chalk5.magenta("\u25B2 Push"))} ${dimText("(Obsidian \u2192 Notion)")}`);
|
|
334
|
-
const pushResult = await orchestrator.push({
|
|
335
|
-
dryRun: options.dryRun,
|
|
336
|
-
onProgress: (_current, _total, item) => {
|
|
337
|
-
const icon = operationIcon(item.operation);
|
|
338
|
-
const label = operationLabel(item.operation);
|
|
339
|
-
console.log(` ${icon} ${item.path} ${label}`);
|
|
340
|
-
}
|
|
341
|
-
});
|
|
342
|
-
const totalDuration = pullResult.duration + pushResult.duration;
|
|
343
|
-
console.log(`
|
|
344
|
-
${header(chalk5.green("Sync complete"))}`);
|
|
345
|
-
console.log(
|
|
346
|
-
` ${chalk5.blue("Pull:")} ${summary(pullResult.created, pullResult.updated, pullResult.deleted)}`
|
|
347
|
-
);
|
|
348
499
|
console.log(
|
|
349
|
-
` ${
|
|
500
|
+
` ${chalk4.magenta("Push:")} ${summary(pushResult.created, pushResult.updated, pushResult.deleted, pushResult.moved)}` + (pushFailed > 0 ? ` ${chalk4.red(`${pushFailed} failed`)}` : "")
|
|
350
501
|
);
|
|
351
|
-
console.log(` ${duration(
|
|
352
|
-
if (pullResult.
|
|
502
|
+
console.log(` ${duration(result.duration)}`);
|
|
503
|
+
if (pullResult.remoteScan) {
|
|
504
|
+
for (const line of remoteScanLines(pullResult.remoteScan)) console.log(` ${line}`);
|
|
505
|
+
}
|
|
506
|
+
if (result.conflicts.length > 0) {
|
|
353
507
|
console.log(
|
|
354
508
|
`
|
|
355
|
-
${icons.conflict} ${
|
|
509
|
+
${icons.conflict} ${chalk4.magenta(`${result.conflicts.length} conflicts`)} \u2014 run ${chalk4.cyan("nobsi resolve")}`
|
|
356
510
|
);
|
|
357
511
|
}
|
|
512
|
+
if (pullFailed + pushFailed > 0) {
|
|
513
|
+
console.log("");
|
|
514
|
+
for (const f of pullResult.failed) failedItem(f.path, f.error, "\u25BC");
|
|
515
|
+
for (const f of pushResult.failed) failedItem(f.path, f.error, "\u25B2");
|
|
516
|
+
process.exitCode = 1;
|
|
517
|
+
}
|
|
358
518
|
} finally {
|
|
359
519
|
stateDb.close();
|
|
360
520
|
}
|
|
@@ -367,9 +527,10 @@ import {
|
|
|
367
527
|
StateDB as StateDB4,
|
|
368
528
|
NotionClient as NotionClient5,
|
|
369
529
|
SyncOrchestrator as SyncOrchestrator4,
|
|
370
|
-
NodeVaultFS as NodeVaultFS4
|
|
530
|
+
NodeVaultFS as NodeVaultFS4,
|
|
531
|
+
isRemoteDeletion
|
|
371
532
|
} from "@im-nobsidian/core";
|
|
372
|
-
import
|
|
533
|
+
import chalk5 from "chalk";
|
|
373
534
|
var statusCommand = new Command5("status").description("\uB3D9\uAE30\uD654 \uC0C1\uD0DC \uD655\uC778 (--full: \uC6D0\uACA9 \uBCC0\uACBD \uD3EC\uD568)").option("--full", "Notion \uC6D0\uACA9 \uBCC0\uACBD\uAE4C\uC9C0 \uC591\uBC29\uD5A5 \uD655\uC778 (API \uD638\uCD9C, \uB290\uB9BC)").action(async (opts) => {
|
|
374
535
|
const cwd = process.cwd();
|
|
375
536
|
const configManager = new ConfigManager5(cwd);
|
|
@@ -388,50 +549,92 @@ ${header(" Sync Status")}`);
|
|
|
388
549
|
console.log(` ${dimText("Root page:")} ${rootId.slice(0, 8)}...`);
|
|
389
550
|
console.log(` ${dimText("Direction:")} ${direction}`);
|
|
390
551
|
if (status.lastSyncAt) {
|
|
391
|
-
|
|
392
|
-
const iso = date.getFullYear() + "-" + String(date.getMonth() + 1).padStart(2, "0") + "-" + String(date.getDate()).padStart(2, "0") + " " + String(date.getHours()).padStart(2, "0") + ":" + String(date.getMinutes()).padStart(2, "0") + ":" + String(date.getSeconds()).padStart(2, "0");
|
|
393
|
-
console.log(` ${dimText("Last sync:")} ${iso}`);
|
|
552
|
+
console.log(` ${dimText("Last sync:")} ${localDateTime(status.lastSyncAt)}`);
|
|
394
553
|
} else {
|
|
395
|
-
console.log(` ${dimText("Last sync:")} ${
|
|
554
|
+
console.log(` ${dimText("Last sync:")} ${chalk5.yellow("never")}`);
|
|
396
555
|
}
|
|
556
|
+
console.log(` ${dimText("Full scan:")} ${lastFullScanText(status.lastFullScanAt)}`);
|
|
397
557
|
const allRecords = stateDb.getAll();
|
|
398
558
|
const total = allRecords.length;
|
|
399
|
-
const synced = allRecords.filter((r) => r.status === "synced").length;
|
|
400
559
|
const conflictCount = status.conflictRecords.length;
|
|
401
560
|
const createdChanges = status.localChanges.filter((c) => c.type === "created");
|
|
402
561
|
const modifiedChanges = status.localChanges.filter((c) => c.type === "modified");
|
|
562
|
+
const deletedChanges = status.localChanges.filter((c) => c.type === "deleted");
|
|
563
|
+
const movedChanges = status.localChanges.filter((c) => c.type === "moved");
|
|
564
|
+
const dirtyPaths = /* @__PURE__ */ new Set([
|
|
565
|
+
...modifiedChanges.map((c) => c.path),
|
|
566
|
+
...deletedChanges.map((c) => c.path),
|
|
567
|
+
...movedChanges.flatMap((c) => [c.path, c.movedFrom ?? c.path]),
|
|
568
|
+
...status.conflictRecords.map((r) => r.obsidianPath)
|
|
569
|
+
]);
|
|
570
|
+
const synced = allRecords.filter(
|
|
571
|
+
(r) => r.status === "synced" && !dirtyPaths.has(r.obsidianPath)
|
|
572
|
+
).length;
|
|
403
573
|
const remoteCreated = status.remoteChanges.filter((c) => c.type === "created");
|
|
404
574
|
const remoteModified = status.remoteChanges.filter((c) => c.type === "modified");
|
|
405
575
|
const remoteDeleted = status.remoteChanges.filter((c) => c.type === "deleted");
|
|
576
|
+
const remoteMoved = status.remoteChanges.filter((c) => c.type === "moved");
|
|
577
|
+
const { folderMoves } = status;
|
|
406
578
|
console.log(`
|
|
407
579
|
${header(`Tracked files: ${total}`)}`);
|
|
408
|
-
console.log(` ${icons.synced} ${
|
|
580
|
+
console.log(` ${icons.synced} ${chalk5.green("synced")} ${synced}`);
|
|
409
581
|
if (modifiedChanges.length > 0) {
|
|
410
|
-
console.log(` ${icons.modified} ${
|
|
582
|
+
console.log(` ${icons.modified} ${chalk5.yellow("modified")} ${modifiedChanges.length}`);
|
|
411
583
|
}
|
|
412
|
-
if (
|
|
413
|
-
console.log(` ${icons.
|
|
584
|
+
if (movedChanges.length > 0) {
|
|
585
|
+
console.log(` ${icons.move} ${chalk5.cyan("moved")} ${movedChanges.length}`);
|
|
586
|
+
}
|
|
587
|
+
if (deletedChanges.length > 0) {
|
|
588
|
+
console.log(` ${chalk5.red("-")} ${chalk5.red("deleted")} ${deletedChanges.length}`);
|
|
414
589
|
}
|
|
415
590
|
if (conflictCount > 0) {
|
|
416
|
-
console.log(` ${icons.conflictDot} ${
|
|
591
|
+
console.log(` ${icons.conflictDot} ${chalk5.magenta("conflict")} ${conflictCount}`);
|
|
592
|
+
}
|
|
593
|
+
if (createdChanges.length > 0) {
|
|
594
|
+
console.log(
|
|
595
|
+
` ${icons.newFile} ${chalk5.cyan("new")} ${createdChanges.length} ${dimText("(untracked)")}`
|
|
596
|
+
);
|
|
417
597
|
}
|
|
418
598
|
if (modifiedChanges.length > 0) {
|
|
419
599
|
console.log(`
|
|
420
600
|
${header("Modified files:")}`);
|
|
421
601
|
for (const c of modifiedChanges.slice(0, 10)) {
|
|
422
602
|
console.log(
|
|
423
|
-
` ${
|
|
603
|
+
` ${chalk5.yellow("~")} ${c.path.padEnd(30)} ${dimText("(local changed)")}`
|
|
424
604
|
);
|
|
425
605
|
}
|
|
426
606
|
if (modifiedChanges.length > 10) {
|
|
427
607
|
console.log(dimText(` ... and ${modifiedChanges.length - 10} more`));
|
|
428
608
|
}
|
|
429
609
|
}
|
|
610
|
+
if (folderMoves.length + movedChanges.length > 0) {
|
|
611
|
+
console.log(`
|
|
612
|
+
${header("Moved:")}`);
|
|
613
|
+
for (const move of folderMoves) {
|
|
614
|
+
console.log(` ${icons.move} ${move.from}/ \u2192 ${move.to}/ ${dimText("(folder)")}`);
|
|
615
|
+
}
|
|
616
|
+
for (const c of movedChanges.slice(0, 10)) {
|
|
617
|
+
console.log(` ${icons.move} ${c.movedFrom ?? "?"} \u2192 ${c.path}`);
|
|
618
|
+
}
|
|
619
|
+
if (movedChanges.length > 10) {
|
|
620
|
+
console.log(dimText(` ... and ${movedChanges.length - 10} more`));
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
if (deletedChanges.length > 0) {
|
|
624
|
+
console.log(`
|
|
625
|
+
${header("Deleted files:")}`);
|
|
626
|
+
for (const c of deletedChanges.slice(0, 10)) {
|
|
627
|
+
console.log(` ${chalk5.red("-")} ${c.path}`);
|
|
628
|
+
}
|
|
629
|
+
if (deletedChanges.length > 10) {
|
|
630
|
+
console.log(dimText(` ... and ${deletedChanges.length - 10} more`));
|
|
631
|
+
}
|
|
632
|
+
}
|
|
430
633
|
if (createdChanges.length > 0) {
|
|
431
634
|
console.log(`
|
|
432
635
|
${header("New files:")}`);
|
|
433
636
|
for (const c of createdChanges.slice(0, 10)) {
|
|
434
|
-
console.log(` ${
|
|
637
|
+
console.log(` ${chalk5.cyan("+")} ${c.path.padEnd(30)} ${dimText("(untracked)")}`);
|
|
435
638
|
}
|
|
436
639
|
if (createdChanges.length > 10) {
|
|
437
640
|
console.log(dimText(` ... and ${createdChanges.length - 10} more`));
|
|
@@ -441,19 +644,25 @@ ${header(" Sync Status")}`);
|
|
|
441
644
|
console.log(`
|
|
442
645
|
${header(`Remote changes (Notion): ${status.remoteChanges.length}`)}`);
|
|
443
646
|
if (remoteCreated.length > 0) {
|
|
444
|
-
console.log(` ${
|
|
647
|
+
console.log(` ${chalk5.cyan("+")} ${chalk5.cyan("new")} ${remoteCreated.length}`);
|
|
445
648
|
}
|
|
446
649
|
if (remoteModified.length > 0) {
|
|
447
650
|
console.log(
|
|
448
|
-
` ${
|
|
651
|
+
` ${chalk5.yellow("~")} ${chalk5.yellow("modified")} ${remoteModified.length}`
|
|
449
652
|
);
|
|
450
653
|
}
|
|
654
|
+
if (remoteMoved.length > 0) {
|
|
655
|
+
console.log(` ${icons.move} ${chalk5.cyan("moved")} ${remoteMoved.length}`);
|
|
656
|
+
}
|
|
451
657
|
if (remoteDeleted.length > 0) {
|
|
452
|
-
console.log(` ${
|
|
658
|
+
console.log(` ${chalk5.red("-")} ${chalk5.red("deleted")} ${remoteDeleted.length}`);
|
|
453
659
|
}
|
|
454
660
|
for (const c of status.remoteChanges.slice(0, 10)) {
|
|
455
|
-
const icon = c.type === "created" ?
|
|
456
|
-
|
|
661
|
+
const icon = c.type === "created" ? chalk5.cyan("+") : c.type === "modified" ? chalk5.yellow("~") : c.type === "moved" ? icons.move : chalk5.red("-");
|
|
662
|
+
const name = c.path ?? (c.title ? `${c.title} ${dimText("(new page)")}` : void 0);
|
|
663
|
+
console.log(
|
|
664
|
+
` ${icon} ${name ?? `${c.pageId.slice(0, 8)}...`} ${dimText(`(${c.type})`)}`
|
|
665
|
+
);
|
|
457
666
|
}
|
|
458
667
|
if (status.remoteChanges.length > 10) {
|
|
459
668
|
console.log(dimText(` ... and ${status.remoteChanges.length - 10} more`));
|
|
@@ -461,33 +670,43 @@ ${header(" Sync Status")}`);
|
|
|
461
670
|
}
|
|
462
671
|
if (conflictCount > 0) {
|
|
463
672
|
console.log(`
|
|
464
|
-
${header(
|
|
673
|
+
${header(chalk5.magenta("Conflicts:"))}`);
|
|
674
|
+
const remoteDeleted2 = new Set(
|
|
675
|
+
status.conflicts.filter(isRemoteDeletion).map((c) => c.syncRecord.obsidianPath)
|
|
676
|
+
);
|
|
465
677
|
for (const r of status.conflictRecords) {
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
);
|
|
678
|
+
const reason = remoteDeleted2.has(r.obsidianPath) ? "(deleted in Notion, local edits not pushed)" : "(both sides changed)";
|
|
679
|
+
console.log(` ${icons.conflict} ${r.obsidianPath.padEnd(30)} ${dimText(reason)}`);
|
|
469
680
|
}
|
|
470
681
|
}
|
|
471
|
-
const hasChanges = status.localChanges.length > 0 || status.remoteChanges.length > 0 || conflictCount > 0;
|
|
682
|
+
const hasChanges = status.localChanges.length > 0 || folderMoves.length > 0 || status.remoteChanges.length > 0 || conflictCount > 0;
|
|
472
683
|
if (hasChanges) {
|
|
473
684
|
console.log("");
|
|
474
685
|
if (conflictCount > 0) {
|
|
475
686
|
console.log(
|
|
476
|
-
` ${dimText("Run")} ${
|
|
687
|
+
` ${dimText("Run")} ${chalk5.cyan("nobsi resolve")} ${dimText("to resolve conflicts")}`
|
|
477
688
|
);
|
|
478
689
|
}
|
|
479
690
|
console.log(
|
|
480
|
-
` ${dimText("Run")} ${
|
|
691
|
+
` ${dimText("Run")} ${chalk5.cyan("nobsi sync")} ${dimText("to push/pull changes")}`
|
|
481
692
|
);
|
|
693
|
+
if (modifiedChanges.length + deletedChanges.length > 0) {
|
|
694
|
+
console.log(
|
|
695
|
+
` ${dimText("Run")} ${chalk5.cyan("nobsi discard <path>")} ${dimText("to discard local edits")}`
|
|
696
|
+
);
|
|
697
|
+
}
|
|
482
698
|
} else {
|
|
483
699
|
console.log(`
|
|
484
|
-
${
|
|
700
|
+
${chalk5.green("Everything up to date")} \u2713`);
|
|
485
701
|
}
|
|
486
702
|
if (!opts.full) {
|
|
487
703
|
console.log(
|
|
488
704
|
`
|
|
489
|
-
${dimText("Run")} ${
|
|
705
|
+
${dimText("Run")} ${chalk5.cyan("nobsi status --full")} ${dimText("to check Notion remote changes")}`
|
|
490
706
|
);
|
|
707
|
+
} else if (status.remoteScan?.deletionsDeferred) {
|
|
708
|
+
console.log(`
|
|
709
|
+
${remoteDeletionHint(status.remoteScan)}`);
|
|
491
710
|
}
|
|
492
711
|
} finally {
|
|
493
712
|
stateDb.close();
|
|
@@ -501,10 +720,35 @@ import {
|
|
|
501
720
|
StateDB as StateDB5,
|
|
502
721
|
NotionClient as NotionClient6,
|
|
503
722
|
SyncOrchestrator as SyncOrchestrator5,
|
|
504
|
-
NodeVaultFS as NodeVaultFS5
|
|
723
|
+
NodeVaultFS as NodeVaultFS5,
|
|
724
|
+
formatUnifiedDiff,
|
|
725
|
+
lineDiff,
|
|
726
|
+
matchesPathScope
|
|
505
727
|
} from "@im-nobsidian/core";
|
|
506
|
-
|
|
507
|
-
|
|
728
|
+
|
|
729
|
+
// src/utils/diff-output.ts
|
|
730
|
+
import { DIFF_SIGN } from "@im-nobsidian/core";
|
|
731
|
+
var BOLD = "\x1B[1m";
|
|
732
|
+
var RED = "\x1B[31m";
|
|
733
|
+
var GREEN = "\x1B[32m";
|
|
734
|
+
var CYAN = "\x1B[36m";
|
|
735
|
+
var RESET = "\x1B[0m";
|
|
736
|
+
var LINE_COLOR = {
|
|
737
|
+
[DIFF_SIGN.added]: GREEN,
|
|
738
|
+
[DIFF_SIGN.removed]: RED,
|
|
739
|
+
"@": CYAN
|
|
740
|
+
};
|
|
741
|
+
function printUnifiedDiff(lines, color) {
|
|
742
|
+
lines.forEach((line, index) => {
|
|
743
|
+
const code = index < 2 ? BOLD : LINE_COLOR[line.charAt(0)];
|
|
744
|
+
console.log(color && code ? `${code}${line}${RESET}` : line);
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// src/commands/diff.ts
|
|
749
|
+
var diffCommand = new Command6("diff").description(
|
|
750
|
+
"\uB85C\uCEEC \uBCC0\uACBD\uC0AC\uD56D \uD45C\uC2DC (\uAE30\uBCF8: \uB9C8\uC9C0\uB9C9 \uB3D9\uAE30\uD654 \uC2DC\uC810\uACFC \uBE44\uAD50, --remote: Notion \uD604\uC7AC\uBCF8\uACFC \uBE44\uAD50)"
|
|
751
|
+
).argument("[path]", "\uD2B9\uC815 \uD30C\uC77C \uACBD\uB85C (\uC0DD\uB7B5 \uC2DC \uBCC0\uACBD\uB41C \uBAA8\uB4E0 \uD30C\uC77C)").option("--color", "\uCEEC\uB7EC \uCD9C\uB825", true).option("--remote", "Notion \uD604\uC7AC \uBCF8\uBB38\uACFC \uBE44\uAD50 (API \uD638\uCD9C, \uB290\uB9BC)").action(async (targetPath, options) => {
|
|
508
752
|
const cwd = process.cwd();
|
|
509
753
|
const configManager = new ConfigManager6(cwd);
|
|
510
754
|
const config = await configManager.load();
|
|
@@ -514,73 +758,71 @@ var diffCommand = new Command6("diff").description("\uB85C\uCEEC\uACFC Notion \u
|
|
|
514
758
|
const vaultFs = new NodeVaultFS5(cwd, config.paths);
|
|
515
759
|
const orchestrator = new SyncOrchestrator5(config, stateDb, client, vaultFs);
|
|
516
760
|
const status = await orchestrator.statusLocal();
|
|
517
|
-
const changes = targetPath ? status.localChanges.filter((c) => c.path
|
|
761
|
+
const changes = targetPath ? status.localChanges.filter((c) => matchesPathScope(c.path, targetPath)) : status.localChanges;
|
|
518
762
|
if (changes.length === 0) {
|
|
519
763
|
console.log("\uBCC0\uACBD\uC0AC\uD56D \uC5C6\uC74C");
|
|
520
764
|
return;
|
|
521
765
|
}
|
|
766
|
+
let failed = 0;
|
|
522
767
|
for (const change of changes) {
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
console.log(
|
|
535
|
-
|
|
536
|
-
console.log(`(\uC774\uC804 \uC2A4\uB0C5\uC0F7 \uC5C6\uC74C)`);
|
|
768
|
+
if (options.remote && change.type !== "deleted") {
|
|
769
|
+
let remotePath = change.path;
|
|
770
|
+
let remote = await orchestrator.renderRemoteSnapshot(remotePath);
|
|
771
|
+
if (remote === null && change.movedFrom) {
|
|
772
|
+
remotePath = change.movedFrom;
|
|
773
|
+
remote = await orchestrator.renderRemoteSnapshot(remotePath);
|
|
774
|
+
}
|
|
775
|
+
if (remote === null) {
|
|
776
|
+
console.log(`--- (\uC6D0\uACA9 \uC5C6\uC74C) ${change.path}`);
|
|
777
|
+
console.log(`+++ b/${change.path}`);
|
|
778
|
+
console.log(`(\uC544\uC9C1 Notion \uC5D0 \uC5C6\uB294 \uD30C\uC77C\uC785\uB2C8\uB2E4)`);
|
|
779
|
+
console.log("");
|
|
780
|
+
continue;
|
|
537
781
|
}
|
|
538
|
-
} else if (change.type === "modified" && record?.baseSnapshot) {
|
|
539
782
|
const currentContent = await vaultFs.readFile(change.path);
|
|
540
|
-
const
|
|
541
|
-
const
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
);
|
|
547
|
-
printPatch(patch, options.color, `a/${change.path}`, `b/${change.path}`);
|
|
783
|
+
const hunks = lineDiff(remote, currentContent);
|
|
784
|
+
const lines = formatUnifiedDiff(hunks, `notion/${remotePath}`, `b/${change.path}`);
|
|
785
|
+
if (hunks.length === 0) lines.push("(Notion \uACFC \uB0B4\uC6A9\uC774 \uAC19\uC2B5\uB2C8\uB2E4)");
|
|
786
|
+
printUnifiedDiff(lines, options.color);
|
|
787
|
+
console.log("");
|
|
788
|
+
continue;
|
|
548
789
|
}
|
|
790
|
+
let diff;
|
|
791
|
+
try {
|
|
792
|
+
diff = await orchestrator.localChangeDiff(change);
|
|
793
|
+
} catch (error) {
|
|
794
|
+
failed++;
|
|
795
|
+
console.log(`--- a/${change.path}`);
|
|
796
|
+
console.log(`(${error instanceof Error ? error.message : String(error)})`);
|
|
797
|
+
console.log("");
|
|
798
|
+
continue;
|
|
799
|
+
}
|
|
800
|
+
printChangeDiff(diff, options.color);
|
|
549
801
|
console.log("");
|
|
550
802
|
}
|
|
803
|
+
if (failed > 0) process.exitCode = 1;
|
|
551
804
|
} finally {
|
|
552
805
|
stateDb.close();
|
|
553
806
|
}
|
|
554
807
|
});
|
|
555
|
-
function
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
}
|
|
565
|
-
if (!color) {
|
|
566
|
-
console.log(output);
|
|
567
|
-
continue;
|
|
568
|
-
}
|
|
569
|
-
if (output.startsWith("+++") || output.startsWith("---")) {
|
|
570
|
-
console.log(`\x1B[1m${output}\x1B[0m`);
|
|
571
|
-
} else if (output.startsWith("+")) {
|
|
572
|
-
console.log(`\x1B[32m${output}\x1B[0m`);
|
|
573
|
-
} else if (output.startsWith("-")) {
|
|
574
|
-
console.log(`\x1B[31m${output}\x1B[0m`);
|
|
575
|
-
} else if (output.startsWith("@@")) {
|
|
576
|
-
console.log(`\x1B[36m${output}\x1B[0m`);
|
|
577
|
-
} else {
|
|
578
|
-
console.log(output);
|
|
808
|
+
function printChangeDiff(diff, color) {
|
|
809
|
+
const oldPath = diff.before === null ? "/dev/null" : `a/${diff.movedFrom ?? diff.path}`;
|
|
810
|
+
const newPath = diff.after === null ? "/dev/null" : `b/${diff.path}`;
|
|
811
|
+
if (diff.movedFrom) {
|
|
812
|
+
console.log(`rename from ${diff.movedFrom}`);
|
|
813
|
+
console.log(`rename to ${diff.path}`);
|
|
814
|
+
if (diff.before === diff.after) {
|
|
815
|
+
console.log("(\uB0B4\uC6A9\uC740 \uADF8\uB300\uB85C\uC785\uB2C8\uB2E4 \u2014 \uC790\uB9AC\uB9CC \uC62E\uACBC\uC2B5\uB2C8\uB2E4)");
|
|
816
|
+
return;
|
|
579
817
|
}
|
|
580
818
|
}
|
|
819
|
+
printUnifiedDiff(
|
|
820
|
+
formatUnifiedDiff(lineDiff(diff.before ?? "", diff.after ?? ""), oldPath, newPath),
|
|
821
|
+
color
|
|
822
|
+
);
|
|
581
823
|
}
|
|
582
824
|
|
|
583
|
-
// src/commands/
|
|
825
|
+
// src/commands/discard.ts
|
|
584
826
|
import { Command as Command7 } from "commander";
|
|
585
827
|
import {
|
|
586
828
|
ConfigManager as ConfigManager7,
|
|
@@ -589,8 +831,7 @@ import {
|
|
|
589
831
|
SyncOrchestrator as SyncOrchestrator6,
|
|
590
832
|
NodeVaultFS as NodeVaultFS6
|
|
591
833
|
} from "@im-nobsidian/core";
|
|
592
|
-
|
|
593
|
-
var fetchCommand = new Command7("fetch").description("Notion \uB9AC\uBAA8\uD2B8 \uC0C1\uD0DC \uC2A4\uCE94 (\uC0AD\uC81C \uAC10\uC9C0 \uD3EC\uD568)").action(async () => {
|
|
834
|
+
var discardCommand = new Command7("discard").description("\uB85C\uCEEC \uBCC0\uACBD\uC744 \uC9C0\uB09C \uB3D9\uAE30\uD654 \uB54C\uC758 \uAE00\uB85C \uB418\uB3CC\uB9AC\uAE30 (Notion \uC740 \uADF8\uB300\uB85C)").argument("<paths...>", "\uB418\uB3CC\uB9B4 \uB178\uD2B8 \uACBD\uB85C \u2014 status \uAC00 \uBCF4\uC778 \uBCFC\uD2B8 \uAE30\uC900 \uACBD\uB85C").action(async (paths) => {
|
|
594
835
|
const cwd = process.cwd();
|
|
595
836
|
const configManager = new ConfigManager7(cwd);
|
|
596
837
|
const config = await configManager.load();
|
|
@@ -599,27 +840,62 @@ var fetchCommand = new Command7("fetch").description("Notion \uB9AC\uBAA8\uD2B8
|
|
|
599
840
|
const client = NotionClient7.fromConfig(config);
|
|
600
841
|
const vaultFs = new NodeVaultFS6(cwd, config.paths);
|
|
601
842
|
const orchestrator = new SyncOrchestrator6(config, stateDb, client, vaultFs);
|
|
843
|
+
let failed = 0;
|
|
844
|
+
for (const path of paths) {
|
|
845
|
+
try {
|
|
846
|
+
await orchestrator.discardLocalChange(path);
|
|
847
|
+
console.log(` ${icons.success} ${path}`);
|
|
848
|
+
} catch (error) {
|
|
849
|
+
failed++;
|
|
850
|
+
failedItem(path, error instanceof Error ? error.message : String(error));
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
if (failed > 0) process.exitCode = 1;
|
|
854
|
+
} finally {
|
|
855
|
+
stateDb.close();
|
|
856
|
+
}
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
// src/commands/fetch.ts
|
|
860
|
+
import { Command as Command8 } from "commander";
|
|
861
|
+
import {
|
|
862
|
+
ConfigManager as ConfigManager8,
|
|
863
|
+
StateDB as StateDB7,
|
|
864
|
+
NotionClient as NotionClient8,
|
|
865
|
+
SyncOrchestrator as SyncOrchestrator7,
|
|
866
|
+
NodeVaultFS as NodeVaultFS7
|
|
867
|
+
} from "@im-nobsidian/core";
|
|
868
|
+
import chalk6 from "chalk";
|
|
869
|
+
var fetchCommand = new Command8("fetch").description("Notion \uB9AC\uBAA8\uD2B8 \uC0C1\uD0DC \uC2A4\uCE94 (\uC0AD\uC81C \uAC10\uC9C0 \uD3EC\uD568)").action(async () => {
|
|
870
|
+
const cwd = process.cwd();
|
|
871
|
+
const configManager = new ConfigManager8(cwd);
|
|
872
|
+
const config = await configManager.load();
|
|
873
|
+
const stateDb = StateDB7.open(configManager.dbPath);
|
|
874
|
+
try {
|
|
875
|
+
const client = NotionClient8.fromConfig(config);
|
|
876
|
+
const vaultFs = new NodeVaultFS7(cwd, config.paths);
|
|
877
|
+
const orchestrator = new SyncOrchestrator7(config, stateDb, client, vaultFs);
|
|
602
878
|
console.log(dimText(" Scanning remote pages..."));
|
|
603
879
|
const result = await orchestrator.fetch();
|
|
604
880
|
console.log(`
|
|
605
881
|
${header(" Remote Scan")}`);
|
|
606
882
|
console.log(` ${separator(40)}`);
|
|
607
883
|
if (result.newPages > 0) {
|
|
608
|
-
console.log(` ${
|
|
884
|
+
console.log(` ${chalk6.cyan("+")} New pages: ${result.newPages}`);
|
|
609
885
|
}
|
|
610
886
|
if (result.modifiedPages > 0) {
|
|
611
|
-
console.log(` ${
|
|
887
|
+
console.log(` ${chalk6.yellow("~")} Modified pages: ${result.modifiedPages}`);
|
|
612
888
|
}
|
|
613
889
|
if (result.deletedPages > 0) {
|
|
614
|
-
console.log(` ${
|
|
890
|
+
console.log(` ${chalk6.red("-")} Deleted pages: ${result.deletedPages}`);
|
|
615
891
|
}
|
|
616
892
|
const total = result.newPages + result.modifiedPages + result.deletedPages;
|
|
617
893
|
if (total === 0) {
|
|
618
|
-
console.log(` ${
|
|
894
|
+
console.log(` ${chalk6.green("Remote is up to date")} \u2713`);
|
|
619
895
|
} else {
|
|
620
896
|
console.log(
|
|
621
897
|
`
|
|
622
|
-
${dimText("Run")} ${
|
|
898
|
+
${dimText("Run")} ${chalk6.cyan("nobsi pull")} ${dimText("to apply remote changes")}`
|
|
623
899
|
);
|
|
624
900
|
}
|
|
625
901
|
console.log(dimText(`
|
|
@@ -630,113 +906,205 @@ ${header(" Remote Scan")}`);
|
|
|
630
906
|
});
|
|
631
907
|
|
|
632
908
|
// src/commands/resolve.ts
|
|
633
|
-
import { Command as
|
|
909
|
+
import { Command as Command9 } from "commander";
|
|
634
910
|
import { select, confirm as confirm2 } from "@inquirer/prompts";
|
|
635
911
|
import {
|
|
636
|
-
ConfigManager as
|
|
637
|
-
StateDB as
|
|
638
|
-
NotionClient as
|
|
639
|
-
SyncOrchestrator as
|
|
640
|
-
NodeVaultFS as
|
|
912
|
+
ConfigManager as ConfigManager9,
|
|
913
|
+
StateDB as StateDB8,
|
|
914
|
+
NotionClient as NotionClient9,
|
|
915
|
+
SyncOrchestrator as SyncOrchestrator8,
|
|
916
|
+
NodeVaultFS as NodeVaultFS8,
|
|
917
|
+
applicableChoices,
|
|
918
|
+
isRemoteDeletion as isRemoteDeletion2
|
|
641
919
|
} from "@im-nobsidian/core";
|
|
642
|
-
|
|
920
|
+
|
|
921
|
+
// src/utils/state-db-failure.ts
|
|
922
|
+
import { StateDbUnavailableError } from "@im-nobsidian/core";
|
|
923
|
+
function describeFailure(error) {
|
|
924
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
925
|
+
if (!(error instanceof StateDbUnavailableError)) return message;
|
|
926
|
+
return `${message} \u2014 ${error.guidance("\uBA85\uB839\uC744 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694")}`;
|
|
927
|
+
}
|
|
928
|
+
function reportStateDbFailure(error, proc) {
|
|
929
|
+
if (!(error instanceof StateDbUnavailableError)) throw error;
|
|
930
|
+
console.error(`\uC624\uB958: ${describeFailure(error)}`);
|
|
931
|
+
proc.exitCode = 1;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
// src/commands/resolve.ts
|
|
935
|
+
var STRATEGIES = ["local-first", "remote-first", "duplicate"];
|
|
936
|
+
var CHOICE_NAMES = {
|
|
937
|
+
local: "\uB85C\uCEEC \uC720\uC9C0 (Obsidian \uBC84\uC804 \uC720\uC9C0)",
|
|
938
|
+
remote: "\uC6D0\uACA9 \uC720\uC9C0 (Notion \uBC84\uC804\uC73C\uB85C \uB36E\uC5B4\uC4F0\uAE30)",
|
|
939
|
+
merge: "\uC790\uB3D9 \uBCD1\uD569 (3-way merge)",
|
|
940
|
+
duplicate: "\uBCF5\uC81C (\uC6D0\uBCF8 \uC720\uC9C0 + \uC62C\uB9AC\uC9C0 \uC54A\uB294 .conflict \uC0AC\uBCF8 \uC0DD\uC131)"
|
|
941
|
+
};
|
|
942
|
+
var REMOTE_DELETION_CHOICE_NAMES = {
|
|
943
|
+
local: "\uB85C\uCEEC \uC720\uC9C0 (Notion \uC5D0 \uC0C8 \uD398\uC774\uC9C0\uB85C \uB2E4\uC2DC \uB9CC\uB4E4\uAE30)",
|
|
944
|
+
remote: "\uC0AD\uC81C \uB530\uB974\uAE30 (Obsidian \uD30C\uC77C\uB3C4 \uC9C0\uC6B0\uAE30)"
|
|
945
|
+
};
|
|
946
|
+
var PULL_ONLY_REMOTE_DELETION_CHOICE_NAMES = {
|
|
947
|
+
local: "\uB85C\uCEEC \uC720\uC9C0 (Obsidian \uD30C\uC77C\uB9CC \uB450\uAE30 \u2014 \uBC1B\uAE30 \uC804\uC6A9\uC774\uB77C Notion \uC5D0 \uB9CC\uB4E4\uC9C0 \uC54A\uC74C)"
|
|
948
|
+
};
|
|
949
|
+
function choiceName(conflict, choice, uploads) {
|
|
950
|
+
const deleted = isRemoteDeletion2(conflict);
|
|
951
|
+
return (deleted && !uploads ? PULL_ONLY_REMOTE_DELETION_CHOICE_NAMES[choice] : void 0) ?? (deleted ? REMOTE_DELETION_CHOICE_NAMES[choice] : void 0) ?? CHOICE_NAMES[choice] ?? choice;
|
|
952
|
+
}
|
|
953
|
+
function isInteractive() {
|
|
954
|
+
return process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
955
|
+
}
|
|
956
|
+
var resolveCommand = new Command9("resolve").description("\uCDA9\uB3CC\uC744 \uD574\uACB0\uD569\uB2C8\uB2E4").option("-s, --strategy <strategy>", `\uC790\uB3D9 \uD574\uACB0 \uC804\uB7B5 (${STRATEGIES.join(" | ")})`).option("-y, --yes", "\uD655\uC778 \uD504\uB86C\uD504\uD2B8 \uC5C6\uC774 \uC9C4\uD589 (\uBE44\uB300\uD654\uD615/CI)").action(async (options) => {
|
|
957
|
+
if (options.strategy && !STRATEGIES.includes(options.strategy)) {
|
|
958
|
+
console.error(`\uC54C \uC218 \uC5C6\uB294 \uC804\uB7B5 '${options.strategy}' \u2014 \uC0AC\uC6A9 \uAC00\uB2A5: ${STRATEGIES.join(", ")}`);
|
|
959
|
+
process.exitCode = 1;
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
643
962
|
const cwd = process.cwd();
|
|
644
|
-
const configManager = new
|
|
963
|
+
const configManager = new ConfigManager9(cwd);
|
|
964
|
+
let stateDb;
|
|
645
965
|
try {
|
|
646
966
|
const config = await configManager.load();
|
|
647
|
-
|
|
648
|
-
const client =
|
|
649
|
-
const vaultFs = new
|
|
650
|
-
const orchestrator = new
|
|
651
|
-
await orchestrator.
|
|
652
|
-
|
|
653
|
-
if (conflictRecords.length === 0) {
|
|
967
|
+
stateDb = StateDB8.open(configManager.dbPath);
|
|
968
|
+
const client = NotionClient9.fromConfig(config);
|
|
969
|
+
const vaultFs = new NodeVaultFS8(cwd, config.paths);
|
|
970
|
+
const orchestrator = new SyncOrchestrator8(config, stateDb, client, vaultFs);
|
|
971
|
+
const conflicts = await orchestrator.listConflicts();
|
|
972
|
+
if (conflicts.length === 0) {
|
|
654
973
|
console.log("\uCDA9\uB3CC\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
655
|
-
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
976
|
+
const cleared = new Set(orchestrator.clearStaleConflicts(conflicts));
|
|
977
|
+
for (const path of cleared) {
|
|
978
|
+
console.log(` \x1B[32m\u2713\x1B[0m ${path} \u2192 \uC591\uCABD \uB3D9\uC77C, \uCDA9\uB3CC \uD45C\uC2DC\uB9CC \uD574\uC81C`);
|
|
979
|
+
}
|
|
980
|
+
const pending = conflicts.filter((c) => !cleared.has(c.syncRecord.obsidianPath));
|
|
981
|
+
if (pending.length === 0) {
|
|
982
|
+
console.log(`
|
|
983
|
+
${cleared.size}\uAC74 \uBAA8\uB450 \uC774\uBBF8 \uD574\uC18C\uB41C \uC0C1\uD0DC\uC600\uC2B5\uB2C8\uB2E4 \u2014 \uC0C1\uD0DC\uB9CC \uC815\uB9AC\uD588\uC2B5\uB2C8\uB2E4.`);
|
|
656
984
|
return;
|
|
657
985
|
}
|
|
658
986
|
console.log(`
|
|
659
|
-
\uCDA9\uB3CC ${
|
|
987
|
+
\uCDA9\uB3CC ${pending.length}\uAC74 \uBC1C\uACAC
|
|
660
988
|
`);
|
|
989
|
+
const uploads = config.sync.direction !== "pull";
|
|
990
|
+
if (!uploads) {
|
|
991
|
+
console.log(
|
|
992
|
+
" \uBC1B\uAE30 \uC804\uC6A9(direction: pull) \u2014 \uD574\uACB0 \uACB0\uACFC\uB294 Obsidian \uC5D0\uB9CC \uB450\uACE0 Notion \uC5D0\uB294 \uC62C\uB9AC\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4\n"
|
|
993
|
+
);
|
|
994
|
+
}
|
|
661
995
|
if (options.strategy) {
|
|
662
996
|
const strategy = options.strategy;
|
|
663
|
-
const
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
return;
|
|
997
|
+
const deletions = pending.filter(isRemoteDeletion2).length;
|
|
998
|
+
if (deletions > 0) {
|
|
999
|
+
console.log(
|
|
1000
|
+
` Notion \uC5D0\uC11C \uC0AD\uC81C\uB41C \uB178\uD2B8 ${deletions}\uAC74 \u2014 ${strategy === "remote-first" ? "Obsidian \uD30C\uC77C\uB3C4 \uC9C0\uC6C1\uB2C8\uB2E4" : uploads ? "Notion \uC5D0 \uC0C8 \uD398\uC774\uC9C0\uB85C \uB2E4\uC2DC \uB9CC\uB4ED\uB2C8\uB2E4" : "Obsidian \uD30C\uC77C\uC744 \uADF8\uB300\uB85C \uB461\uB2C8\uB2E4"}`
|
|
1001
|
+
);
|
|
669
1002
|
}
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
1003
|
+
if (!options.yes) {
|
|
1004
|
+
if (!isInteractive()) {
|
|
1005
|
+
console.error(
|
|
1006
|
+
`\uBE44\uB300\uD654\uD615 \uD658\uACBD\uC785\uB2C8\uB2E4 \u2014 '${strategy}' \uC804\uB7B5\uC73C\uB85C ${pending.length}\uAC74\uC744 \uC77C\uAD04 \uD574\uACB0\uD558\uB824\uBA74 --yes \uB97C \uD568\uAED8 \uC9C0\uC815\uD558\uC138\uC694.`
|
|
1007
|
+
);
|
|
1008
|
+
process.exitCode = 1;
|
|
1009
|
+
return;
|
|
1010
|
+
}
|
|
1011
|
+
const shouldProceed = await confirm2({
|
|
1012
|
+
message: `${pending.length}\uAC74\uC758 \uCDA9\uB3CC\uC744 '${strategy}' \uC804\uB7B5\uC73C\uB85C \uC77C\uAD04 \uD574\uACB0\uD560\uAE4C\uC694?`
|
|
1013
|
+
});
|
|
1014
|
+
if (!shouldProceed) return;
|
|
675
1015
|
}
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
for (const
|
|
679
|
-
|
|
1016
|
+
const results = await orchestrator.resolveAllConflicts(pending, strategy);
|
|
1017
|
+
let failed2 = 0;
|
|
1018
|
+
for (const [index, result] of results.entries()) {
|
|
1019
|
+
if (!result.success) failed2++;
|
|
1020
|
+
const icon = result.success ? "\x1B[32m\u2713\x1B[0m" : "\x1B[31m\u2717\x1B[0m";
|
|
1021
|
+
const reason = result.error ? ` \u2014 ${result.error}` : "";
|
|
1022
|
+
console.log(
|
|
1023
|
+
` ${icon} ${result.path} \u2192 ${choiceName(pending[index], result.choice, uploads)}${reason}`
|
|
1024
|
+
);
|
|
680
1025
|
}
|
|
1026
|
+
report(results.length - failed2, failed2);
|
|
1027
|
+
return;
|
|
681
1028
|
}
|
|
682
|
-
|
|
683
|
-
|
|
1029
|
+
if (!isInteractive()) {
|
|
1030
|
+
console.error(
|
|
1031
|
+
`\uBE44\uB300\uD654\uD615 \uD658\uACBD\uC5D0\uC11C\uB294 \uB300\uD654\uD615 \uD574\uACB0\uC744 \uC4F8 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 --strategy <${STRATEGIES.join("|")}> --yes \uB97C \uC9C0\uC815\uD558\uC138\uC694.`
|
|
1032
|
+
);
|
|
1033
|
+
process.exitCode = 1;
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
let resolved = 0;
|
|
1037
|
+
let failed = 0;
|
|
1038
|
+
for (const conflict of pending) {
|
|
1039
|
+
if (await resolveInteractive(conflict, orchestrator, uploads)) resolved++;
|
|
1040
|
+
else failed++;
|
|
1041
|
+
}
|
|
1042
|
+
report(resolved, failed);
|
|
684
1043
|
} catch (error) {
|
|
685
|
-
console.error("\uC624\uB958:",
|
|
686
|
-
process.
|
|
1044
|
+
console.error("\uC624\uB958:", describeFailure(error));
|
|
1045
|
+
process.exitCode = 1;
|
|
1046
|
+
} finally {
|
|
1047
|
+
stateDb?.close();
|
|
687
1048
|
}
|
|
688
1049
|
});
|
|
689
|
-
|
|
1050
|
+
function report(resolved, failed) {
|
|
1051
|
+
if (failed === 0) {
|
|
1052
|
+
console.log(`
|
|
1053
|
+
\uCDA9\uB3CC \uD574\uACB0 \uC644\uB8CC \u2014 ${resolved}\uAC74`);
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1056
|
+
console.log(`
|
|
1057
|
+
\x1B[33m\uCDA9\uB3CC ${resolved}\uAC74 \uD574\uACB0 \xB7 ${failed}\uAC74 \uBBF8\uD574\uACB0\x1B[0m`);
|
|
1058
|
+
process.exitCode = 1;
|
|
1059
|
+
}
|
|
1060
|
+
async function resolveInteractive(conflict, orchestrator, uploads) {
|
|
690
1061
|
const path = conflict.syncRecord.obsidianPath;
|
|
691
1062
|
console.log(`
|
|
692
1063
|
${"\u2500".repeat(60)}`);
|
|
693
1064
|
console.log(`\uD30C\uC77C: \x1B[1m${path}\x1B[0m`);
|
|
694
1065
|
console.log(`${"\u2500".repeat(60)}`);
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
console.log(`\x1B[32m${line}\x1B[0m`);
|
|
702
|
-
} else if (line.startsWith("---") || line.startsWith("+++")) {
|
|
703
|
-
console.log(`\x1B[1m${line}\x1B[0m`);
|
|
704
|
-
} else {
|
|
705
|
-
console.log(line);
|
|
706
|
-
}
|
|
1066
|
+
if (isRemoteDeletion2(conflict)) {
|
|
1067
|
+
console.log(
|
|
1068
|
+
"\x1B[33mNotion \uC5D0\uC11C \uC0AD\uC81C\uB41C \uB178\uD2B8\uC785\uB2C8\uB2E4 \u2014 Notion \uC5D0 \uC62C\uB9AC\uC9C0 \uC54A\uC740 \uB85C\uCEEC \uD3B8\uC9D1\uC774 \uB0A8\uC544 \uC788\uC2B5\uB2C8\uB2E4.\x1B[0m"
|
|
1069
|
+
);
|
|
1070
|
+
} else {
|
|
1071
|
+
printUnifiedDiff(orchestrator.generateConflictDiff(conflict).split("\n"), true);
|
|
707
1072
|
}
|
|
708
1073
|
const choice = await select({
|
|
709
1074
|
message: "\uC5B4\uB5BB\uAC8C \uD574\uACB0\uD560\uAE4C\uC694?",
|
|
710
|
-
choices:
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
{ name: "\uBCF5\uC81C (\uC6D0\uBCF8 \uC720\uC9C0 + .conflict \uD30C\uC77C \uC0DD\uC131)", value: "duplicate" }
|
|
715
|
-
]
|
|
1075
|
+
choices: applicableChoices(conflict).map((value) => ({
|
|
1076
|
+
name: choiceName(conflict, value, uploads),
|
|
1077
|
+
value
|
|
1078
|
+
}))
|
|
716
1079
|
});
|
|
717
1080
|
const result = await orchestrator.resolveConflict(conflict, choice);
|
|
718
1081
|
if (result.success) {
|
|
719
|
-
console.log(`\x1B[32m\u2713 ${path} \u2192 ${choice}\
|
|
720
|
-
|
|
1082
|
+
console.log(`\x1B[32m\u2713 ${path} \u2192 ${choiceName(conflict, choice, uploads)}\x1B[0m`);
|
|
1083
|
+
return true;
|
|
1084
|
+
}
|
|
1085
|
+
if (result.mergeHadConflicts) {
|
|
721
1086
|
console.log(`\x1B[33m\u26A0 ${path} \u2192 \uC790\uB3D9 \uBCD1\uD569 \uC644\uB8CC (\uC218\uB3D9 \uD655\uC778 \uD544\uC694\uD55C \uCDA9\uB3CC \uB9C8\uCEE4 \uC788\uC74C)\x1B[0m`);
|
|
722
|
-
|
|
723
|
-
console.log(`\x1B[31m\u2717 ${path} \u2192 \uD574\uACB0 \uC2E4\uD328\x1B[0m`);
|
|
1087
|
+
return false;
|
|
724
1088
|
}
|
|
1089
|
+
console.log(`\x1B[31m\u2717 ${path} \u2192 \uD574\uACB0 \uC2E4\uD328\x1B[0m`);
|
|
1090
|
+
return false;
|
|
725
1091
|
}
|
|
726
1092
|
|
|
727
1093
|
// src/commands/watch.ts
|
|
728
|
-
import { Command as
|
|
1094
|
+
import { Command as Command10 } from "commander";
|
|
729
1095
|
import {
|
|
730
|
-
ConfigManager as
|
|
731
|
-
StateDB as
|
|
732
|
-
NotionClient as
|
|
733
|
-
SyncOrchestrator as
|
|
734
|
-
NodeVaultFS as
|
|
735
|
-
WatchSyncService
|
|
1096
|
+
ConfigManager as ConfigManager10,
|
|
1097
|
+
StateDB as StateDB9,
|
|
1098
|
+
NotionClient as NotionClient10,
|
|
1099
|
+
SyncOrchestrator as SyncOrchestrator9,
|
|
1100
|
+
NodeVaultFS as NodeVaultFS9,
|
|
1101
|
+
WatchSyncService,
|
|
1102
|
+
DEFAULT_WATCH_DEBOUNCE_MS
|
|
736
1103
|
} from "@im-nobsidian/core";
|
|
737
1104
|
function formatResult(result) {
|
|
738
1105
|
const pull = `Pull: +${result.pull.created} ~${result.pull.updated} -${result.pull.deleted}`;
|
|
739
|
-
const
|
|
1106
|
+
const moved = result.push.moved > 0 ? ` \u2192${result.push.moved}` : "";
|
|
1107
|
+
const push = `Push: +${result.push.created} ~${result.push.updated} -${result.push.deleted}${moved}`;
|
|
740
1108
|
const parts = [pull, push];
|
|
741
1109
|
if (result.conflicts.length > 0) {
|
|
742
1110
|
parts.push(`\uCDA9\uB3CC ${result.conflicts.length}\uAC74`);
|
|
@@ -746,35 +1114,59 @@ function formatResult(result) {
|
|
|
746
1114
|
function timestamp() {
|
|
747
1115
|
return (/* @__PURE__ */ new Date()).toLocaleTimeString("ko-KR", { hour12: false });
|
|
748
1116
|
}
|
|
749
|
-
|
|
1117
|
+
function syncLabel(scope) {
|
|
1118
|
+
return scope === "full" ? "\uC8FC\uAE30\uC801 \uB3D9\uAE30\uD654" : "\uB3D9\uAE30\uD654";
|
|
1119
|
+
}
|
|
1120
|
+
var watchCommand = new Command10("watch").description("\uD30C\uC77C \uBCC0\uACBD \uAC10\uC9C0 + \uC790\uB3D9 \uB3D9\uAE30\uD654").option("-d, --debounce <ms>", "\uB514\uBC14\uC6B4\uC2A4 \uB300\uAE30 \uC2DC\uAC04(ms)", String(DEFAULT_WATCH_DEBOUNCE_MS)).option("-i, --interval <seconds>", "\uD480 \uB3D9\uAE30\uD654 \uC8FC\uAE30(\uCD08, 0\uC774\uBA74 \uBE44\uD65C\uC131)", "0").action(async (options) => {
|
|
1121
|
+
const debounceMs = Number(options.debounce);
|
|
1122
|
+
const intervalSec = Number(options.interval);
|
|
1123
|
+
if (!Number.isInteger(debounceMs) || debounceMs < 0) {
|
|
1124
|
+
console.error(`--debounce \uB294 0 \uC774\uC0C1\uC758 \uC815\uC218(ms)\uC5EC\uC57C \uD569\uB2C8\uB2E4: ${options.debounce}`);
|
|
1125
|
+
process.exitCode = 1;
|
|
1126
|
+
return;
|
|
1127
|
+
}
|
|
1128
|
+
if (!Number.isInteger(intervalSec) || intervalSec < 0) {
|
|
1129
|
+
console.error(`--interval \uC740 0 \uC774\uC0C1\uC758 \uC815\uC218(\uCD08)\uC5EC\uC57C \uD569\uB2C8\uB2E4: ${options.interval}`);
|
|
1130
|
+
process.exitCode = 1;
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
750
1133
|
const cwd = process.cwd();
|
|
751
|
-
const configManager = new
|
|
1134
|
+
const configManager = new ConfigManager10(cwd);
|
|
752
1135
|
const config = await configManager.load();
|
|
753
|
-
const stateDb =
|
|
754
|
-
const client =
|
|
755
|
-
const vaultFs = new
|
|
756
|
-
const orchestrator = new
|
|
757
|
-
const debounceMs = parseInt(options.debounce, 10);
|
|
758
|
-
const intervalSec = parseInt(options.interval, 10);
|
|
1136
|
+
const stateDb = StateDB9.open(configManager.dbPath);
|
|
1137
|
+
const client = NotionClient10.fromConfig(config);
|
|
1138
|
+
const vaultFs = new NodeVaultFS9(cwd, config.paths);
|
|
1139
|
+
const orchestrator = new SyncOrchestrator9(config, stateDb, client, vaultFs);
|
|
759
1140
|
const service = new WatchSyncService(cwd, orchestrator, {
|
|
760
1141
|
debounceMs,
|
|
761
1142
|
onFileChange: (event, path) => {
|
|
762
1143
|
console.log(`[${timestamp()}] ${event}: ${path}`);
|
|
763
1144
|
},
|
|
764
|
-
onSyncStart: () => {
|
|
765
|
-
console.log(`[${timestamp()}]
|
|
1145
|
+
onSyncStart: (scope) => {
|
|
1146
|
+
console.log(`[${timestamp()}] ${syncLabel(scope)} \uC2DC\uC791...`);
|
|
766
1147
|
},
|
|
767
|
-
onSyncComplete: (result) => {
|
|
768
|
-
console.log(`[${timestamp()}]
|
|
1148
|
+
onSyncComplete: (result, scope) => {
|
|
1149
|
+
console.log(`[${timestamp()}] ${syncLabel(scope)} \uC644\uB8CC \u2014 ${formatResult(result)}`);
|
|
1150
|
+
},
|
|
1151
|
+
onSyncCancelled: (result, scope) => {
|
|
1152
|
+
console.log(
|
|
1153
|
+
`[${timestamp()}] ${syncLabel(scope)} \uCDE8\uC18C \u2014 \uBA48\uCD94\uAE30 \uC804\uAE4C\uC9C0 ${formatResult(result)}`
|
|
1154
|
+
);
|
|
769
1155
|
},
|
|
770
|
-
onSyncError: (error) => {
|
|
771
|
-
console.error(`[${timestamp()}]
|
|
1156
|
+
onSyncError: (error, scope) => {
|
|
1157
|
+
console.error(`[${timestamp()}] ${syncLabel(scope)} \uC2E4\uD328: ${error.message}`);
|
|
772
1158
|
}
|
|
773
1159
|
});
|
|
774
1160
|
let pullTimer = null;
|
|
1161
|
+
let shuttingDown = false;
|
|
775
1162
|
const shutdown = async () => {
|
|
1163
|
+
if (shuttingDown) {
|
|
1164
|
+
console.log(`[${timestamp()}] \uAC15\uC81C \uC885\uB8CC`);
|
|
1165
|
+
return process.exit(1);
|
|
1166
|
+
}
|
|
1167
|
+
shuttingDown = true;
|
|
776
1168
|
console.log(`
|
|
777
|
-
[${timestamp()}] \uAC10\uC2DC \uC885\uB8CC \uC911
|
|
1169
|
+
[${timestamp()}] \uAC10\uC2DC \uC885\uB8CC \uC911... (\uB3C4\uB294 \uB3D9\uAE30\uD654\uB97C \uBA48\uCD94\uACE0 \uAE30\uB2E4\uB9BD\uB2C8\uB2E4)`);
|
|
778
1170
|
if (pullTimer) clearInterval(pullTimer);
|
|
779
1171
|
await service.stop();
|
|
780
1172
|
stateDb.close();
|
|
@@ -787,26 +1179,148 @@ var watchCommand = new Command9("watch").description("\uD30C\uC77C \uBCC0\uACBD
|
|
|
787
1179
|
console.log(`[${timestamp()}] \uD30C\uC77C \uAC10\uC2DC \uC2DC\uC791 (debounce: ${debounceMs}ms)`);
|
|
788
1180
|
if (intervalSec > 0) {
|
|
789
1181
|
console.log(`[${timestamp()}] \uD480 \uB3D9\uAE30\uD654 \uC8FC\uAE30: ${intervalSec}\uCD08`);
|
|
790
|
-
pullTimer = setInterval(
|
|
791
|
-
if (service.isSyncing()) return;
|
|
792
|
-
try {
|
|
793
|
-
console.log(`[${timestamp()}] \uC8FC\uAE30\uC801 \uB3D9\uAE30\uD654 \uC2DC\uC791...`);
|
|
794
|
-
const result = await orchestrator.sync();
|
|
795
|
-
console.log(`[${timestamp()}] \uC8FC\uAE30\uC801 \uB3D9\uAE30\uD654 \uC644\uB8CC \u2014 ${formatResult(result)}`);
|
|
796
|
-
} catch (error) {
|
|
797
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
798
|
-
console.error(`[${timestamp()}] \uC8FC\uAE30\uC801 \uB3D9\uAE30\uD654 \uC2E4\uD328: ${msg}`);
|
|
799
|
-
}
|
|
800
|
-
}, intervalSec * 1e3);
|
|
1182
|
+
pullTimer = setInterval(() => service.requestFullSync(), intervalSec * 1e3);
|
|
801
1183
|
}
|
|
802
1184
|
await new Promise(() => {
|
|
803
1185
|
});
|
|
804
1186
|
});
|
|
805
1187
|
|
|
1188
|
+
// src/commands/verify.ts
|
|
1189
|
+
import { Command as Command11 } from "commander";
|
|
1190
|
+
import {
|
|
1191
|
+
ConfigManager as ConfigManager11,
|
|
1192
|
+
StateDB as StateDB10,
|
|
1193
|
+
NotionClient as NotionClient11,
|
|
1194
|
+
SyncOrchestrator as SyncOrchestrator10,
|
|
1195
|
+
NodeVaultFS as NodeVaultFS10
|
|
1196
|
+
} from "@im-nobsidian/core";
|
|
1197
|
+
import chalk7 from "chalk";
|
|
1198
|
+
var SAMPLE_LIMIT = 10;
|
|
1199
|
+
function printIdSample(label, ids, color) {
|
|
1200
|
+
console.log(` ${color(label)} ${ids.length}`);
|
|
1201
|
+
for (const id of ids.slice(0, SAMPLE_LIMIT)) {
|
|
1202
|
+
console.log(` ${dimText(id)}`);
|
|
1203
|
+
}
|
|
1204
|
+
if (ids.length > SAMPLE_LIMIT) {
|
|
1205
|
+
console.log(dimText(` ... and ${ids.length - SAMPLE_LIMIT} more`));
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
function printDatabaseSection(report2) {
|
|
1209
|
+
console.log(`
|
|
1210
|
+
${header(" DB Completeness")}`);
|
|
1211
|
+
console.log(` ${separator(50)}`);
|
|
1212
|
+
console.log(` ${dimText("Databases:")} ${report2.databases.length}`);
|
|
1213
|
+
console.log(` ${dimText("Remote rows:")} ${report2.remoteTotal}`);
|
|
1214
|
+
console.log(` ${dimText("Vault rows:")} ${report2.localTotal}`);
|
|
1215
|
+
const broken = report2.databases.filter((d) => d.missingIds.length > 0 || d.extraIds.length > 0);
|
|
1216
|
+
if (broken.length > 0) {
|
|
1217
|
+
console.log(`
|
|
1218
|
+
${header(chalk7.red(`Incomplete databases: ${broken.length}`))}`);
|
|
1219
|
+
for (const db of broken) {
|
|
1220
|
+
console.log(
|
|
1221
|
+
` ${icons.fail} ${db.databaseId} ${dimText(`remote ${db.remoteRows} / vault ${db.localRows}`)}`
|
|
1222
|
+
);
|
|
1223
|
+
if (db.missingIds.length > 0) printIdSample("missing", db.missingIds, chalk7.red);
|
|
1224
|
+
if (db.extraIds.length > 0) printIdSample("extra", db.extraIds, chalk7.yellow);
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
if (report2.failures.length > 0) {
|
|
1228
|
+
console.log(`
|
|
1229
|
+
${header(chalk7.yellow(`Query failures: ${report2.failures.length}`))}`);
|
|
1230
|
+
for (const failure of report2.failures) {
|
|
1231
|
+
console.log(` ${icons.fail} ${failure.databaseId} ${dimText(failure.error)}`);
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
if (report2.complete) {
|
|
1235
|
+
console.log(`
|
|
1236
|
+
${chalk7.green("All database rows present in vault")} ${icons.success}`);
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
function printPageSection(report2) {
|
|
1240
|
+
console.log(`
|
|
1241
|
+
${header(" Page Completeness")}`);
|
|
1242
|
+
console.log(` ${separator(50)}`);
|
|
1243
|
+
console.log(` ${dimText("Remote pages:")} ${report2.remotePages}`);
|
|
1244
|
+
console.log(` ${dimText("Vault pages:")} ${report2.vaultPages}`);
|
|
1245
|
+
if (report2.error) {
|
|
1246
|
+
console.log(`
|
|
1247
|
+
${icons.fail} ${chalk7.yellow("Page enumeration failed")}`);
|
|
1248
|
+
console.log(` ${dimText(report2.error)}`);
|
|
1249
|
+
return;
|
|
1250
|
+
}
|
|
1251
|
+
if (report2.missingIds.length > 0) {
|
|
1252
|
+
console.log(
|
|
1253
|
+
`
|
|
1254
|
+
${header(chalk7.red(`Pages missing from vault: ${report2.missingIds.length}`))}`
|
|
1255
|
+
);
|
|
1256
|
+
printIdSample("missing", report2.missingIds, chalk7.red);
|
|
1257
|
+
}
|
|
1258
|
+
if (report2.localOnlyIds.length > 0) {
|
|
1259
|
+
console.log(
|
|
1260
|
+
`
|
|
1261
|
+
${dimText(`local-only pages: ${report2.localOnlyIds.length} (informational)`)}`
|
|
1262
|
+
);
|
|
1263
|
+
printIdSample("local-only", report2.localOnlyIds, dimText);
|
|
1264
|
+
}
|
|
1265
|
+
if (report2.complete) {
|
|
1266
|
+
console.log(`
|
|
1267
|
+
${chalk7.green("All remote pages present in vault")} ${icons.success}`);
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
function printReport(report2) {
|
|
1271
|
+
printDatabaseSection(report2.databases);
|
|
1272
|
+
if (report2.pages) {
|
|
1273
|
+
printPageSection(report2.pages);
|
|
1274
|
+
} else {
|
|
1275
|
+
console.log(`
|
|
1276
|
+
${dimText("Page completeness: skipped (database mode)")}`);
|
|
1277
|
+
}
|
|
1278
|
+
if (!report2.complete) {
|
|
1279
|
+
console.log(
|
|
1280
|
+
`
|
|
1281
|
+
${dimText("Run")} ${chalk7.cyan("nobsi pull")} ${dimText("to fetch what is missing")}`
|
|
1282
|
+
);
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
var verifyCommand = new Command11("verify").description("\uC644\uACB0\uC131 \uAC80\uC99D \u2014 \uC6D0\uACA9 DB \uD589\xB7\uD398\uC774\uC9C0\uAC00 \uBCFC\uD2B8\uC5D0 \uBE60\uC9D0\uC5C6\uC774 \uC788\uB294\uC9C0 \uB300\uC870 (API \uD638\uCD9C)").option("--json", "\uACB0\uACFC\uB97C JSON \uC73C\uB85C \uCD9C\uB825").action(async (opts) => {
|
|
1286
|
+
const cwd = process.cwd();
|
|
1287
|
+
const configManager = new ConfigManager11(cwd);
|
|
1288
|
+
const config = await configManager.load();
|
|
1289
|
+
const stateDb = StateDB10.open(configManager.dbPath);
|
|
1290
|
+
try {
|
|
1291
|
+
const client = NotionClient11.fromConfig(config);
|
|
1292
|
+
const vaultFs = new NodeVaultFS10(cwd, config.paths);
|
|
1293
|
+
const orchestrator = new SyncOrchestrator10(config, stateDb, client, vaultFs);
|
|
1294
|
+
const report2 = await orchestrator.verifyCompleteness();
|
|
1295
|
+
if (opts.json) {
|
|
1296
|
+
console.log(JSON.stringify(report2, null, 2));
|
|
1297
|
+
} else {
|
|
1298
|
+
printReport(report2);
|
|
1299
|
+
}
|
|
1300
|
+
if (!report2.complete) process.exitCode = 1;
|
|
1301
|
+
} finally {
|
|
1302
|
+
stateDb.close();
|
|
1303
|
+
}
|
|
1304
|
+
});
|
|
1305
|
+
|
|
1306
|
+
// src/utils/exit.ts
|
|
1307
|
+
var FORCED_EXIT_DELAY_MS = 100;
|
|
1308
|
+
function resolveExitCode(exitCode) {
|
|
1309
|
+
if (typeof exitCode === "number") return exitCode;
|
|
1310
|
+
if (typeof exitCode === "string") {
|
|
1311
|
+
const parsed = Number(exitCode);
|
|
1312
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
1313
|
+
}
|
|
1314
|
+
return 0;
|
|
1315
|
+
}
|
|
1316
|
+
function scheduleForcedExit(proc, schedule = setTimeout) {
|
|
1317
|
+
schedule(() => proc.exit(resolveExitCode(proc.exitCode)), FORCED_EXIT_DELAY_MS);
|
|
1318
|
+
}
|
|
1319
|
+
|
|
806
1320
|
// src/index.ts
|
|
807
1321
|
var requireJson = createRequire(import.meta.url);
|
|
808
1322
|
var { version } = requireJson("../package.json");
|
|
809
|
-
var program = new
|
|
1323
|
+
var program = new Command12();
|
|
810
1324
|
program.name("im-nobsidian").description("Im-Nobsidian: Obsidian \u2194 Notion \uC591\uBC29\uD5A5 \uB3D9\uAE30\uD654 CLI").version(version).option("--verbose", "\uC0C1\uC138 \uB85C\uADF8 \uCD9C\uB825").option("--quiet", "\uCD5C\uC18C \uCD9C\uB825");
|
|
811
1325
|
program.addCommand(initCommand);
|
|
812
1326
|
program.addCommand(pushCommand);
|
|
@@ -814,10 +1328,18 @@ program.addCommand(pullCommand);
|
|
|
814
1328
|
program.addCommand(syncCommand);
|
|
815
1329
|
program.addCommand(statusCommand);
|
|
816
1330
|
program.addCommand(diffCommand);
|
|
1331
|
+
program.addCommand(discardCommand);
|
|
817
1332
|
program.addCommand(fetchCommand);
|
|
818
1333
|
program.addCommand(resolveCommand);
|
|
819
1334
|
program.addCommand(watchCommand);
|
|
820
|
-
program.
|
|
821
|
-
|
|
822
|
-
|
|
1335
|
+
program.addCommand(verifyCommand);
|
|
1336
|
+
program.parseAsync().then(
|
|
1337
|
+
() => {
|
|
1338
|
+
scheduleForcedExit(process);
|
|
1339
|
+
},
|
|
1340
|
+
(error) => {
|
|
1341
|
+
reportStateDbFailure(error, process);
|
|
1342
|
+
scheduleForcedExit(process);
|
|
1343
|
+
}
|
|
1344
|
+
);
|
|
823
1345
|
//# sourceMappingURL=index.js.map
|