@im-nobsidian/core 0.18.0 → 0.19.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.d.ts +15 -4
- package/dist/index.js +264 -113
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -597,10 +597,16 @@ interface PullResult {
|
|
|
597
597
|
*/
|
|
598
598
|
readonly relinkedPaths?: readonly string[];
|
|
599
599
|
/**
|
|
600
|
-
* dry-run 만 —
|
|
601
|
-
*
|
|
600
|
+
* dry-run 만 — {@link relinkedPaths} 는 이은 것이 아니라 실제 pull 이 다시 이을 것이다(P7). 미리 보기는 노트 글을 받아
|
|
601
|
+
* 견주지 않아 글이 다른 노트는 실제 pull 에서 충돌로 남는다. 자연 이름의 노트는 이은 노트가 없는 볼트에서만 이을 것으로
|
|
602
|
+
* 센다 — 이은 노트가 있는 볼트는 글이 같아야 잇는다. 이을 노트가 없으면 생략.
|
|
602
603
|
*/
|
|
603
604
|
readonly relinkPending?: true;
|
|
605
|
+
/**
|
|
606
|
+
* dry-run 만 — 이번 pull 이 새로 찾을 DB(처음 훑는 볼트 · 새로 받을 페이지 안의 DB)의 행은 세지 않았다. 받아 봐야 그 DB 가
|
|
607
|
+
* 있는지 안다. 없으면 생략.
|
|
608
|
+
*/
|
|
609
|
+
readonly undiscoveredDatabases?: true;
|
|
604
610
|
/**
|
|
605
611
|
* 볼트에서 고쳐 덮어쓰지 않은 사이드카(`.notion.json`) — push 하면 고친 속성을 Notion 에 올린다(ADR-045). 없으면 생략.
|
|
606
612
|
*/
|
|
@@ -2820,7 +2826,7 @@ declare class SyncOrchestrator {
|
|
|
2820
2826
|
* 적어 push 가 올린다 — dry-run 은 받지 않아 원격이 바뀐 그대로라, 원격을 보고 거절하지 않는다.
|
|
2821
2827
|
*/
|
|
2822
2828
|
/**
|
|
2823
|
-
* @param relinkedByPull 같은 sync 의 dry-run pull 이 다시 이을
|
|
2829
|
+
* @param relinkedByPull 같은 sync 의 dry-run pull 이 받거나 다시 이을 것이 있다 — 실제 sync 는 받으며 먼저 잇고 올리므로
|
|
2824
2830
|
* 상태 기록이 비었어도 막지 않는다({@link assertVaultLinked}).
|
|
2825
2831
|
*/
|
|
2826
2832
|
private executePush;
|
|
@@ -3643,6 +3649,8 @@ interface PlannedRow {
|
|
|
3643
3649
|
readonly moves?: readonly RemoteMove[];
|
|
3644
3650
|
/** 로컬도 고친 행을 실제 pull 이 받지 않는 까닭 — 없으면 받는다. */
|
|
3645
3651
|
readonly resolution?: PlannedResolution;
|
|
3652
|
+
/** `create` 만 — 상태 기록에 없던 같은 자리의 행 노트를 다시 잇는다(P7). 새로 받는 것으로 세지 않는다. */
|
|
3653
|
+
readonly relink?: true;
|
|
3646
3654
|
}
|
|
3647
3655
|
/**
|
|
3648
3656
|
* 로컬도 고친 노트를 실제 pull 이 어떻게 처리하나 — 충돌로 남김 · 합침(merge) · 받지 않음(원격이 그대로거나
|
|
@@ -3798,6 +3806,8 @@ declare class DatabaseSyncer {
|
|
|
3798
3806
|
* 볼트에서 옮긴 레코드 — dry-run 은 옮겨 적지 않아 이동 WAL 이 없다. 실제 pull 처럼 볼트가 먼저다.
|
|
3799
3807
|
*/
|
|
3800
3808
|
movingInVault?: ReadonlySet<string>;
|
|
3809
|
+
/** 이번 미리 보기가 다시 이을 것으로 고른 자리({@link pathKey}) — 고르면 더한다. */
|
|
3810
|
+
relinkTaken?: Set<string>;
|
|
3801
3811
|
}): Promise<PlannedRow[]>;
|
|
3802
3812
|
/**
|
|
3803
3813
|
* 받은 행이 지난 사본 그대로인가 — 렌더한 글이 달라도 받을 것이 없으면 그 사이 로컬 편집을 충돌로 올리지 않는다
|
|
@@ -4110,7 +4120,8 @@ declare class WikilinkResolver implements Processor {
|
|
|
4110
4120
|
process(input: ProcessorInput): ProcessorOutput;
|
|
4111
4121
|
/**
|
|
4112
4122
|
* `[[노트#제목]]` · `[[노트#^블록]]` · `[[#제목]]` → `[글자](https://www.notion.so/<페이지>#<블록>)`. 글자는 별칭,
|
|
4113
|
-
* 없으면 Obsidian 이 보이는 「노트 > 제목」 — pull 이 그 글자면 별칭을 붙이지 않는다.
|
|
4123
|
+
* 없으면 Obsidian 이 보이는 「노트 > 제목」 — pull 이 그 글자면 별칭을 붙이지 않는다. 앵커 링크가 아니면 null, 못 풀면
|
|
4124
|
+
* 그 까닭({@link AnchorMiss}).
|
|
4114
4125
|
*/
|
|
4115
4126
|
private anchorLink;
|
|
4116
4127
|
}
|
package/dist/index.js
CHANGED
|
@@ -5994,6 +5994,9 @@ function anchorBlockOf(block) {
|
|
|
5994
5994
|
text: (body?.rich_text ?? []).map((t) => t.plain_text ?? "").join("")
|
|
5995
5995
|
};
|
|
5996
5996
|
}
|
|
5997
|
+
function flattenBlockTree(blocks) {
|
|
5998
|
+
return blocks.flatMap((block) => [block, ...flattenBlockTree(block.children ?? [])]);
|
|
5999
|
+
}
|
|
5997
6000
|
var ANCHORED_WIKILINK_RE = new LookbehindRegExp(
|
|
5998
6001
|
/\[\[([^[\]|]*#[^[\]|]*?)\\?(?:\|[^[\]]+)?\]\]/g,
|
|
5999
6002
|
void 0,
|
|
@@ -6037,7 +6040,7 @@ var WikilinkResolver = class {
|
|
|
6037
6040
|
const target = display === void 0 ? written : withoutTableAliasEscape(written);
|
|
6038
6041
|
const label = display ?? target;
|
|
6039
6042
|
const anchored = this.anchorLink(target, display, input.context.anchors);
|
|
6040
|
-
if (anchored) return anchored;
|
|
6043
|
+
if (anchored?.link) return anchored.link;
|
|
6041
6044
|
if (this.resolve) {
|
|
6042
6045
|
const entry = this.resolve(target);
|
|
6043
6046
|
if (entry) {
|
|
@@ -6048,6 +6051,7 @@ var WikilinkResolver = class {
|
|
|
6048
6051
|
}
|
|
6049
6052
|
}
|
|
6050
6053
|
const params = display ? { text: target, display } : { text: target };
|
|
6054
|
+
if (anchored?.miss) params.anchor = anchored.miss;
|
|
6051
6055
|
params.__anchor = computeAnchor(input.content, base + offset);
|
|
6052
6056
|
preserveMarkers.push({
|
|
6053
6057
|
type: "wikilink",
|
|
@@ -6065,16 +6069,19 @@ var WikilinkResolver = class {
|
|
|
6065
6069
|
}
|
|
6066
6070
|
/**
|
|
6067
6071
|
* `[[노트#제목]]` · `[[노트#^블록]]` · `[[#제목]]` → `[글자](https://www.notion.so/<페이지>#<블록>)`. 글자는 별칭,
|
|
6068
|
-
* 없으면 Obsidian 이 보이는 「노트 > 제목」 — pull 이 그 글자면 별칭을 붙이지 않는다.
|
|
6072
|
+
* 없으면 Obsidian 이 보이는 「노트 > 제목」 — pull 이 그 글자면 별칭을 붙이지 않는다. 앵커 링크가 아니면 null, 못 풀면
|
|
6073
|
+
* 그 까닭({@link AnchorMiss}).
|
|
6069
6074
|
*/
|
|
6070
6075
|
anchorLink(target, display, anchors) {
|
|
6071
6076
|
const split = anchors ? splitAnchor(target) : null;
|
|
6072
6077
|
if (!anchors || !split) return null;
|
|
6073
6078
|
const pageId = split.note === "" ? anchors.selfPageId : this.resolve?.(split.note)?.notionPageId ?? null;
|
|
6074
|
-
if (!pageId) return
|
|
6079
|
+
if (!pageId) return { miss: "page" };
|
|
6075
6080
|
const blockId = anchors.blockOf(pageId, split.anchor);
|
|
6076
|
-
if (!blockId) return
|
|
6077
|
-
return
|
|
6081
|
+
if (!blockId) return { miss: "block" };
|
|
6082
|
+
return {
|
|
6083
|
+
link: `[${display ?? anchorDisplay(split.note, split.anchor)}](${notionBlockUrl(pageId, blockId)})`
|
|
6084
|
+
};
|
|
6078
6085
|
}
|
|
6079
6086
|
};
|
|
6080
6087
|
|
|
@@ -15686,6 +15693,77 @@ async function readLocalNote(vaultFs, path) {
|
|
|
15686
15693
|
}
|
|
15687
15694
|
}
|
|
15688
15695
|
|
|
15696
|
+
// src/utils/db-row-path.ts
|
|
15697
|
+
function selectDbRowFiles(files, localFolder) {
|
|
15698
|
+
const prefix = localFolder.endsWith("/") ? localFolder : `${localFolder}/`;
|
|
15699
|
+
return files.filter((file) => {
|
|
15700
|
+
if (!file.path.startsWith(prefix)) return false;
|
|
15701
|
+
const rel = file.path.slice(prefix.length);
|
|
15702
|
+
return rel.length > 0 && !rel.includes("/");
|
|
15703
|
+
});
|
|
15704
|
+
}
|
|
15705
|
+
var PAGE_ID_SUFFIX_LENGTHS = [8, 16, 32];
|
|
15706
|
+
var PAGE_ID_SUFFIX = new RegExp(
|
|
15707
|
+
` \\((?:${PAGE_ID_SUFFIX_LENGTHS.map((length) => `[0-9a-f]{${length}}`).join("|")})\\)$`,
|
|
15708
|
+
"i"
|
|
15709
|
+
);
|
|
15710
|
+
function withoutPageIdSuffix(name) {
|
|
15711
|
+
return name.replace(PAGE_ID_SUFFIX, "");
|
|
15712
|
+
}
|
|
15713
|
+
function pagePathCandidates(dir, safeName, pageId) {
|
|
15714
|
+
const base = dir ? `${dir}/${safeName}` : safeName;
|
|
15715
|
+
const raw = pageId.replace(/-/g, "");
|
|
15716
|
+
return [
|
|
15717
|
+
`${base}.md`,
|
|
15718
|
+
...PAGE_ID_SUFFIX_LENGTHS.map((len) => `${base} (${raw.slice(0, len)}).md`)
|
|
15719
|
+
];
|
|
15720
|
+
}
|
|
15721
|
+
function resolveDbRowPath(localFolder, safeName, pageId, lookupByPath) {
|
|
15722
|
+
const ownedBySelf = (candidate) => {
|
|
15723
|
+
const owner = lookupByPath(candidate);
|
|
15724
|
+
if (!owner) return true;
|
|
15725
|
+
return owner.notionPageId != null && notionIdsEqual(owner.notionPageId, pageId);
|
|
15726
|
+
};
|
|
15727
|
+
const candidates = pagePathCandidates(localFolder, safeName, pageId);
|
|
15728
|
+
return candidates.find(ownedBySelf) ?? candidates[candidates.length - 1];
|
|
15729
|
+
}
|
|
15730
|
+
function inClaimOrder(items, chosen, idOf2) {
|
|
15731
|
+
const slots = items.flatMap((item, index) => chosen(item) ? [index] : []);
|
|
15732
|
+
const key = (item) => compactNotionId(idOf2(item));
|
|
15733
|
+
const members = slots.map((index) => items[index]).sort((a, b) => key(a) < key(b) ? -1 : key(a) > key(b) ? 1 : 0);
|
|
15734
|
+
const ordered = [...items];
|
|
15735
|
+
slots.forEach((slot, k) => ordered[slot] = members[k]);
|
|
15736
|
+
return ordered;
|
|
15737
|
+
}
|
|
15738
|
+
|
|
15739
|
+
// src/utils/path-key.ts
|
|
15740
|
+
function pathKey(path) {
|
|
15741
|
+
return path.normalize("NFC").toLowerCase();
|
|
15742
|
+
}
|
|
15743
|
+
function foldedOwnerOf(stateDb, candidate, pageId) {
|
|
15744
|
+
return stateDb.getByPathKey(candidate).some(
|
|
15745
|
+
(record) => record.obsidianPath !== candidate && !(record.notionPageId != null && notionIdsEqual(record.notionPageId, pageId))
|
|
15746
|
+
);
|
|
15747
|
+
}
|
|
15748
|
+
|
|
15749
|
+
// src/sync/untracked-note.ts
|
|
15750
|
+
async function findUntrackedNote(stateDb, vaultFs, dir, safeName, pageId, taken) {
|
|
15751
|
+
const [natural, ...suffixed] = pagePathCandidates(dir, safeName, pageId);
|
|
15752
|
+
const order = [
|
|
15753
|
+
...suffixed.map((path) => ({ path, existing: "own" })),
|
|
15754
|
+
{ path: natural, existing: "name" }
|
|
15755
|
+
];
|
|
15756
|
+
for (const candidate of order) {
|
|
15757
|
+
if (taken(pathKey(candidate.path)) || stateDb.getByPath(candidate.path)) continue;
|
|
15758
|
+
if (foldedOwnerOf(stateDb, candidate.path, pageId)) continue;
|
|
15759
|
+
if (await vaultFs.exists(candidate.path)) return candidate;
|
|
15760
|
+
}
|
|
15761
|
+
return null;
|
|
15762
|
+
}
|
|
15763
|
+
function relinksWithoutContent(note, unlinkedVault) {
|
|
15764
|
+
return note.existing === "own" || unlinkedVault;
|
|
15765
|
+
}
|
|
15766
|
+
|
|
15689
15767
|
// src/conflict/merger.ts
|
|
15690
15768
|
function threeWayMerge(base, local, remote) {
|
|
15691
15769
|
const baseLines = base.split("\n");
|
|
@@ -16151,49 +16229,6 @@ function withUnsentProperties(content, keys, before) {
|
|
|
16151
16229
|
return (lines && replaceFrontmatterLines(content, lines)) ?? content;
|
|
16152
16230
|
}
|
|
16153
16231
|
|
|
16154
|
-
// src/utils/db-row-path.ts
|
|
16155
|
-
function selectDbRowFiles(files, localFolder) {
|
|
16156
|
-
const prefix = localFolder.endsWith("/") ? localFolder : `${localFolder}/`;
|
|
16157
|
-
return files.filter((file) => {
|
|
16158
|
-
if (!file.path.startsWith(prefix)) return false;
|
|
16159
|
-
const rel = file.path.slice(prefix.length);
|
|
16160
|
-
return rel.length > 0 && !rel.includes("/");
|
|
16161
|
-
});
|
|
16162
|
-
}
|
|
16163
|
-
var PAGE_ID_SUFFIX_LENGTHS = [8, 16, 32];
|
|
16164
|
-
var PAGE_ID_SUFFIX = new RegExp(
|
|
16165
|
-
` \\((?:${PAGE_ID_SUFFIX_LENGTHS.map((length) => `[0-9a-f]{${length}}`).join("|")})\\)$`,
|
|
16166
|
-
"i"
|
|
16167
|
-
);
|
|
16168
|
-
function withoutPageIdSuffix(name) {
|
|
16169
|
-
return name.replace(PAGE_ID_SUFFIX, "");
|
|
16170
|
-
}
|
|
16171
|
-
function pagePathCandidates(dir, safeName, pageId) {
|
|
16172
|
-
const base = dir ? `${dir}/${safeName}` : safeName;
|
|
16173
|
-
const raw = pageId.replace(/-/g, "");
|
|
16174
|
-
return [
|
|
16175
|
-
`${base}.md`,
|
|
16176
|
-
...PAGE_ID_SUFFIX_LENGTHS.map((len) => `${base} (${raw.slice(0, len)}).md`)
|
|
16177
|
-
];
|
|
16178
|
-
}
|
|
16179
|
-
function resolveDbRowPath(localFolder, safeName, pageId, lookupByPath) {
|
|
16180
|
-
const ownedBySelf = (candidate) => {
|
|
16181
|
-
const owner = lookupByPath(candidate);
|
|
16182
|
-
if (!owner) return true;
|
|
16183
|
-
return owner.notionPageId != null && notionIdsEqual(owner.notionPageId, pageId);
|
|
16184
|
-
};
|
|
16185
|
-
const candidates = pagePathCandidates(localFolder, safeName, pageId);
|
|
16186
|
-
return candidates.find(ownedBySelf) ?? candidates[candidates.length - 1];
|
|
16187
|
-
}
|
|
16188
|
-
function inClaimOrder(items, chosen, idOf2) {
|
|
16189
|
-
const slots = items.flatMap((item, index) => chosen(item) ? [index] : []);
|
|
16190
|
-
const key = (item) => compactNotionId(idOf2(item));
|
|
16191
|
-
const members = slots.map((index) => items[index]).sort((a, b) => key(a) < key(b) ? -1 : key(a) > key(b) ? 1 : 0);
|
|
16192
|
-
const ordered = [...items];
|
|
16193
|
-
slots.forEach((slot, k) => ordered[slot] = members[k]);
|
|
16194
|
-
return ordered;
|
|
16195
|
-
}
|
|
16196
|
-
|
|
16197
16232
|
// src/sync/note-title.ts
|
|
16198
16233
|
function explicitTitle(frontmatter) {
|
|
16199
16234
|
const title = frontmatter.title;
|
|
@@ -16543,6 +16578,10 @@ function decodeId(id) {
|
|
|
16543
16578
|
// src/sync/discovered-databases.ts
|
|
16544
16579
|
var ROOT_DATABASES_FOLDER = "databases";
|
|
16545
16580
|
var DISCOVERED_DBS_META_KEY = "discovered_dbs";
|
|
16581
|
+
var DISCOVERY_SCANNED_META_KEY = "discovery_scanned_at";
|
|
16582
|
+
function discoveryScanPending(stateDb) {
|
|
16583
|
+
return parseDiscoveredDbs(stateDb.getMeta(DISCOVERED_DBS_META_KEY)).length === 0 && !stateDb.getMeta(DISCOVERY_SCANNED_META_KEY);
|
|
16584
|
+
}
|
|
16546
16585
|
var INACCESSIBLE_DBS_META_KEY = "inaccessible_dbs";
|
|
16547
16586
|
var LINKED_DBS_META_KEY = "linked_dbs";
|
|
16548
16587
|
function parseDiscoveredDbs(raw) {
|
|
@@ -19161,16 +19200,6 @@ function joinPath(folder, name) {
|
|
|
19161
19200
|
return folder === "" ? name : `${folder}/${name}`;
|
|
19162
19201
|
}
|
|
19163
19202
|
|
|
19164
|
-
// src/utils/path-key.ts
|
|
19165
|
-
function pathKey(path) {
|
|
19166
|
-
return path.normalize("NFC").toLowerCase();
|
|
19167
|
-
}
|
|
19168
|
-
function foldedOwnerOf(stateDb, candidate, pageId) {
|
|
19169
|
-
return stateDb.getByPathKey(candidate).some(
|
|
19170
|
-
(record) => record.obsidianPath !== candidate && !(record.notionPageId != null && notionIdsEqual(record.notionPageId, pageId))
|
|
19171
|
-
);
|
|
19172
|
-
}
|
|
19173
|
-
|
|
19174
19203
|
// src/converter/unknown-tag-fallback.ts
|
|
19175
19204
|
var UNKNOWN_BLOCK_TAG_RE2 = /<unknown (url|id)="([^"]*)"[^>]*\/>/g;
|
|
19176
19205
|
var UNKNOWN_MENTION_TAG_RE = /<unknown_mention\s+url="([^"]*)"[^>]*?\/>/g;
|
|
@@ -19846,7 +19875,13 @@ var DatabaseSyncer = class {
|
|
|
19846
19875
|
const target = await this.queryRows(dbConfig, opts);
|
|
19847
19876
|
if (target.kind !== "rows") return [];
|
|
19848
19877
|
const folderMove = opts?.followPlacement && !opts.paths ? await this.unblocked(this.placementMoveOf(dbConfig, target.meta, opts.movingInVault)) : null;
|
|
19849
|
-
const planned = await this.planRows(
|
|
19878
|
+
const planned = await this.planRows(
|
|
19879
|
+
dbConfig,
|
|
19880
|
+
target,
|
|
19881
|
+
opts?.paths,
|
|
19882
|
+
opts?.movingInVault,
|
|
19883
|
+
opts?.relinkTaken
|
|
19884
|
+
);
|
|
19850
19885
|
if (!folderMove) return planned;
|
|
19851
19886
|
const moved = (path) => within(path, folderMove.from) ? folderMove.to + path.slice(folderMove.from.length) : path;
|
|
19852
19887
|
return [
|
|
@@ -19925,7 +19960,7 @@ var DatabaseSyncer = class {
|
|
|
19925
19960
|
return false;
|
|
19926
19961
|
}
|
|
19927
19962
|
}
|
|
19928
|
-
async planRows(dbConfig, target, paths, movingInVault = /* @__PURE__ */ new Set()) {
|
|
19963
|
+
async planRows(dbConfig, target, paths, movingInVault = /* @__PURE__ */ new Set(), relinkTaken = /* @__PURE__ */ new Set()) {
|
|
19929
19964
|
const opts = { paths };
|
|
19930
19965
|
const planned = [];
|
|
19931
19966
|
const claimed = /* @__PURE__ */ new Map();
|
|
@@ -19946,6 +19981,24 @@ var DatabaseSyncer = class {
|
|
|
19946
19981
|
if (action === "skip") continue;
|
|
19947
19982
|
if (!record) {
|
|
19948
19983
|
const name = sanitizeFileName(this.notionClient.extractTitle(page));
|
|
19984
|
+
const untracked = await findUntrackedNote(
|
|
19985
|
+
this.stateDb,
|
|
19986
|
+
this.vaultFs,
|
|
19987
|
+
dbConfig.localFolder,
|
|
19988
|
+
name,
|
|
19989
|
+
page.id,
|
|
19990
|
+
(key) => relinkTaken.has(key) || claimed.has(key)
|
|
19991
|
+
);
|
|
19992
|
+
if (untracked && relinksWithoutContent(untracked, this.unlinkedVault)) {
|
|
19993
|
+
relinkTaken.add(pathKey(untracked.path));
|
|
19994
|
+
planned.push({
|
|
19995
|
+
pageId: page.id,
|
|
19996
|
+
path: untracked.path,
|
|
19997
|
+
operation: "create",
|
|
19998
|
+
relink: true
|
|
19999
|
+
});
|
|
20000
|
+
continue;
|
|
20001
|
+
}
|
|
19949
20002
|
const ownerOf = await this.rowPathOwners(dbConfig.localFolder, name, page.id);
|
|
19950
20003
|
const path = resolveDbRowPath(dbConfig.localFolder, name, page.id, (p) => {
|
|
19951
20004
|
const owner = claimed.get(pathKey(p));
|
|
@@ -20761,20 +20814,16 @@ var DatabaseSyncer = class {
|
|
|
20761
20814
|
* `claimExistingNote` 와 같은 규칙이다.
|
|
20762
20815
|
*/
|
|
20763
20816
|
async claimExistingRow(localFolder, safeName, pageId) {
|
|
20764
|
-
const
|
|
20765
|
-
|
|
20766
|
-
|
|
20767
|
-
|
|
20768
|
-
|
|
20769
|
-
|
|
20770
|
-
|
|
20771
|
-
|
|
20772
|
-
|
|
20773
|
-
|
|
20774
|
-
this.relinkClaims.add(key);
|
|
20775
|
-
return { path: candidate, existing };
|
|
20776
|
-
}
|
|
20777
|
-
return null;
|
|
20817
|
+
const found2 = await findUntrackedNote(
|
|
20818
|
+
this.stateDb,
|
|
20819
|
+
this.vaultFs,
|
|
20820
|
+
localFolder,
|
|
20821
|
+
safeName,
|
|
20822
|
+
pageId,
|
|
20823
|
+
(key) => this.relinkClaims.has(key)
|
|
20824
|
+
);
|
|
20825
|
+
if (found2) this.relinkClaims.add(pathKey(found2.path));
|
|
20826
|
+
return found2;
|
|
20778
20827
|
}
|
|
20779
20828
|
/**
|
|
20780
20829
|
* 상태 기록에 없던 행 노트를 이 행과 다시 잇는다(P7) — 페이지의 `relinkExistingNote` 와 같은 규칙. 글이 같으면 잇고, 다르면
|
|
@@ -23332,7 +23381,6 @@ function dropEmbeddedBaseMarkers(content, basePaths) {
|
|
|
23332
23381
|
|
|
23333
23382
|
// src/sync/database-discovery.ts
|
|
23334
23383
|
var DISCOVERY_RETRY_META_KEY = "discovery_retry";
|
|
23335
|
-
var DISCOVERY_SCANNED_META_KEY = "discovery_scanned_at";
|
|
23336
23384
|
function parseDiscoveryRetry(raw) {
|
|
23337
23385
|
if (!raw) return { parents: [], dbs: [] };
|
|
23338
23386
|
try {
|
|
@@ -23511,7 +23559,7 @@ var DatabaseDiscovery = class {
|
|
|
23511
23559
|
retryLater(dbId, parentPageId, outcome.error);
|
|
23512
23560
|
}
|
|
23513
23561
|
};
|
|
23514
|
-
const scanAll = forceRediscovery ||
|
|
23562
|
+
const scanAll = forceRediscovery || discoveryScanPending(this.stateDb);
|
|
23515
23563
|
if (signal?.aborted) {
|
|
23516
23564
|
nextRetry.parents.push(...retry.parents);
|
|
23517
23565
|
nextRetry.dbs.push(...retry.dbs);
|
|
@@ -26099,23 +26147,32 @@ var PagePusher = class {
|
|
|
26099
26147
|
return uploads;
|
|
26100
26148
|
}
|
|
26101
26149
|
/**
|
|
26102
|
-
* 노트의 제목 · 블록 링크(`[[노트#제목]]`)가 가리키는 페이지의
|
|
26103
|
-
* (anchor-links).
|
|
26150
|
+
* 노트의 제목 · 블록 링크(`[[노트#제목]]`)가 가리키는 페이지의 블록을 읽어 둔다 — 변환은 기다리지 않는다
|
|
26151
|
+
* (anchor-links). 맨 위 블록에서 모두 찾으면 거기서 멈추고, 못 찾은 앵커가 있으면 콜아웃 · 토글 · 목록 자식까지 읽는다.
|
|
26152
|
+
* 그런 링크가 없으면 읽지 않는다. 읽지 못한 페이지의 링크는 예전처럼 간다.
|
|
26104
26153
|
*
|
|
26105
|
-
* @param selfPageId 이 노트의 페이지 — `[[#제목]]` 의 대상. 새로 만드는 노트는 아직 없다(그 링크는
|
|
26154
|
+
* @param selfPageId 이 노트의 페이지 — `[[#제목]]` 의 대상. 새로 만드는 노트는 아직 없다(그 링크는 push 뒤 패스가 다시 보낸다 — `push-link-pass`).
|
|
26106
26155
|
*/
|
|
26107
26156
|
async anchorLookup(content, selfPageId) {
|
|
26108
|
-
const pages = /* @__PURE__ */ new
|
|
26157
|
+
const pages = /* @__PURE__ */ new Map();
|
|
26109
26158
|
for (const target of anchoredWikilinkTargets(content)) {
|
|
26110
|
-
const { note } = splitAnchor(target);
|
|
26159
|
+
const { note, anchor } = splitAnchor(target);
|
|
26111
26160
|
const pageId = note === "" ? selfPageId : this.stateDb.resolveWikilink(note)?.notionPageId ?? null;
|
|
26112
|
-
if (pageId)
|
|
26161
|
+
if (!pageId) continue;
|
|
26162
|
+
const key = compactNotionId(pageId);
|
|
26163
|
+
pages.set(key, [...pages.get(key) ?? [], anchor]);
|
|
26113
26164
|
}
|
|
26114
26165
|
if (pages.size === 0) return void 0;
|
|
26115
26166
|
const blocks = /* @__PURE__ */ new Map();
|
|
26116
|
-
for (const pageId of pages) {
|
|
26167
|
+
for (const [pageId, anchors] of pages) {
|
|
26117
26168
|
try {
|
|
26118
|
-
|
|
26169
|
+
const top = (await this.notionClient.fetchAllChildren(pageId)).map(anchorBlockOf);
|
|
26170
|
+
blocks.set(pageId, top);
|
|
26171
|
+
if (anchors.every((anchor) => anchorBlockId(top, anchor) !== null)) continue;
|
|
26172
|
+
blocks.set(
|
|
26173
|
+
pageId,
|
|
26174
|
+
flattenBlockTree(await this.notionClient.getBlockTree(pageId)).map(anchorBlockOf)
|
|
26175
|
+
);
|
|
26119
26176
|
} catch (error) {
|
|
26120
26177
|
getLogger().debug(
|
|
26121
26178
|
`[Im-Nobsidian] \uC81C\uBAA9 \uB9C1\uD06C\uC758 \uB300\uC0C1 \uD398\uC774\uC9C0 \uBE14\uB85D\uC744 \uC77D\uC9C0 \uBABB\uD568 (${pageId}): ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -27396,6 +27453,36 @@ var PagePuller = class {
|
|
|
27396
27453
|
sanitizeFileName(this.notionClient.extractTitle(page))
|
|
27397
27454
|
);
|
|
27398
27455
|
}
|
|
27456
|
+
/**
|
|
27457
|
+
* dry-run — 실제 pull 이 이 새 페이지를 새로 받지 않고 상태 기록에 없던 노트 · 폴더와 다시 잇는가(P7). 다시 이을 노트(`note`) ·
|
|
27458
|
+
* 이미 있는 폴더(`folder` — 이은 노트가 없는 볼트에서 폴더로만 받으면 만들지 않는다), 아니면 null. 받기 전에는 폴더 노트가 될지
|
|
27459
|
+
* 모른다 — 노트 자리 · 폴더 노트 자리를 차례로 본다. `taken` 은 이번 미리 보기가 이미 이은 자리({@link pathKey}) — 고르면
|
|
27460
|
+
* 더한다. 볼트도 상태 DB 도 바꾸지 않는다.
|
|
27461
|
+
*/
|
|
27462
|
+
async plannedRelink(pageId, taken) {
|
|
27463
|
+
const page = this.detector.discoveredPage(pageId) ?? await this.notionClient.getPage(pageId);
|
|
27464
|
+
const safeName = sanitizeFileName(this.notionClient.extractTitle(page));
|
|
27465
|
+
const parentPath = await this.resolveParentPath(page);
|
|
27466
|
+
const folder = parentPath ? `${parentPath}/${safeName}` : safeName;
|
|
27467
|
+
for (const dir of [parentPath, folder]) {
|
|
27468
|
+
const found2 = await findUntrackedNote(
|
|
27469
|
+
this.stateDb,
|
|
27470
|
+
this.vaultFs,
|
|
27471
|
+
dir,
|
|
27472
|
+
safeName,
|
|
27473
|
+
pageId,
|
|
27474
|
+
(key) => taken.has(key)
|
|
27475
|
+
);
|
|
27476
|
+
if (found2 && relinksWithoutContent(found2, this.unlinkedVault)) {
|
|
27477
|
+
taken.add(pathKey(found2.path));
|
|
27478
|
+
return { path: found2.path, kind: "note" };
|
|
27479
|
+
}
|
|
27480
|
+
}
|
|
27481
|
+
const folderExists = this.unlinkedVault && !taken.has(pathKey(folder)) && this.stateDb.getByPath(folder) === null && await this.vaultFs.exists(folder);
|
|
27482
|
+
if (!folderExists) return null;
|
|
27483
|
+
taken.add(pathKey(folder));
|
|
27484
|
+
return { path: folder, kind: "folder" };
|
|
27485
|
+
}
|
|
27399
27486
|
/**
|
|
27400
27487
|
* 새 페이지가 놓일 자리가 뺀 자리인가 — 부모 폴더 · 노트 이름 · 폴더 노트가 될 때의 폴더를 본다. 받기 전에는 폴더 노트가
|
|
27401
27488
|
* 될지 모른다(본문을 받아 봐야 안다) — 어느 쪽이든 빠지면 뺀다. 자연 이름으로 보고, 같은 이름이 있을 때 붙는 id 접미사는
|
|
@@ -28081,21 +28168,22 @@ var PagePuller = class {
|
|
|
28081
28168
|
* 이름. 다시 이을지는 받은 글과 견줘 호출측이 정한다({@link relinkExistingNote}) — 잇지 않으면 장부에서 지운다.
|
|
28082
28169
|
*/
|
|
28083
28170
|
async claimExistingNote(dir, safeName, pageId) {
|
|
28084
|
-
const
|
|
28085
|
-
|
|
28086
|
-
|
|
28087
|
-
|
|
28088
|
-
|
|
28089
|
-
|
|
28090
|
-
|
|
28091
|
-
|
|
28092
|
-
|
|
28093
|
-
|
|
28171
|
+
const taken = (key) => this.claimedPaths.has(key);
|
|
28172
|
+
for (; ; ) {
|
|
28173
|
+
const found2 = await findUntrackedNote(
|
|
28174
|
+
this.stateDb,
|
|
28175
|
+
this.vaultFs,
|
|
28176
|
+
dir,
|
|
28177
|
+
safeName,
|
|
28178
|
+
pageId,
|
|
28179
|
+
taken
|
|
28180
|
+
);
|
|
28181
|
+
if (!found2) return null;
|
|
28182
|
+
const key = pathKey(found2.path);
|
|
28094
28183
|
if (this.claimedPaths.has(key)) continue;
|
|
28095
|
-
this.claimedPaths.set(key,
|
|
28096
|
-
return
|
|
28184
|
+
this.claimedPaths.set(key, found2.path);
|
|
28185
|
+
return found2;
|
|
28097
28186
|
}
|
|
28098
|
-
return null;
|
|
28099
28187
|
}
|
|
28100
28188
|
async claimFilePath(dir, safeName, pageId) {
|
|
28101
28189
|
const candidates = pagePathCandidates(dir, safeName, pageId);
|
|
@@ -28297,17 +28385,31 @@ async function resolveNotionLinks(stateDb, vaultFs, paths, pageTitle, blockAncho
|
|
|
28297
28385
|
}
|
|
28298
28386
|
|
|
28299
28387
|
// src/sync/push-link-pass.ts
|
|
28300
|
-
function notesWithResolvableWikilinks(stateDb) {
|
|
28388
|
+
function notesWithResolvableWikilinks(stateDb, pushed = /* @__PURE__ */ new Set()) {
|
|
28301
28389
|
const paths = [];
|
|
28302
28390
|
for (const record of stateDb.getAll()) {
|
|
28303
28391
|
if (!record.notionPageId || record.status !== "synced") continue;
|
|
28304
|
-
const resolvable = stateDb.getPreserveMarkers(record.obsidianPath).some(
|
|
28305
|
-
(marker) => marker.type === "wikilink" && marker.params.text !== void 0 && stateDb.resolveWikilink(marker.params.text) !== null
|
|
28306
|
-
);
|
|
28392
|
+
const resolvable = stateDb.getPreserveMarkers(record.obsidianPath).some((marker) => marker.type === "wikilink" && resolvesNow(stateDb, record, marker, pushed));
|
|
28307
28393
|
if (resolvable) paths.push(record.obsidianPath);
|
|
28308
28394
|
}
|
|
28309
28395
|
return paths.sort();
|
|
28310
28396
|
}
|
|
28397
|
+
function resolvesNow(stateDb, record, marker, pushed) {
|
|
28398
|
+
const text = marker.params.text;
|
|
28399
|
+
if (text === void 0) return false;
|
|
28400
|
+
const split = splitAnchor(text);
|
|
28401
|
+
if (!split) return stateDb.resolveWikilink(text) !== null;
|
|
28402
|
+
const target = split.note === "" ? null : stateDb.resolveWikilink(split.note);
|
|
28403
|
+
if (split.note !== "" && target === null) return false;
|
|
28404
|
+
switch (marker.params.anchor) {
|
|
28405
|
+
case "page":
|
|
28406
|
+
return true;
|
|
28407
|
+
case "block":
|
|
28408
|
+
return target !== null && target.obsidianPath !== record.obsidianPath && pushed.has(target.obsidianPath);
|
|
28409
|
+
default:
|
|
28410
|
+
return true;
|
|
28411
|
+
}
|
|
28412
|
+
}
|
|
28311
28413
|
|
|
28312
28414
|
// src/sync/pull-planner.ts
|
|
28313
28415
|
var PullPlanner = class {
|
|
@@ -28356,12 +28458,19 @@ var PullPlanner = class {
|
|
|
28356
28458
|
}
|
|
28357
28459
|
planned.push(change);
|
|
28358
28460
|
}
|
|
28461
|
+
const relinks = await this.plannedRelinks(planned);
|
|
28359
28462
|
const queued = new Set(
|
|
28360
28463
|
[...filtered, ...restoreChanges].map((change) => compactNotionId(change.pageId))
|
|
28361
28464
|
);
|
|
28362
28465
|
const movingInVault = new Set(localPlan?.scan.adoptions.map((adoption) => adoption.record.id));
|
|
28363
28466
|
const rows = [];
|
|
28364
|
-
for (const row of await this.planDatabaseRows(
|
|
28467
|
+
for (const row of await this.planDatabaseRows(
|
|
28468
|
+
options.paths,
|
|
28469
|
+
failed,
|
|
28470
|
+
ledger,
|
|
28471
|
+
movingInVault,
|
|
28472
|
+
relinks.taken
|
|
28473
|
+
)) {
|
|
28365
28474
|
if (queued.has(compactNotionId(row.pageId))) continue;
|
|
28366
28475
|
if (row.resolution?.action === "conflict") conflicts.push(row.resolution.conflict);
|
|
28367
28476
|
else if (row.resolution?.action === "merge") mergedPaths.push(row.path);
|
|
@@ -28371,16 +28480,27 @@ var PullPlanner = class {
|
|
|
28371
28480
|
const items = [];
|
|
28372
28481
|
const plannedPath = this.planner.plannedPaths(localPlan);
|
|
28373
28482
|
for (const change of [...planned, ...restoreChanges]) {
|
|
28483
|
+
const relink = relinks.byPage.get(change.pageId);
|
|
28484
|
+
if (relink) {
|
|
28485
|
+
items.push({ path: relink.path, operation: "create", unchanged: true });
|
|
28486
|
+
continue;
|
|
28487
|
+
}
|
|
28374
28488
|
const record = this.stateDb.getByNotionId(change.pageId);
|
|
28375
28489
|
items.push({
|
|
28376
28490
|
path: (record && plannedPath.get(record.id)) ?? record?.obsidianPath ?? change.title ?? change.pageId,
|
|
28377
28491
|
operation: change.type === "created" ? "create" : change.type === "deleted" ? "delete" : "update"
|
|
28378
28492
|
});
|
|
28379
28493
|
}
|
|
28380
|
-
for (const row of rows)
|
|
28494
|
+
for (const row of rows) {
|
|
28495
|
+
items.push({
|
|
28496
|
+
path: row.path,
|
|
28497
|
+
operation: row.operation,
|
|
28498
|
+
...row.relink ? { unchanged: true } : {}
|
|
28499
|
+
});
|
|
28500
|
+
}
|
|
28381
28501
|
const movedRows = rows.filter((row) => row.moves !== void 0);
|
|
28382
28502
|
items.forEach((item, index) => options.onProgress?.(index + 1, items.length, item));
|
|
28383
|
-
const count = (type) => planned.filter((c) => c.type === type).length;
|
|
28503
|
+
const count = (type) => planned.filter((c) => c.type === type && !relinks.byPage.has(c.pageId)).length;
|
|
28384
28504
|
const restoredModified = planned.filter(
|
|
28385
28505
|
(c) => c.type === "modified" && restoreIds.has(c.pageId)
|
|
28386
28506
|
).length;
|
|
@@ -28391,7 +28511,14 @@ var PullPlanner = class {
|
|
|
28391
28511
|
].map((change) => this.stateDb.getByNotionId(change.pageId)?.obsidianPath).filter((path) => path !== void 0),
|
|
28392
28512
|
...rows.filter((row) => row.operation === "update" && row.restore).map((row) => row.path)
|
|
28393
28513
|
];
|
|
28394
|
-
const rowCount = (operation, restore = false) => rows.filter(
|
|
28514
|
+
const rowCount = (operation, restore = false) => rows.filter(
|
|
28515
|
+
(row) => row.operation === operation && (row.restore ?? false) === restore && !row.relink
|
|
28516
|
+
).length;
|
|
28517
|
+
const undiscoveredDatabases = !isDatabaseMode(this.config) && (discoveryScanPending(this.stateDb) || planned.some((c) => c.type === "created"));
|
|
28518
|
+
const relinkedPaths = [
|
|
28519
|
+
...[...relinks.byPage.values()].filter((relink) => relink.kind === "note").map((relink) => relink.path),
|
|
28520
|
+
...rows.filter((row) => row.relink).map((row) => row.path)
|
|
28521
|
+
];
|
|
28395
28522
|
return {
|
|
28396
28523
|
created: count("created") + rowCount("create"),
|
|
28397
28524
|
updated: count("modified") - restoredModified + rowCount("update"),
|
|
@@ -28403,6 +28530,8 @@ var PullPlanner = class {
|
|
|
28403
28530
|
...movedRows.length > 0 ? { moved: movedRows.flatMap((row) => row.moves) } : {},
|
|
28404
28531
|
conflicts,
|
|
28405
28532
|
...mergedPaths.length > 0 ? { mergedPaths } : {},
|
|
28533
|
+
...relinkedPaths.length > 0 ? { relinkedPaths, relinkPending: true } : {},
|
|
28534
|
+
...undiscoveredDatabases ? { undiscoveredDatabases: true } : {},
|
|
28406
28535
|
writtenPaths: [],
|
|
28407
28536
|
failed,
|
|
28408
28537
|
duration: Date.now() - startTime,
|
|
@@ -28419,6 +28548,27 @@ var PullPlanner = class {
|
|
|
28419
28548
|
}
|
|
28420
28549
|
};
|
|
28421
28550
|
}
|
|
28551
|
+
/**
|
|
28552
|
+
* 새 페이지 가운데 실제 pull 이 상태 기록에 없던 노트 · 폴더와 다시 이을 것({@link PagePuller.plannedRelink}) — 페이지 id →
|
|
28553
|
+
* 자리. `taken` 은 고른 자리라 행도 피한다.
|
|
28554
|
+
*/
|
|
28555
|
+
async plannedRelinks(planned) {
|
|
28556
|
+
const byPage = /* @__PURE__ */ new Map();
|
|
28557
|
+
const taken = /* @__PURE__ */ new Set();
|
|
28558
|
+
for (const change of planned) {
|
|
28559
|
+
if (change.type !== "created") continue;
|
|
28560
|
+
try {
|
|
28561
|
+
const relink = await this.puller.plannedRelink(change.pageId, taken);
|
|
28562
|
+
if (relink) byPage.set(change.pageId, relink);
|
|
28563
|
+
} catch (error) {
|
|
28564
|
+
getLogger().debug(
|
|
28565
|
+
`[Im-Nobsidian] \uB2E4\uC2DC \uC774\uC744 \uC790\uB9AC\uB97C \uBBF8\uB9AC \uBCF4\uC9C0 \uBABB\uD568 (${change.pageId}):`,
|
|
28566
|
+
error
|
|
28567
|
+
);
|
|
28568
|
+
}
|
|
28569
|
+
}
|
|
28570
|
+
return { byPage, taken };
|
|
28571
|
+
}
|
|
28422
28572
|
/**
|
|
28423
28573
|
* 고친 페이지를 실제 pull 이 받는가 — 폴더로만 추적하는 페이지는 본문이 생겼을 때만 폴더 노트로 받는다. 하위 페이지 ·
|
|
28424
28574
|
* DB 를 옮기기만 해도 그 페이지의 수정 시각이 바뀌어 고친 것으로 보였다. 확인하지 못하면 센다(실제 pull 이 받아 본다).
|
|
@@ -28467,7 +28617,7 @@ var PullPlanner = class {
|
|
|
28467
28617
|
* (`DatabaseDiscovery.pullDiscoveredDatabases`)를 같은 규칙으로 고른다. 세지 못한 DB 는 이유와
|
|
28468
28618
|
* 함께 `failed` 에 싣는다 — 세지 못한 것을 없다고 하지 않는다.
|
|
28469
28619
|
*/
|
|
28470
|
-
async planDatabaseRows(paths, failed, ledger, movingInVault) {
|
|
28620
|
+
async planDatabaseRows(paths, failed, ledger, movingInVault, relinkTaken) {
|
|
28471
28621
|
const configured = this.config.notion.databases ?? [];
|
|
28472
28622
|
const rows = [];
|
|
28473
28623
|
const plan = async (dbConfig, resolveDbFolder2) => {
|
|
@@ -28481,7 +28631,8 @@ var PullPlanner = class {
|
|
|
28481
28631
|
paths,
|
|
28482
28632
|
resolveDbFolder: resolveDbFolder2,
|
|
28483
28633
|
followPlacement: resolveDbFolder2 !== void 0,
|
|
28484
|
-
movingInVault
|
|
28634
|
+
movingInVault,
|
|
28635
|
+
relinkTaken
|
|
28485
28636
|
})
|
|
28486
28637
|
);
|
|
28487
28638
|
} catch (error) {
|
|
@@ -29518,7 +29669,7 @@ var SyncOrchestrator = class {
|
|
|
29518
29669
|
* 적어 push 가 올린다 — dry-run 은 받지 않아 원격이 바뀐 그대로라, 원격을 보고 거절하지 않는다.
|
|
29519
29670
|
*/
|
|
29520
29671
|
/**
|
|
29521
|
-
* @param relinkedByPull 같은 sync 의 dry-run pull 이 다시 이을
|
|
29672
|
+
* @param relinkedByPull 같은 sync 의 dry-run pull 이 받거나 다시 이을 것이 있다 — 실제 sync 는 받으며 먼저 잇고 올리므로
|
|
29522
29673
|
* 상태 기록이 비었어도 막지 않는다({@link assertVaultLinked}).
|
|
29523
29674
|
*/
|
|
29524
29675
|
async executePush(options, mergedByPull = /* @__PURE__ */ new Set(), relinkedByPull = false) {
|
|
@@ -29876,7 +30027,7 @@ var SyncOrchestrator = class {
|
|
|
29876
30027
|
);
|
|
29877
30028
|
}
|
|
29878
30029
|
};
|
|
29879
|
-
const relinkable = notesWithResolvableWikilinks(this.stateDb).filter(
|
|
30030
|
+
const relinkable = notesWithResolvableWikilinks(this.stateDb, pushedBodies).filter(
|
|
29880
30031
|
(path) => inAnyPathScope(path, options?.paths) && !excludeSet?.has(path)
|
|
29881
30032
|
);
|
|
29882
30033
|
await runPool(relinkable, relink, {
|
|
@@ -30190,8 +30341,7 @@ var SyncOrchestrator = class {
|
|
|
30190
30341
|
},
|
|
30191
30342
|
moved: [...relocated.moved, ...plan.moved ?? []],
|
|
30192
30343
|
moveSkipped: relocated.skipped,
|
|
30193
|
-
excluded: [...exclusions.values()]
|
|
30194
|
-
...this.puller.unlinkedVaultAtStart && plan.created > 0 ? { relinkPending: true } : {}
|
|
30344
|
+
excluded: [...exclusions.values()]
|
|
30195
30345
|
};
|
|
30196
30346
|
}
|
|
30197
30347
|
let pullTotal = workItems.length;
|
|
@@ -30403,15 +30553,16 @@ var SyncOrchestrator = class {
|
|
|
30403
30553
|
const tagged = (direction) => onProgress && ((current, total, item) => onProgress(current, total, { ...item, direction }));
|
|
30404
30554
|
const pullResult = await this.executePull({ ...options, onProgress: tagged("pull") });
|
|
30405
30555
|
const conflicted = options?.dryRun ? pullResult.conflicts.map((conflict) => conflict.syncRecord.obsidianPath) : [];
|
|
30556
|
+
const relinked = options?.dryRun ? pullResult.relinkedPaths ?? [] : [];
|
|
30406
30557
|
const pushResult = await this.executePush(
|
|
30407
30558
|
{
|
|
30408
30559
|
...options,
|
|
30409
30560
|
onProgress: tagged("push"),
|
|
30410
30561
|
paths: options?.paths,
|
|
30411
|
-
excludePaths: [...pullResult.writtenPaths, ...conflicted]
|
|
30562
|
+
excludePaths: [...pullResult.writtenPaths, ...conflicted, ...relinked]
|
|
30412
30563
|
},
|
|
30413
30564
|
new Set(options?.dryRun ? pullResult.mergedPaths ?? [] : []),
|
|
30414
|
-
pullResult.
|
|
30565
|
+
options?.dryRun === true && (pullResult.created > 0 || relinked.length > 0)
|
|
30415
30566
|
);
|
|
30416
30567
|
return {
|
|
30417
30568
|
pull: pullResult,
|