im-nobsidian 0.3.2 → 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 +499 -216
- 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,12 +48,15 @@ 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(" ");
|
|
@@ -61,18 +70,50 @@ function failedCountText(count) {
|
|
|
61
70
|
function duration(ms) {
|
|
62
71
|
return chalk.dim(`Done in ${(ms / 1e3).toFixed(1)}s`);
|
|
63
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
|
+
}
|
|
64
103
|
function progress(current, total) {
|
|
65
104
|
return chalk.dim(`[${current}/${total}]`);
|
|
66
105
|
}
|
|
67
106
|
function dimText(text) {
|
|
68
107
|
return chalk.dim(text);
|
|
69
108
|
}
|
|
70
|
-
function failedItem(path, error) {
|
|
71
|
-
|
|
109
|
+
function failedItem(path, error, direction) {
|
|
110
|
+
const where = `${direction ? `${direction} ` : ""}${path || "/"}`;
|
|
111
|
+
console.log(` ${icons.fail} ${where}: ${chalk.red(error)}`);
|
|
72
112
|
}
|
|
73
113
|
|
|
74
114
|
// src/commands/init.ts
|
|
75
|
-
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) => {
|
|
76
117
|
const cwd = process.cwd();
|
|
77
118
|
const configManager = new ConfigManager(cwd);
|
|
78
119
|
console.log(`
|
|
@@ -84,8 +125,14 @@ ${header(chalk2.cyan(" Im-Nobsidian Setup"))}`);
|
|
|
84
125
|
process.exitCode = 1;
|
|
85
126
|
return;
|
|
86
127
|
}
|
|
87
|
-
if (!options.token.startsWith(
|
|
88
|
-
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);
|
|
89
136
|
process.exitCode = 1;
|
|
90
137
|
return;
|
|
91
138
|
}
|
|
@@ -95,10 +142,9 @@ ${header(chalk2.cyan(" Im-Nobsidian Setup"))}`);
|
|
|
95
142
|
await client.search({ pageSize: 1, filter: { property: "object", value: "page" } });
|
|
96
143
|
spinner2.stop();
|
|
97
144
|
console.log(` ${icons.success} Token validated`);
|
|
98
|
-
await configManager.init({ token: options.token, rootPageId
|
|
145
|
+
await configManager.init({ token: options.token, rootPageId });
|
|
99
146
|
console.log(`
|
|
100
147
|
${icons.success} Config saved to ${dimText(configManager.configPath)}`);
|
|
101
|
-
console.log(` ${icons.success} State DB initialized`);
|
|
102
148
|
console.log(
|
|
103
149
|
`
|
|
104
150
|
${header(chalk2.green("Ready!"))} Run ${chalk2.cyan("nobsi sync")} to start syncing.`
|
|
@@ -120,7 +166,7 @@ ${header(chalk2.cyan(" Im-Nobsidian Setup"))}`);
|
|
|
120
166
|
}
|
|
121
167
|
const token = await input({
|
|
122
168
|
message: "Notion Integration Token:",
|
|
123
|
-
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`
|
|
124
170
|
});
|
|
125
171
|
const spinner = ora("Notion \uC5F0\uACB0 \uD655\uC778 \uC911...").start();
|
|
126
172
|
try {
|
|
@@ -144,14 +190,15 @@ ${header(chalk2.cyan(" Im-Nobsidian Setup"))}`);
|
|
|
144
190
|
const title = extractPageTitle(page);
|
|
145
191
|
console.log(` ${dimText(`${i + 1}.`)} ${title}`);
|
|
146
192
|
}
|
|
147
|
-
const
|
|
148
|
-
message: "\uB8E8\uD2B8 \uD398\uC774\uC9C0 ID (\uC704\uC5D0\uC11C \uC120\uD0DD\uD558\uAC70\uB098 \uC9C1\uC811 \uC785\uB825):",
|
|
149
|
-
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
|
|
150
197
|
});
|
|
198
|
+
const rootPageId = notionIdFromInput(rootPageInput);
|
|
151
199
|
await configManager.init({ token, rootPageId });
|
|
152
200
|
console.log(`
|
|
153
201
|
${icons.success} Config saved to ${dimText(configManager.configPath)}`);
|
|
154
|
-
console.log(` ${icons.success} State DB initialized`);
|
|
155
202
|
console.log(
|
|
156
203
|
`
|
|
157
204
|
${header(chalk2.green("Ready!"))} Run ${chalk2.cyan("nobsi sync")} to start syncing.`
|
|
@@ -183,7 +230,51 @@ import {
|
|
|
183
230
|
SyncOrchestrator,
|
|
184
231
|
NodeVaultFS
|
|
185
232
|
} from "@im-nobsidian/core";
|
|
186
|
-
|
|
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();
|
|
187
278
|
const cwd = process.cwd();
|
|
188
279
|
const configManager = new ConfigManager2(cwd);
|
|
189
280
|
const config = await configManager.load();
|
|
@@ -192,25 +283,32 @@ var pushCommand = new Command2("push").description("\uB85C\uCEEC \uBCC0\uACBD\uC
|
|
|
192
283
|
const client = NotionClient2.fromConfig(config);
|
|
193
284
|
const vaultFs = new NodeVaultFS(cwd, config.paths);
|
|
194
285
|
const orchestrator = new SyncOrchestrator(config, stateDb, client, vaultFs);
|
|
195
|
-
|
|
286
|
+
if (!json) {
|
|
287
|
+
console.log(`
|
|
196
288
|
${header(" Pushing to Notion...")}`);
|
|
197
|
-
|
|
198
|
-
|
|
289
|
+
if (options.dryRun) console.log(dimText(" (dry-run mode)"));
|
|
290
|
+
console.log("");
|
|
291
|
+
}
|
|
199
292
|
const result = await orchestrator.push({
|
|
200
293
|
dryRun: options.dryRun,
|
|
201
294
|
paths: options.path,
|
|
202
|
-
onProgress: (current, total, item) => {
|
|
295
|
+
onProgress: json ? void 0 : (current, total, item) => {
|
|
203
296
|
const icon = operationIcon(item.operation);
|
|
204
297
|
const label = operationLabel(item.operation);
|
|
205
298
|
const prog = progress(current, total);
|
|
206
299
|
console.log(` ${icon} ${item.path} ${prog} ${label}`);
|
|
207
300
|
}
|
|
208
301
|
});
|
|
302
|
+
if (json) {
|
|
303
|
+
printJson(pushJson(result));
|
|
304
|
+
if (result.failed.length > 0) process.exitCode = 1;
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
209
307
|
const failedCount = result.failed.length;
|
|
210
308
|
console.log(`
|
|
211
309
|
${completionHeader("Push", failedCount)}`);
|
|
212
310
|
console.log(
|
|
213
|
-
` ${summary(result.created, result.updated, result.deleted)} ${failedCountText(failedCount)}`
|
|
311
|
+
` ${summary(result.created, result.updated, result.deleted, result.moved)} ${failedCountText(failedCount)}`
|
|
214
312
|
);
|
|
215
313
|
console.log(` ${duration(result.duration)}`);
|
|
216
314
|
if (failedCount > 0) {
|
|
@@ -226,7 +324,7 @@ ${header(" Pushing to Notion...")}`);
|
|
|
226
324
|
});
|
|
227
325
|
|
|
228
326
|
// src/commands/pull.ts
|
|
229
|
-
import { Command as Command3 } from "commander";
|
|
327
|
+
import { Command as Command3, Option } from "commander";
|
|
230
328
|
import {
|
|
231
329
|
ConfigManager as ConfigManager3,
|
|
232
330
|
StateDB as StateDB2,
|
|
@@ -238,7 +336,14 @@ import chalk3 from "chalk";
|
|
|
238
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(
|
|
239
337
|
"--force",
|
|
240
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)"
|
|
241
|
-
).
|
|
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();
|
|
242
347
|
const cwd = process.cwd();
|
|
243
348
|
const configManager = new ConfigManager3(cwd);
|
|
244
349
|
const config = await configManager.load();
|
|
@@ -247,29 +352,40 @@ var pullCommand = new Command3("pull").description("Notion \uBCC0\uACBD\uC0AC\uD
|
|
|
247
352
|
const client = NotionClient3.fromConfig(config);
|
|
248
353
|
const vaultFs = new NodeVaultFS2(cwd, config.paths);
|
|
249
354
|
const orchestrator = new SyncOrchestrator2(config, stateDb, client, vaultFs);
|
|
250
|
-
|
|
355
|
+
if (!json) {
|
|
356
|
+
console.log(`
|
|
251
357
|
${header(" Pulling from Notion...")}`);
|
|
252
|
-
|
|
253
|
-
|
|
358
|
+
if (options.dryRun) console.log(dimText(" (dry-run mode)"));
|
|
359
|
+
console.log("");
|
|
360
|
+
}
|
|
254
361
|
const result = await orchestrator.pull({
|
|
255
362
|
dryRun: options.dryRun,
|
|
256
363
|
paths: options.path,
|
|
257
364
|
force: options.force,
|
|
258
|
-
|
|
365
|
+
rerender: options.rerender,
|
|
366
|
+
onProgress: json ? void 0 : (current, total, item) => {
|
|
367
|
+
if (item.unchanged) return;
|
|
259
368
|
const icon = operationIcon(item.operation);
|
|
260
369
|
const label = operationLabel(item.operation);
|
|
261
370
|
const prog = progress(current, total);
|
|
262
371
|
console.log(` ${icon} ${item.path} ${prog} ${label}`);
|
|
263
372
|
}
|
|
264
373
|
});
|
|
374
|
+
if (json) {
|
|
375
|
+
printJson(pullJson(result));
|
|
376
|
+
if (result.failed.length > 0) process.exitCode = 1;
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
265
379
|
if (result.imageCount > 0) {
|
|
266
380
|
console.log(
|
|
267
381
|
`
|
|
268
|
-
${icons.success} ${result.imageCount} images \u2192 ${dimText(
|
|
382
|
+
${icons.success} ${result.imageCount} images \u2192 ${dimText(`${config.paths.attachments}/`)}`
|
|
269
383
|
);
|
|
270
384
|
}
|
|
271
385
|
if (result.fileCount > 0) {
|
|
272
|
-
console.log(
|
|
386
|
+
console.log(
|
|
387
|
+
` ${icons.success} ${result.fileCount} files \u2192 ${dimText(`${config.paths.attachments}/`)}`
|
|
388
|
+
);
|
|
273
389
|
}
|
|
274
390
|
if (result.linkCount > 0) {
|
|
275
391
|
console.log(` ${icons.success} ${result.linkCount} links resolved`);
|
|
@@ -284,6 +400,9 @@ ${header(" Pulling from Notion...")}`);
|
|
|
284
400
|
console.log(` ${icons.success} ${chalk3.yellow(`${result.restored} restored`)}`);
|
|
285
401
|
}
|
|
286
402
|
console.log(` ${duration(result.duration)}`);
|
|
403
|
+
if (result.remoteScan) {
|
|
404
|
+
for (const line of remoteScanLines(result.remoteScan)) console.log(` ${line}`);
|
|
405
|
+
}
|
|
287
406
|
if (result.conflicts.length > 0) {
|
|
288
407
|
console.log(
|
|
289
408
|
`
|
|
@@ -312,7 +431,11 @@ import {
|
|
|
312
431
|
NodeVaultFS as NodeVaultFS3
|
|
313
432
|
} from "@im-nobsidian/core";
|
|
314
433
|
import chalk4 from "chalk";
|
|
315
|
-
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) => {
|
|
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);
|
|
316
439
|
const cwd = process.cwd();
|
|
317
440
|
const configManager = new ConfigManager4(cwd);
|
|
318
441
|
const config = await configManager.load();
|
|
@@ -321,37 +444,44 @@ var syncCommand = new Command4("sync").description("\uC591\uBC29\uD5A5 \uB3D9\uA
|
|
|
321
444
|
const client = NotionClient4.fromConfig(config);
|
|
322
445
|
const vaultFs = new NodeVaultFS3(cwd, config.paths);
|
|
323
446
|
const orchestrator = new SyncOrchestrator3(config, stateDb, client, vaultFs);
|
|
324
|
-
|
|
447
|
+
log(`
|
|
325
448
|
${header(" Bidirectional Sync")}`);
|
|
326
|
-
|
|
327
|
-
if (options.dryRun)
|
|
328
|
-
|
|
449
|
+
log(` ${separator()}`);
|
|
450
|
+
if (options.dryRun) log(dimText(" (dry-run mode)"));
|
|
451
|
+
log(`
|
|
329
452
|
${header(chalk4.blue("\u25BC Pull"))} ${dimText("(Notion \u2192 Obsidian)")}`);
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
console.log(` ${icon} ${item.path} ${label}`);
|
|
336
|
-
}
|
|
337
|
-
});
|
|
338
|
-
if (pullResult.imageCount > 0) {
|
|
339
|
-
console.log(` ${icons.success} ${pullResult.imageCount} images downloaded`);
|
|
340
|
-
}
|
|
341
|
-
if (pullResult.linkCount > 0) {
|
|
342
|
-
console.log(` ${icons.success} ${pullResult.linkCount} links resolved`);
|
|
343
|
-
}
|
|
344
|
-
console.log(`
|
|
453
|
+
let pushShown = false;
|
|
454
|
+
const showPush = () => {
|
|
455
|
+
if (pushShown) return;
|
|
456
|
+
pushShown = true;
|
|
457
|
+
log(`
|
|
345
458
|
${header(chalk4.magenta("\u25B2 Push"))} ${dimText("(Obsidian \u2192 Notion)")}`);
|
|
346
|
-
|
|
459
|
+
};
|
|
460
|
+
const result = await orchestrator.sync({
|
|
347
461
|
dryRun: options.dryRun,
|
|
348
462
|
onProgress: (_current, _total, item) => {
|
|
463
|
+
if (item.direction === "push") showPush();
|
|
464
|
+
if (item.unchanged) return;
|
|
349
465
|
const icon = operationIcon(item.operation);
|
|
350
466
|
const label = operationLabel(item.operation);
|
|
351
|
-
|
|
467
|
+
log(` ${icon} ${item.path} ${label}`);
|
|
352
468
|
}
|
|
353
469
|
});
|
|
354
|
-
|
|
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
|
+
}
|
|
355
485
|
const pullFailed = pullResult.failed.length;
|
|
356
486
|
const pushFailed = pushResult.failed.length;
|
|
357
487
|
console.log(`
|
|
@@ -360,20 +490,29 @@ ${header(" Bidirectional Sync")}`);
|
|
|
360
490
|
` ${chalk4.blue("Pull:")} ${summary(pullResult.created, pullResult.updated, pullResult.deleted)}` + // 사라졌던 파일이 되살아난 경우에만 덧붙인다 — 평상시 요약을 어지럽히지 않는다.
|
|
361
491
|
(pullResult.restored > 0 ? ` ${chalk4.yellow(`${pullResult.restored} restored`)}` : "") + (pullFailed > 0 ? ` ${chalk4.red(`${pullFailed} failed`)}` : "")
|
|
362
492
|
);
|
|
493
|
+
if (pullResult.imageCount > 0) {
|
|
494
|
+
console.log(` ${icons.success} ${pullResult.imageCount} images downloaded`);
|
|
495
|
+
}
|
|
496
|
+
if (pullResult.linkCount > 0) {
|
|
497
|
+
console.log(` ${icons.success} ${pullResult.linkCount} links resolved`);
|
|
498
|
+
}
|
|
363
499
|
console.log(
|
|
364
|
-
` ${chalk4.magenta("Push:")} ${summary(pushResult.created, pushResult.updated, pushResult.deleted)}` + (pushFailed > 0 ? ` ${chalk4.red(`${pushFailed} failed`)}` : "")
|
|
500
|
+
` ${chalk4.magenta("Push:")} ${summary(pushResult.created, pushResult.updated, pushResult.deleted, pushResult.moved)}` + (pushFailed > 0 ? ` ${chalk4.red(`${pushFailed} failed`)}` : "")
|
|
365
501
|
);
|
|
366
|
-
console.log(` ${duration(
|
|
367
|
-
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) {
|
|
368
507
|
console.log(
|
|
369
508
|
`
|
|
370
|
-
${icons.conflict} ${chalk4.magenta(`${
|
|
509
|
+
${icons.conflict} ${chalk4.magenta(`${result.conflicts.length} conflicts`)} \u2014 run ${chalk4.cyan("nobsi resolve")}`
|
|
371
510
|
);
|
|
372
511
|
}
|
|
373
512
|
if (pullFailed + pushFailed > 0) {
|
|
374
513
|
console.log("");
|
|
375
|
-
for (const f of pullResult.failed) failedItem(
|
|
376
|
-
for (const f of pushResult.failed) failedItem(
|
|
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");
|
|
377
516
|
process.exitCode = 1;
|
|
378
517
|
}
|
|
379
518
|
} finally {
|
|
@@ -388,7 +527,8 @@ import {
|
|
|
388
527
|
StateDB as StateDB4,
|
|
389
528
|
NotionClient as NotionClient5,
|
|
390
529
|
SyncOrchestrator as SyncOrchestrator4,
|
|
391
|
-
NodeVaultFS as NodeVaultFS4
|
|
530
|
+
NodeVaultFS as NodeVaultFS4,
|
|
531
|
+
isRemoteDeletion
|
|
392
532
|
} from "@im-nobsidian/core";
|
|
393
533
|
import chalk5 from "chalk";
|
|
394
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) => {
|
|
@@ -409,21 +549,22 @@ ${header(" Sync Status")}`);
|
|
|
409
549
|
console.log(` ${dimText("Root page:")} ${rootId.slice(0, 8)}...`);
|
|
410
550
|
console.log(` ${dimText("Direction:")} ${direction}`);
|
|
411
551
|
if (status.lastSyncAt) {
|
|
412
|
-
|
|
413
|
-
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");
|
|
414
|
-
console.log(` ${dimText("Last sync:")} ${iso}`);
|
|
552
|
+
console.log(` ${dimText("Last sync:")} ${localDateTime(status.lastSyncAt)}`);
|
|
415
553
|
} else {
|
|
416
554
|
console.log(` ${dimText("Last sync:")} ${chalk5.yellow("never")}`);
|
|
417
555
|
}
|
|
556
|
+
console.log(` ${dimText("Full scan:")} ${lastFullScanText(status.lastFullScanAt)}`);
|
|
418
557
|
const allRecords = stateDb.getAll();
|
|
419
558
|
const total = allRecords.length;
|
|
420
559
|
const conflictCount = status.conflictRecords.length;
|
|
421
560
|
const createdChanges = status.localChanges.filter((c) => c.type === "created");
|
|
422
561
|
const modifiedChanges = status.localChanges.filter((c) => c.type === "modified");
|
|
423
562
|
const deletedChanges = status.localChanges.filter((c) => c.type === "deleted");
|
|
563
|
+
const movedChanges = status.localChanges.filter((c) => c.type === "moved");
|
|
424
564
|
const dirtyPaths = /* @__PURE__ */ new Set([
|
|
425
565
|
...modifiedChanges.map((c) => c.path),
|
|
426
566
|
...deletedChanges.map((c) => c.path),
|
|
567
|
+
...movedChanges.flatMap((c) => [c.path, c.movedFrom ?? c.path]),
|
|
427
568
|
...status.conflictRecords.map((r) => r.obsidianPath)
|
|
428
569
|
]);
|
|
429
570
|
const synced = allRecords.filter(
|
|
@@ -432,12 +573,17 @@ ${header(" Sync Status")}`);
|
|
|
432
573
|
const remoteCreated = status.remoteChanges.filter((c) => c.type === "created");
|
|
433
574
|
const remoteModified = status.remoteChanges.filter((c) => c.type === "modified");
|
|
434
575
|
const remoteDeleted = status.remoteChanges.filter((c) => c.type === "deleted");
|
|
576
|
+
const remoteMoved = status.remoteChanges.filter((c) => c.type === "moved");
|
|
577
|
+
const { folderMoves } = status;
|
|
435
578
|
console.log(`
|
|
436
579
|
${header(`Tracked files: ${total}`)}`);
|
|
437
580
|
console.log(` ${icons.synced} ${chalk5.green("synced")} ${synced}`);
|
|
438
581
|
if (modifiedChanges.length > 0) {
|
|
439
582
|
console.log(` ${icons.modified} ${chalk5.yellow("modified")} ${modifiedChanges.length}`);
|
|
440
583
|
}
|
|
584
|
+
if (movedChanges.length > 0) {
|
|
585
|
+
console.log(` ${icons.move} ${chalk5.cyan("moved")} ${movedChanges.length}`);
|
|
586
|
+
}
|
|
441
587
|
if (deletedChanges.length > 0) {
|
|
442
588
|
console.log(` ${chalk5.red("-")} ${chalk5.red("deleted")} ${deletedChanges.length}`);
|
|
443
589
|
}
|
|
@@ -461,6 +607,29 @@ ${header(" Sync Status")}`);
|
|
|
461
607
|
console.log(dimText(` ... and ${modifiedChanges.length - 10} more`));
|
|
462
608
|
}
|
|
463
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
|
+
}
|
|
464
633
|
if (createdChanges.length > 0) {
|
|
465
634
|
console.log(`
|
|
466
635
|
${header("New files:")}`);
|
|
@@ -482,12 +651,18 @@ ${header(" Sync Status")}`);
|
|
|
482
651
|
` ${chalk5.yellow("~")} ${chalk5.yellow("modified")} ${remoteModified.length}`
|
|
483
652
|
);
|
|
484
653
|
}
|
|
654
|
+
if (remoteMoved.length > 0) {
|
|
655
|
+
console.log(` ${icons.move} ${chalk5.cyan("moved")} ${remoteMoved.length}`);
|
|
656
|
+
}
|
|
485
657
|
if (remoteDeleted.length > 0) {
|
|
486
658
|
console.log(` ${chalk5.red("-")} ${chalk5.red("deleted")} ${remoteDeleted.length}`);
|
|
487
659
|
}
|
|
488
660
|
for (const c of status.remoteChanges.slice(0, 10)) {
|
|
489
|
-
const icon = c.type === "created" ? chalk5.cyan("+") : c.type === "modified" ? chalk5.yellow("~") : chalk5.red("-");
|
|
490
|
-
|
|
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
|
+
);
|
|
491
666
|
}
|
|
492
667
|
if (status.remoteChanges.length > 10) {
|
|
493
668
|
console.log(dimText(` ... and ${status.remoteChanges.length - 10} more`));
|
|
@@ -496,13 +671,15 @@ ${header(" Sync Status")}`);
|
|
|
496
671
|
if (conflictCount > 0) {
|
|
497
672
|
console.log(`
|
|
498
673
|
${header(chalk5.magenta("Conflicts:"))}`);
|
|
674
|
+
const remoteDeleted2 = new Set(
|
|
675
|
+
status.conflicts.filter(isRemoteDeletion).map((c) => c.syncRecord.obsidianPath)
|
|
676
|
+
);
|
|
499
677
|
for (const r of status.conflictRecords) {
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
);
|
|
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)}`);
|
|
503
680
|
}
|
|
504
681
|
}
|
|
505
|
-
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;
|
|
506
683
|
if (hasChanges) {
|
|
507
684
|
console.log("");
|
|
508
685
|
if (conflictCount > 0) {
|
|
@@ -513,6 +690,11 @@ ${header(" Sync Status")}`);
|
|
|
513
690
|
console.log(
|
|
514
691
|
` ${dimText("Run")} ${chalk5.cyan("nobsi sync")} ${dimText("to push/pull changes")}`
|
|
515
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
|
+
}
|
|
516
698
|
} else {
|
|
517
699
|
console.log(`
|
|
518
700
|
${chalk5.green("Everything up to date")} \u2713`);
|
|
@@ -522,6 +704,9 @@ ${header(" Sync Status")}`);
|
|
|
522
704
|
`
|
|
523
705
|
${dimText("Run")} ${chalk5.cyan("nobsi status --full")} ${dimText("to check Notion remote changes")}`
|
|
524
706
|
);
|
|
707
|
+
} else if (status.remoteScan?.deletionsDeferred) {
|
|
708
|
+
console.log(`
|
|
709
|
+
${remoteDeletionHint(status.remoteScan)}`);
|
|
525
710
|
}
|
|
526
711
|
} finally {
|
|
527
712
|
stateDb.close();
|
|
@@ -536,9 +721,31 @@ import {
|
|
|
536
721
|
NotionClient as NotionClient6,
|
|
537
722
|
SyncOrchestrator as SyncOrchestrator5,
|
|
538
723
|
NodeVaultFS as NodeVaultFS5,
|
|
724
|
+
formatUnifiedDiff,
|
|
725
|
+
lineDiff,
|
|
539
726
|
matchesPathScope
|
|
540
727
|
} from "@im-nobsidian/core";
|
|
541
|
-
|
|
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
|
|
542
749
|
var diffCommand = new Command6("diff").description(
|
|
543
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)"
|
|
544
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) => {
|
|
@@ -556,10 +763,15 @@ var diffCommand = new Command6("diff").description(
|
|
|
556
763
|
console.log("\uBCC0\uACBD\uC0AC\uD56D \uC5C6\uC74C");
|
|
557
764
|
return;
|
|
558
765
|
}
|
|
766
|
+
let failed = 0;
|
|
559
767
|
for (const change of changes) {
|
|
560
|
-
const record = stateDb.getByPath(change.path);
|
|
561
768
|
if (options.remote && change.type !== "deleted") {
|
|
562
|
-
|
|
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
|
+
}
|
|
563
775
|
if (remote === null) {
|
|
564
776
|
console.log(`--- (\uC6D0\uACA9 \uC5C6\uC74C) ${change.path}`);
|
|
565
777
|
console.log(`+++ b/${change.path}`);
|
|
@@ -568,76 +780,49 @@ var diffCommand = new Command6("diff").description(
|
|
|
568
780
|
continue;
|
|
569
781
|
}
|
|
570
782
|
const currentContent = await vaultFs.readFile(change.path);
|
|
571
|
-
const
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
currentContent
|
|
576
|
-
);
|
|
577
|
-
printPatch(patch, options.color, `notion/${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);
|
|
578
787
|
console.log("");
|
|
579
788
|
continue;
|
|
580
789
|
}
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
} else {
|
|
591
|
-
console.log(`--- a/${change.path}`);
|
|
592
|
-
console.log(`+++ /dev/null`);
|
|
593
|
-
console.log(`(\uC774\uC804 \uC2A4\uB0C5\uC0F7 \uC5C6\uC74C)`);
|
|
594
|
-
}
|
|
595
|
-
} else if (change.type === "modified" && record?.baseSnapshot) {
|
|
596
|
-
const currentContent = await vaultFs.readFile(change.path);
|
|
597
|
-
const baseContent = record.baseSnapshot.toString("utf-8");
|
|
598
|
-
const patch = createTwoFilesPatch(
|
|
599
|
-
`a/${change.path}`,
|
|
600
|
-
`b/${change.path}`,
|
|
601
|
-
baseContent,
|
|
602
|
-
currentContent
|
|
603
|
-
);
|
|
604
|
-
printPatch(patch, options.color, `a/${change.path}`, `b/${change.path}`);
|
|
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;
|
|
605
799
|
}
|
|
800
|
+
printChangeDiff(diff, options.color);
|
|
606
801
|
console.log("");
|
|
607
802
|
}
|
|
803
|
+
if (failed > 0) process.exitCode = 1;
|
|
608
804
|
} finally {
|
|
609
805
|
stateDb.close();
|
|
610
806
|
}
|
|
611
807
|
});
|
|
612
|
-
function
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
}
|
|
622
|
-
if (!color) {
|
|
623
|
-
console.log(output);
|
|
624
|
-
continue;
|
|
625
|
-
}
|
|
626
|
-
if (output.startsWith("+++") || output.startsWith("---")) {
|
|
627
|
-
console.log(`\x1B[1m${output}\x1B[0m`);
|
|
628
|
-
} else if (output.startsWith("+")) {
|
|
629
|
-
console.log(`\x1B[32m${output}\x1B[0m`);
|
|
630
|
-
} else if (output.startsWith("-")) {
|
|
631
|
-
console.log(`\x1B[31m${output}\x1B[0m`);
|
|
632
|
-
} else if (output.startsWith("@@")) {
|
|
633
|
-
console.log(`\x1B[36m${output}\x1B[0m`);
|
|
634
|
-
} else {
|
|
635
|
-
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;
|
|
636
817
|
}
|
|
637
818
|
}
|
|
819
|
+
printUnifiedDiff(
|
|
820
|
+
formatUnifiedDiff(lineDiff(diff.before ?? "", diff.after ?? ""), oldPath, newPath),
|
|
821
|
+
color
|
|
822
|
+
);
|
|
638
823
|
}
|
|
639
824
|
|
|
640
|
-
// src/commands/
|
|
825
|
+
// src/commands/discard.ts
|
|
641
826
|
import { Command as Command7 } from "commander";
|
|
642
827
|
import {
|
|
643
828
|
ConfigManager as ConfigManager7,
|
|
@@ -646,8 +831,7 @@ import {
|
|
|
646
831
|
SyncOrchestrator as SyncOrchestrator6,
|
|
647
832
|
NodeVaultFS as NodeVaultFS6
|
|
648
833
|
} from "@im-nobsidian/core";
|
|
649
|
-
|
|
650
|
-
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) => {
|
|
651
835
|
const cwd = process.cwd();
|
|
652
836
|
const configManager = new ConfigManager7(cwd);
|
|
653
837
|
const config = await configManager.load();
|
|
@@ -656,6 +840,41 @@ var fetchCommand = new Command7("fetch").description("Notion \uB9AC\uBAA8\uD2B8
|
|
|
656
840
|
const client = NotionClient7.fromConfig(config);
|
|
657
841
|
const vaultFs = new NodeVaultFS6(cwd, config.paths);
|
|
658
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);
|
|
659
878
|
console.log(dimText(" Scanning remote pages..."));
|
|
660
879
|
const result = await orchestrator.fetch();
|
|
661
880
|
console.log(`
|
|
@@ -687,34 +906,68 @@ ${header(" Remote Scan")}`);
|
|
|
687
906
|
});
|
|
688
907
|
|
|
689
908
|
// src/commands/resolve.ts
|
|
690
|
-
import { Command as
|
|
909
|
+
import { Command as Command9 } from "commander";
|
|
691
910
|
import { select, confirm as confirm2 } from "@inquirer/prompts";
|
|
692
911
|
import {
|
|
693
|
-
ConfigManager as
|
|
694
|
-
StateDB as
|
|
695
|
-
NotionClient as
|
|
696
|
-
SyncOrchestrator as
|
|
697
|
-
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
|
|
698
919
|
} from "@im-nobsidian/core";
|
|
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
|
|
699
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
|
+
}
|
|
700
953
|
function isInteractive() {
|
|
701
954
|
return process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
702
955
|
}
|
|
703
|
-
var resolveCommand = new
|
|
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) => {
|
|
704
957
|
if (options.strategy && !STRATEGIES.includes(options.strategy)) {
|
|
705
958
|
console.error(`\uC54C \uC218 \uC5C6\uB294 \uC804\uB7B5 '${options.strategy}' \u2014 \uC0AC\uC6A9 \uAC00\uB2A5: ${STRATEGIES.join(", ")}`);
|
|
706
959
|
process.exitCode = 1;
|
|
707
960
|
return;
|
|
708
961
|
}
|
|
709
962
|
const cwd = process.cwd();
|
|
710
|
-
const configManager = new
|
|
963
|
+
const configManager = new ConfigManager9(cwd);
|
|
711
964
|
let stateDb;
|
|
712
965
|
try {
|
|
713
966
|
const config = await configManager.load();
|
|
714
|
-
stateDb =
|
|
715
|
-
const client =
|
|
716
|
-
const vaultFs = new
|
|
717
|
-
const orchestrator = new
|
|
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);
|
|
718
971
|
const conflicts = await orchestrator.listConflicts();
|
|
719
972
|
if (conflicts.length === 0) {
|
|
720
973
|
console.log("\uCDA9\uB3CC\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
@@ -733,8 +986,20 @@ ${cleared.size}\uAC74 \uBAA8\uB450 \uC774\uBBF8 \uD574\uC18C\uB41C \uC0C1\uD0DC\
|
|
|
733
986
|
console.log(`
|
|
734
987
|
\uCDA9\uB3CC ${pending.length}\uAC74 \uBC1C\uACAC
|
|
735
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
|
+
}
|
|
736
995
|
if (options.strategy) {
|
|
737
996
|
const strategy = options.strategy;
|
|
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
|
+
);
|
|
1002
|
+
}
|
|
738
1003
|
if (!options.yes) {
|
|
739
1004
|
if (!isInteractive()) {
|
|
740
1005
|
console.error(
|
|
@@ -750,17 +1015,20 @@ ${cleared.size}\uAC74 \uBAA8\uB450 \uC774\uBBF8 \uD574\uC18C\uB41C \uC0C1\uD0DC\
|
|
|
750
1015
|
}
|
|
751
1016
|
const results = await orchestrator.resolveAllConflicts(pending, strategy);
|
|
752
1017
|
let failed2 = 0;
|
|
753
|
-
for (const result of results) {
|
|
1018
|
+
for (const [index, result] of results.entries()) {
|
|
754
1019
|
if (!result.success) failed2++;
|
|
755
1020
|
const icon = result.success ? "\x1B[32m\u2713\x1B[0m" : "\x1B[31m\u2717\x1B[0m";
|
|
756
|
-
|
|
1021
|
+
const reason = result.error ? ` \u2014 ${result.error}` : "";
|
|
1022
|
+
console.log(
|
|
1023
|
+
` ${icon} ${result.path} \u2192 ${choiceName(pending[index], result.choice, uploads)}${reason}`
|
|
1024
|
+
);
|
|
757
1025
|
}
|
|
758
1026
|
report(results.length - failed2, failed2);
|
|
759
1027
|
return;
|
|
760
1028
|
}
|
|
761
1029
|
if (!isInteractive()) {
|
|
762
1030
|
console.error(
|
|
763
|
-
|
|
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.`
|
|
764
1032
|
);
|
|
765
1033
|
process.exitCode = 1;
|
|
766
1034
|
return;
|
|
@@ -768,12 +1036,12 @@ ${cleared.size}\uAC74 \uBAA8\uB450 \uC774\uBBF8 \uD574\uC18C\uB41C \uC0C1\uD0DC\
|
|
|
768
1036
|
let resolved = 0;
|
|
769
1037
|
let failed = 0;
|
|
770
1038
|
for (const conflict of pending) {
|
|
771
|
-
if (await resolveInteractive(conflict, orchestrator)) resolved++;
|
|
1039
|
+
if (await resolveInteractive(conflict, orchestrator, uploads)) resolved++;
|
|
772
1040
|
else failed++;
|
|
773
1041
|
}
|
|
774
1042
|
report(resolved, failed);
|
|
775
1043
|
} catch (error) {
|
|
776
|
-
console.error("\uC624\uB958:",
|
|
1044
|
+
console.error("\uC624\uB958:", describeFailure(error));
|
|
777
1045
|
process.exitCode = 1;
|
|
778
1046
|
} finally {
|
|
779
1047
|
stateDb?.close();
|
|
@@ -789,37 +1057,29 @@ function report(resolved, failed) {
|
|
|
789
1057
|
\x1B[33m\uCDA9\uB3CC ${resolved}\uAC74 \uD574\uACB0 \xB7 ${failed}\uAC74 \uBBF8\uD574\uACB0\x1B[0m`);
|
|
790
1058
|
process.exitCode = 1;
|
|
791
1059
|
}
|
|
792
|
-
async function resolveInteractive(conflict, orchestrator) {
|
|
1060
|
+
async function resolveInteractive(conflict, orchestrator, uploads) {
|
|
793
1061
|
const path = conflict.syncRecord.obsidianPath;
|
|
794
1062
|
console.log(`
|
|
795
1063
|
${"\u2500".repeat(60)}`);
|
|
796
1064
|
console.log(`\uD30C\uC77C: \x1B[1m${path}\x1B[0m`);
|
|
797
1065
|
console.log(`${"\u2500".repeat(60)}`);
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
console.log(`\x1B[32m${line}\x1B[0m`);
|
|
805
|
-
} else if (line.startsWith("---") || line.startsWith("+++")) {
|
|
806
|
-
console.log(`\x1B[1m${line}\x1B[0m`);
|
|
807
|
-
} else {
|
|
808
|
-
console.log(line);
|
|
809
|
-
}
|
|
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);
|
|
810
1072
|
}
|
|
811
1073
|
const choice = await select({
|
|
812
1074
|
message: "\uC5B4\uB5BB\uAC8C \uD574\uACB0\uD560\uAE4C\uC694?",
|
|
813
|
-
choices:
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
{ name: "\uBCF5\uC81C (\uC6D0\uBCF8 \uC720\uC9C0 + .conflict \uD30C\uC77C \uC0DD\uC131)", value: "duplicate" }
|
|
818
|
-
]
|
|
1075
|
+
choices: applicableChoices(conflict).map((value) => ({
|
|
1076
|
+
name: choiceName(conflict, value, uploads),
|
|
1077
|
+
value
|
|
1078
|
+
}))
|
|
819
1079
|
});
|
|
820
1080
|
const result = await orchestrator.resolveConflict(conflict, choice);
|
|
821
1081
|
if (result.success) {
|
|
822
|
-
console.log(`\x1B[32m\u2713 ${path} \u2192 ${choice}\
|
|
1082
|
+
console.log(`\x1B[32m\u2713 ${path} \u2192 ${choiceName(conflict, choice, uploads)}\x1B[0m`);
|
|
823
1083
|
return true;
|
|
824
1084
|
}
|
|
825
1085
|
if (result.mergeHadConflicts) {
|
|
@@ -831,18 +1091,20 @@ ${"\u2500".repeat(60)}`);
|
|
|
831
1091
|
}
|
|
832
1092
|
|
|
833
1093
|
// src/commands/watch.ts
|
|
834
|
-
import { Command as
|
|
1094
|
+
import { Command as Command10 } from "commander";
|
|
835
1095
|
import {
|
|
836
|
-
ConfigManager as
|
|
837
|
-
StateDB as
|
|
838
|
-
NotionClient as
|
|
839
|
-
SyncOrchestrator as
|
|
840
|
-
NodeVaultFS as
|
|
841
|
-
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
|
|
842
1103
|
} from "@im-nobsidian/core";
|
|
843
1104
|
function formatResult(result) {
|
|
844
1105
|
const pull = `Pull: +${result.pull.created} ~${result.pull.updated} -${result.pull.deleted}`;
|
|
845
|
-
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}`;
|
|
846
1108
|
const parts = [pull, push];
|
|
847
1109
|
if (result.conflicts.length > 0) {
|
|
848
1110
|
parts.push(`\uCDA9\uB3CC ${result.conflicts.length}\uAC74`);
|
|
@@ -852,35 +1114,59 @@ function formatResult(result) {
|
|
|
852
1114
|
function timestamp() {
|
|
853
1115
|
return (/* @__PURE__ */ new Date()).toLocaleTimeString("ko-KR", { hour12: false });
|
|
854
1116
|
}
|
|
855
|
-
|
|
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
|
+
}
|
|
856
1133
|
const cwd = process.cwd();
|
|
857
|
-
const configManager = new
|
|
1134
|
+
const configManager = new ConfigManager10(cwd);
|
|
858
1135
|
const config = await configManager.load();
|
|
859
|
-
const stateDb =
|
|
860
|
-
const client =
|
|
861
|
-
const vaultFs = new
|
|
862
|
-
const orchestrator = new
|
|
863
|
-
const debounceMs = parseInt(options.debounce, 10);
|
|
864
|
-
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);
|
|
865
1140
|
const service = new WatchSyncService(cwd, orchestrator, {
|
|
866
1141
|
debounceMs,
|
|
867
1142
|
onFileChange: (event, path) => {
|
|
868
1143
|
console.log(`[${timestamp()}] ${event}: ${path}`);
|
|
869
1144
|
},
|
|
870
|
-
onSyncStart: () => {
|
|
871
|
-
console.log(`[${timestamp()}]
|
|
1145
|
+
onSyncStart: (scope) => {
|
|
1146
|
+
console.log(`[${timestamp()}] ${syncLabel(scope)} \uC2DC\uC791...`);
|
|
1147
|
+
},
|
|
1148
|
+
onSyncComplete: (result, scope) => {
|
|
1149
|
+
console.log(`[${timestamp()}] ${syncLabel(scope)} \uC644\uB8CC \u2014 ${formatResult(result)}`);
|
|
872
1150
|
},
|
|
873
|
-
|
|
874
|
-
console.log(
|
|
1151
|
+
onSyncCancelled: (result, scope) => {
|
|
1152
|
+
console.log(
|
|
1153
|
+
`[${timestamp()}] ${syncLabel(scope)} \uCDE8\uC18C \u2014 \uBA48\uCD94\uAE30 \uC804\uAE4C\uC9C0 ${formatResult(result)}`
|
|
1154
|
+
);
|
|
875
1155
|
},
|
|
876
|
-
onSyncError: (error) => {
|
|
877
|
-
console.error(`[${timestamp()}]
|
|
1156
|
+
onSyncError: (error, scope) => {
|
|
1157
|
+
console.error(`[${timestamp()}] ${syncLabel(scope)} \uC2E4\uD328: ${error.message}`);
|
|
878
1158
|
}
|
|
879
1159
|
});
|
|
880
1160
|
let pullTimer = null;
|
|
1161
|
+
let shuttingDown = false;
|
|
881
1162
|
const shutdown = async () => {
|
|
1163
|
+
if (shuttingDown) {
|
|
1164
|
+
console.log(`[${timestamp()}] \uAC15\uC81C \uC885\uB8CC`);
|
|
1165
|
+
return process.exit(1);
|
|
1166
|
+
}
|
|
1167
|
+
shuttingDown = true;
|
|
882
1168
|
console.log(`
|
|
883
|
-
[${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)`);
|
|
884
1170
|
if (pullTimer) clearInterval(pullTimer);
|
|
885
1171
|
await service.stop();
|
|
886
1172
|
stateDb.close();
|
|
@@ -893,30 +1179,20 @@ var watchCommand = new Command9("watch").description("\uD30C\uC77C \uBCC0\uACBD
|
|
|
893
1179
|
console.log(`[${timestamp()}] \uD30C\uC77C \uAC10\uC2DC \uC2DC\uC791 (debounce: ${debounceMs}ms)`);
|
|
894
1180
|
if (intervalSec > 0) {
|
|
895
1181
|
console.log(`[${timestamp()}] \uD480 \uB3D9\uAE30\uD654 \uC8FC\uAE30: ${intervalSec}\uCD08`);
|
|
896
|
-
pullTimer = setInterval(
|
|
897
|
-
if (service.isSyncing()) return;
|
|
898
|
-
try {
|
|
899
|
-
console.log(`[${timestamp()}] \uC8FC\uAE30\uC801 \uB3D9\uAE30\uD654 \uC2DC\uC791...`);
|
|
900
|
-
const result = await orchestrator.sync();
|
|
901
|
-
console.log(`[${timestamp()}] \uC8FC\uAE30\uC801 \uB3D9\uAE30\uD654 \uC644\uB8CC \u2014 ${formatResult(result)}`);
|
|
902
|
-
} catch (error) {
|
|
903
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
904
|
-
console.error(`[${timestamp()}] \uC8FC\uAE30\uC801 \uB3D9\uAE30\uD654 \uC2E4\uD328: ${msg}`);
|
|
905
|
-
}
|
|
906
|
-
}, intervalSec * 1e3);
|
|
1182
|
+
pullTimer = setInterval(() => service.requestFullSync(), intervalSec * 1e3);
|
|
907
1183
|
}
|
|
908
1184
|
await new Promise(() => {
|
|
909
1185
|
});
|
|
910
1186
|
});
|
|
911
1187
|
|
|
912
1188
|
// src/commands/verify.ts
|
|
913
|
-
import { Command as
|
|
1189
|
+
import { Command as Command11 } from "commander";
|
|
914
1190
|
import {
|
|
915
|
-
ConfigManager as
|
|
916
|
-
StateDB as
|
|
917
|
-
NotionClient as
|
|
918
|
-
SyncOrchestrator as
|
|
919
|
-
NodeVaultFS as
|
|
1191
|
+
ConfigManager as ConfigManager11,
|
|
1192
|
+
StateDB as StateDB10,
|
|
1193
|
+
NotionClient as NotionClient11,
|
|
1194
|
+
SyncOrchestrator as SyncOrchestrator10,
|
|
1195
|
+
NodeVaultFS as NodeVaultFS10
|
|
920
1196
|
} from "@im-nobsidian/core";
|
|
921
1197
|
import chalk7 from "chalk";
|
|
922
1198
|
var SAMPLE_LIMIT = 10;
|
|
@@ -1006,15 +1282,15 @@ function printReport(report2) {
|
|
|
1006
1282
|
);
|
|
1007
1283
|
}
|
|
1008
1284
|
}
|
|
1009
|
-
var verifyCommand = new
|
|
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) => {
|
|
1010
1286
|
const cwd = process.cwd();
|
|
1011
|
-
const configManager = new
|
|
1287
|
+
const configManager = new ConfigManager11(cwd);
|
|
1012
1288
|
const config = await configManager.load();
|
|
1013
|
-
const stateDb =
|
|
1289
|
+
const stateDb = StateDB10.open(configManager.dbPath);
|
|
1014
1290
|
try {
|
|
1015
|
-
const client =
|
|
1016
|
-
const vaultFs = new
|
|
1017
|
-
const orchestrator = new
|
|
1291
|
+
const client = NotionClient11.fromConfig(config);
|
|
1292
|
+
const vaultFs = new NodeVaultFS10(cwd, config.paths);
|
|
1293
|
+
const orchestrator = new SyncOrchestrator10(config, stateDb, client, vaultFs);
|
|
1018
1294
|
const report2 = await orchestrator.verifyCompleteness();
|
|
1019
1295
|
if (opts.json) {
|
|
1020
1296
|
console.log(JSON.stringify(report2, null, 2));
|
|
@@ -1044,7 +1320,7 @@ function scheduleForcedExit(proc, schedule = setTimeout) {
|
|
|
1044
1320
|
// src/index.ts
|
|
1045
1321
|
var requireJson = createRequire(import.meta.url);
|
|
1046
1322
|
var { version } = requireJson("../package.json");
|
|
1047
|
-
var program = new
|
|
1323
|
+
var program = new Command12();
|
|
1048
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");
|
|
1049
1325
|
program.addCommand(initCommand);
|
|
1050
1326
|
program.addCommand(pushCommand);
|
|
@@ -1052,11 +1328,18 @@ program.addCommand(pullCommand);
|
|
|
1052
1328
|
program.addCommand(syncCommand);
|
|
1053
1329
|
program.addCommand(statusCommand);
|
|
1054
1330
|
program.addCommand(diffCommand);
|
|
1331
|
+
program.addCommand(discardCommand);
|
|
1055
1332
|
program.addCommand(fetchCommand);
|
|
1056
1333
|
program.addCommand(resolveCommand);
|
|
1057
1334
|
program.addCommand(watchCommand);
|
|
1058
1335
|
program.addCommand(verifyCommand);
|
|
1059
|
-
program.parseAsync().then(
|
|
1060
|
-
|
|
1061
|
-
|
|
1336
|
+
program.parseAsync().then(
|
|
1337
|
+
() => {
|
|
1338
|
+
scheduleForcedExit(process);
|
|
1339
|
+
},
|
|
1340
|
+
(error) => {
|
|
1341
|
+
reportStateDbFailure(error, process);
|
|
1342
|
+
scheduleForcedExit(process);
|
|
1343
|
+
}
|
|
1344
|
+
);
|
|
1062
1345
|
//# sourceMappingURL=index.js.map
|