@im-nobsidian/core 0.9.1 → 0.9.2
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 +63 -6
- package/dist/index.js +235 -68
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -848,6 +848,21 @@ declare class StateDB implements IStateDB {
|
|
|
848
848
|
static open(dbPath: string): StateDB;
|
|
849
849
|
private static openLocked;
|
|
850
850
|
private migrate;
|
|
851
|
+
/**
|
|
852
|
+
* `\` 로 이은 경로를 `/` 로 고쳐 적는다 — Windows 에서만 부른다(이슈 #8). 0.9.1 까지의 CLI 는 Windows 에서
|
|
853
|
+
* 볼트 경로를 `relative()` 그대로(`00 Me\a.md`) 적었다. 이제 목록은 `/` 로 이으므로 그대로 두면 그
|
|
854
|
+
* 노트들이 「볼트에서 지움」 과 「새 노트」 로 보인다.
|
|
855
|
+
*
|
|
856
|
+
* 같은 노트가 이미 `/` 경로로도 추적되고 있으면(pull 이 `/` 로 적은 노트를 `\` 목록이 새 노트로 보고 또
|
|
857
|
+
* 올렸다) `\` 쪽 기록을 버린다 — 그 기록의 Notion 페이지는 그때 생긴 사본이다. 지우지 않고 추적만 놓는다.
|
|
858
|
+
* Linux · macOS 에서는 `\` 가 파일 이름의 글자일 수 있어 부르지 않는다.
|
|
859
|
+
*
|
|
860
|
+
* @returns 고쳐 적은 경로 수와 버린 사본 기록 수
|
|
861
|
+
*/
|
|
862
|
+
normalizePathSeparators(): {
|
|
863
|
+
readonly renamed: number;
|
|
864
|
+
readonly dropped: number;
|
|
865
|
+
};
|
|
851
866
|
private getSchemaVersion;
|
|
852
867
|
/** 닫고 잠금을 푼다 — 닫는 사이 다른 곳이 열지 않게 닫은 뒤에 푼다. */
|
|
853
868
|
close(): void;
|
|
@@ -2075,7 +2090,12 @@ interface LocalScan {
|
|
|
2075
2090
|
}
|
|
2076
2091
|
declare class ChangeDetector {
|
|
2077
2092
|
private readonly stateDb;
|
|
2078
|
-
|
|
2093
|
+
private readonly excludes;
|
|
2094
|
+
/**
|
|
2095
|
+
* @param excludes 설정이 동기화에서 뺀 경로(이슈 #8) — 목록에 없어도 지운 것이 아니다. 예전에는 뺀 폴더의
|
|
2096
|
+
* 추적 노트를 「삭제」 로 보고, deleteSync 면 Notion 페이지를 지울 수 있었다.
|
|
2097
|
+
*/
|
|
2098
|
+
constructor(stateDb: IStateDB, excludes?: (path: string) => boolean);
|
|
2079
2099
|
detectLocalChangesFast(stats: FileStatInfo[], readFile: (path: string) => Promise<string>, options?: LocalScanOptions): Promise<LocalChange[]>;
|
|
2080
2100
|
detectLocalChanges(currentFiles: FileInfo[], options?: LocalScanOptions): LocalChange[];
|
|
2081
2101
|
/** 파일 크기 · 수정 시각이 지난 동기화와 같으면 내용을 읽지 않는다. */
|
|
@@ -2113,6 +2133,12 @@ interface VaultFS {
|
|
|
2113
2133
|
* 이 볼트(연결)가 맡은 경로인가 — 볼트 안의 다른 연결 폴더(ADR-038)는 false. 없으면 전부 맡는다.
|
|
2114
2134
|
*/
|
|
2115
2135
|
ownsPath?(path: string): boolean;
|
|
2136
|
+
/**
|
|
2137
|
+
* 설정(`paths.include` · `paths.exclude`)이나 `.im-nobsidian-ignore` 가 동기화에서 뺀 경로인가 (이슈 #8).
|
|
2138
|
+
* 뺀 노트는 목록에 없다 — 추적하던 노트라도 「볼트에서 지움」 으로 보지 않고, pull 이 받거나 되살리지도
|
|
2139
|
+
* 않는다. `folder` 면 제외 패턴만 본다(포함 패턴은 안의 파일이 정한다). 없으면 아무것도 빼지 않는다.
|
|
2140
|
+
*/
|
|
2141
|
+
excludesPath?(path: string, kind?: "file" | "folder"): boolean;
|
|
2116
2142
|
}
|
|
2117
2143
|
|
|
2118
2144
|
type ResolutionChoice = "local" | "remote" | "merge" | "duplicate";
|
|
@@ -2549,9 +2575,9 @@ interface PathFilterConfig {
|
|
|
2549
2575
|
}
|
|
2550
2576
|
declare class NodeVaultFS implements VaultFS {
|
|
2551
2577
|
private readonly rootPath;
|
|
2552
|
-
private readonly
|
|
2553
|
-
private readonly excludePatterns;
|
|
2578
|
+
private readonly filter;
|
|
2554
2579
|
constructor(rootPath: string, pathConfig?: PathFilterConfig);
|
|
2580
|
+
excludesPath(path: string, kind?: "file" | "folder"): boolean;
|
|
2555
2581
|
private loadImNobsidianIgnore;
|
|
2556
2582
|
readFile(path: string): Promise<string>;
|
|
2557
2583
|
readBinary(path: string): Promise<Buffer>;
|
|
@@ -2565,8 +2591,8 @@ declare class NodeVaultFS implements VaultFS {
|
|
|
2565
2591
|
listMarkdownFileStats(): Promise<FileStatInfo[]>;
|
|
2566
2592
|
getFileStat(path: string): Promise<FileStatInfo | null>;
|
|
2567
2593
|
listNonMarkdownFiles(): Promise<NonMdFileInfo[]>;
|
|
2568
|
-
|
|
2569
|
-
private
|
|
2594
|
+
/** 볼트 경로 — Windows 의 `relative()` 는 `\` 로 잇는다. 상태 DB · 패턴 · Notion 경로는 모두 `/` 다. */
|
|
2595
|
+
private vaultPath;
|
|
2570
2596
|
private walkDirNonMd;
|
|
2571
2597
|
private walkDirStats;
|
|
2572
2598
|
private walkDir;
|
|
@@ -3942,6 +3968,36 @@ declare function matchesPathScope(path: string, scope: string): boolean;
|
|
|
3942
3968
|
/** 범위 목록 중 하나라도 걸리면 true. 목록이 비어 있으면(범위 미지정) 항상 true. */
|
|
3943
3969
|
declare function inAnyPathScope(path: string, scopes: readonly string[] | undefined): boolean;
|
|
3944
3970
|
|
|
3971
|
+
/**
|
|
3972
|
+
* 설정 `paths.include` · `paths.exclude` 와 `.im-nobsidian-ignore` 의 경로 패턴 (이슈 #8).
|
|
3973
|
+
*
|
|
3974
|
+
* 볼트 경로는 늘 `/` 로 잇는다({@link toVaultPath}). 예전 판정은 세 가지가 틀렸다.
|
|
3975
|
+
* 1. Windows 의 `relative()` 가 준 `00 Me\a.md` 를 `/` 로만 견줘 어느 패턴에도 맞지 않았다
|
|
3976
|
+
* 2. 포함 패턴을 폴더에도 대어 `00 Me/**\/*.md` 면 폴더 `00 Me` 가 빠지고 안을 걷지 않았다
|
|
3977
|
+
* 3. `**\/` 가 폴더 0 단계를 받지 않았고(`a/**\/*.md` ↛ `a/x.md`) 정규식 글자를 이스케이프하지 않았다
|
|
3978
|
+
*
|
|
3979
|
+
* 규칙
|
|
3980
|
+
* - 글롭 없는 패턴은 그 경로이거나 그 폴더 안이다 — `Private` ↔ `Private/a.md`
|
|
3981
|
+
* - `/` 없는 글롭은 경로 조각 어느 것에나 댄다 — `*.pdf` ↔ `a/b/x.pdf`, `draft-*` ↔ `x/draft-1/a.md`
|
|
3982
|
+
* - `/` 있는 글롭은 볼트 맨 위부터 댄다. `*` · `?` 는 조각 안, `**` 는 조각을 넘는다
|
|
3983
|
+
* - 패턴에 맞는 폴더의 안은 모두 맞는다 — 걷기가 그 폴더에서 멈추는 것과 같다
|
|
3984
|
+
* - 패턴의 `\` 는 `/` 로, 앞의 `./` · `/` 와 끝의 `/` 는 뗀다
|
|
3985
|
+
*/
|
|
3986
|
+
declare class PathFilter {
|
|
3987
|
+
private readonly include;
|
|
3988
|
+
private readonly exclude;
|
|
3989
|
+
constructor(include: readonly string[], exclude: readonly string[]);
|
|
3990
|
+
/** 동기화하지 않는 노트 · 첨부 — 포함 패턴 어느 것에도 맞지 않거나 제외 패턴에 맞는다. */
|
|
3991
|
+
excludesFile(path: string): boolean;
|
|
3992
|
+
/**
|
|
3993
|
+
* 걷지 않을 폴더 — 제외 패턴만 본다. 포함 패턴은 안의 파일이 정한다: `00 Me/**\/*.md` 는 폴더
|
|
3994
|
+
* `00 Me` 에 맞지 않지만 그 안의 노트에 맞는다.
|
|
3995
|
+
*/
|
|
3996
|
+
excludesFolder(path: string): boolean;
|
|
3997
|
+
}
|
|
3998
|
+
/** 운영체제 구분자로 이은 상대 경로 → 볼트 경로(`/`). `relative()` 의 결과에 쓴다. */
|
|
3999
|
+
declare function toVaultPath(relativePath: string, separator: string): string;
|
|
4000
|
+
|
|
3945
4001
|
/** 줄 비교의 한 줄. 번호는 1부터 — 더한 줄은 옛 번호가, 지운 줄은 새 번호가 없다. */
|
|
3946
4002
|
interface DiffLine {
|
|
3947
4003
|
readonly kind: "same" | "added" | "removed";
|
|
@@ -4045,6 +4101,7 @@ declare class ScopedVaultFS implements VaultFS {
|
|
|
4045
4101
|
*/
|
|
4046
4102
|
constructor(base: VaultFS, folder: string, connections: readonly string[]);
|
|
4047
4103
|
ownsPath(path: string): boolean;
|
|
4104
|
+
excludesPath(path: string, kind?: "file" | "folder"): boolean;
|
|
4048
4105
|
/** 이 연결의 경로 → 볼트 경로. */
|
|
4049
4106
|
private toBase;
|
|
4050
4107
|
/** 볼트 경로 → 이 연결의 경로. 이 연결이 맡지 않으면 null. */
|
|
@@ -4226,4 +4283,4 @@ declare function readColumnLayout(lines: readonly string[]): (ColumnPlace | unde
|
|
|
4226
4283
|
/** 열 이름표 — `열 2/3 · 50%`. 비율이 없으면 `열 2/3`. */
|
|
4227
4284
|
declare function columnLabel(place: ColumnPlace): string;
|
|
4228
4285
|
|
|
4229
|
-
export { BaseFileGenerator, type BaseFileOptions, type BasePropertyOption, type BasePropertySchema, type BaseStatusGroup, type BaseSyncOptions, type BasesFilterNode, type BasesViewMapping, type BasesViewType, BlankLineCollapser, type BookmarkTarget, CONFIG_FILE, type CalendarEntry, type CalloutDesign, CalloutRestorer, CalloutTransformer, ChangeDetector, type ChangeDiff, ColorAnnotator, type ColumnPlace, type CompletenessFailure, type CompletenessLocalSource, type CompletenessOptions, type CompletenessRemoteSource, type CompletenessReport, type Config, ConfigManager, ConfigSchema, type Conflict, ConflictResolver, type ConflictStrategy, ConnectionBoundaryError, ConnectionScopeError, type ConversionContext, type ConversionPath, ConversionPipeline, type ConversionResult, type CoverConfig, type DBEntry, DB_VIEWS_FILE, DB_VIEWS_PATH, DEFAULT_CONFIG, DEFAULT_TITLE_PROPERTY, DEFAULT_WATCH_DEBOUNCE_MS, DIFF_SIGN, type DatabaseCompleteness, type DatabaseSyncConfig, type DatabaseSyncResult, DatabaseSyncer, type DatabaseViewsConfig, type DeferredCode, type DegradeNote, type DesignColor, type DesignLine, type DesignSegment, type DiffHunk, type DiffLine, EmbedResolver, EntryEditor, type FailedOperation, type FidelityClassification, type FidelityContext, type FidelityDefect, type FidelityDefectForm, type FidelitySummary, type FileDownloadResult, FileHandler, type FileInfo, type FileRegistryEntry, type FileStatInfo, type FileSystemEntry, type FileType, type FileUploadResult, FileWatcher, type FilterCondition, type FolderDatabaseReport, type FolderMoveChange, FrontmatterExtractor, FrontmatterGenerator, type FullScanReason, GITIGNORE_ENTRY, type GatedOperation, type GroupByConfig, type GroupedEntries, IGNORE_FILE, INTERNAL_DIR, INTERNAL_DIR_GLOB, type IStateDB, type ImageDownloadResult, ImageHandler, type ImageReference, type InferredProperty, InlineAnnotationPreserver, InlineDBParser, type InlineStyle, type LineDiffOptions, type LineRange, type LocalChange, LocalImageRestorer, type LocalMoveAdoption, type LocalScan, type LocalScanOptions, type Logger, MARKER_BRAND, type MappingResult, type MappingType, MathNormalizer, MentionToWikilink, type MergeConflictRegion, type MergeResult, NOTION_COLOR_NAMES, NOTION_TOKEN_PREFIX, NOTION_TO_BASES_VIEW, NodeVaultFS, type NonMdFileInfo, type NotionBlock, NotionBlockBuilder, NotionClient, type NotionClientOptions, type NotionColor, type NotionPageNode, type NotionRichText, type RichTextItem as NotionRichTextItem, type NotionSidecar, OBSIDIAN_CONFIG_DIR, type OperationType, type PageCompletenessRemoteSource, type PageCompletenessReport, type PageCover, type PageCreationPlan, type PageIcon, type PathFilterConfig, type PendingOperation, PendingWalError, type PlannedRow, type PreserveMarker, PreserveMarkerCollector, PreserveMarkerInjector, type Processor, type ProcessorInput, type ProcessorMetadata, type ProcessorOutput, type ProgressCallback, type ProgressItem, PropertiesTableInjector, PropertiesTableRestorer, PropertyMapper, type PropertyOption, type PropertySchema, type PropertyValue, type PullOptions, type PullResult, type PushOptions, type PushResult, RENDER_LINE_RULES, type RecordPendingInput, type RegisterFileInput, type RemoteChange, type RemoteObservation, type RemoteScanInfo, type RenameHints, type RenameKind, type RenderFinding, type RenderLineRule, type ResolutionChoice, type ResolutionResult, type RichTextSegment, type RowProgress, SAVED_STATE_TABLES_QUERY, SEPARATOR_BODY, STATE_DB_FILE, STATE_DB_MIGRATIONS, STATE_DB_PATH, SavedStateDbError, ScopedVaultFS, SidecarGenerator, type SidecarProperty, type SidecarView, StateDB, StateDbLockedError, type StateDbMigration, type StateDbTool, StateDbUnavailableError, StateLock, type StateLockClaim, type StateLockOwner, type StatusOptions, type StatusResult, type StyledRange, SyncBusyError, type SyncDirection, type SyncOptions, SyncOrchestrator, type SyncRecord, type SyncResult, type SyncStatus, type TableLineDesign, type TranslationResult, TreeMapper, UnsupportedBlockStripper, type UpsertSyncRecord, type VaultCompletenessReport, type VaultFS, type ViewConfig, ViewDataProvider, type ViewPropertyConfig, type ViewRenderData, type ViewType, WIKILINK_LOOKUPS, type WatchSyncOptions, type WatchSyncScope, WatchSyncService, type WikilinkEntry, type WikilinkLookup, WikilinkResolver, applicableChoices, assertNoPendingWal, basesViewMappingOf, basesViewTypeOf, choiceForStrategy, classifyBodyFidelity, columnLabel, compactNotionId, computeBufferHash, computeHash, configDirFrom, connectionFolderOf, createDefaultPipeline, extractCalendarEntries, filterByMonth, filterEntries, findConnectionFolders, formatUnifiedDiff, generateColorCSS, generateId, getLogger, getNotionBgColor, getNotionColor, getVisibleProperties, groupEntries, hasColumnLayout, hasDesignMarker, inAnyPathScope, isInternalPath, isMarkdownPath, isRemoteDeletion, lineDiff, lintRenderedMarkdown, matchesPathScope, normalizeNotionId, normalizeVaultPath, noteNameFromPath, notionIdFromInput, notionIdsEqual, operationLabel, parseDesignColor, pendingWalBytes, readColumnLayout, readDesignLine, resolvePropertyName, sanitizeFileName, setLogger, sortEntries, stateDbLockPath, stateDbWalPath, stripMarkdownExtension, summarizeFidelity, systemTimeZone, threeWayMerge, translateNotionFilter, verifyDatabaseCompleteness, verifyPageCompleteness, wikilinkLookupsIn };
|
|
4286
|
+
export { BaseFileGenerator, type BaseFileOptions, type BasePropertyOption, type BasePropertySchema, type BaseStatusGroup, type BaseSyncOptions, type BasesFilterNode, type BasesViewMapping, type BasesViewType, BlankLineCollapser, type BookmarkTarget, CONFIG_FILE, type CalendarEntry, type CalloutDesign, CalloutRestorer, CalloutTransformer, ChangeDetector, type ChangeDiff, ColorAnnotator, type ColumnPlace, type CompletenessFailure, type CompletenessLocalSource, type CompletenessOptions, type CompletenessRemoteSource, type CompletenessReport, type Config, ConfigManager, ConfigSchema, type Conflict, ConflictResolver, type ConflictStrategy, ConnectionBoundaryError, ConnectionScopeError, type ConversionContext, type ConversionPath, ConversionPipeline, type ConversionResult, type CoverConfig, type DBEntry, DB_VIEWS_FILE, DB_VIEWS_PATH, DEFAULT_CONFIG, DEFAULT_TITLE_PROPERTY, DEFAULT_WATCH_DEBOUNCE_MS, DIFF_SIGN, type DatabaseCompleteness, type DatabaseSyncConfig, type DatabaseSyncResult, DatabaseSyncer, type DatabaseViewsConfig, type DeferredCode, type DegradeNote, type DesignColor, type DesignLine, type DesignSegment, type DiffHunk, type DiffLine, EmbedResolver, EntryEditor, type FailedOperation, type FidelityClassification, type FidelityContext, type FidelityDefect, type FidelityDefectForm, type FidelitySummary, type FileDownloadResult, FileHandler, type FileInfo, type FileRegistryEntry, type FileStatInfo, type FileSystemEntry, type FileType, type FileUploadResult, FileWatcher, type FilterCondition, type FolderDatabaseReport, type FolderMoveChange, FrontmatterExtractor, FrontmatterGenerator, type FullScanReason, GITIGNORE_ENTRY, type GatedOperation, type GroupByConfig, type GroupedEntries, IGNORE_FILE, INTERNAL_DIR, INTERNAL_DIR_GLOB, type IStateDB, type ImageDownloadResult, ImageHandler, type ImageReference, type InferredProperty, InlineAnnotationPreserver, InlineDBParser, type InlineStyle, type LineDiffOptions, type LineRange, type LocalChange, LocalImageRestorer, type LocalMoveAdoption, type LocalScan, type LocalScanOptions, type Logger, MARKER_BRAND, type MappingResult, type MappingType, MathNormalizer, MentionToWikilink, type MergeConflictRegion, type MergeResult, NOTION_COLOR_NAMES, NOTION_TOKEN_PREFIX, NOTION_TO_BASES_VIEW, NodeVaultFS, type NonMdFileInfo, type NotionBlock, NotionBlockBuilder, NotionClient, type NotionClientOptions, type NotionColor, type NotionPageNode, type NotionRichText, type RichTextItem as NotionRichTextItem, type NotionSidecar, OBSIDIAN_CONFIG_DIR, type OperationType, type PageCompletenessRemoteSource, type PageCompletenessReport, type PageCover, type PageCreationPlan, type PageIcon, PathFilter, type PathFilterConfig, type PendingOperation, PendingWalError, type PlannedRow, type PreserveMarker, PreserveMarkerCollector, PreserveMarkerInjector, type Processor, type ProcessorInput, type ProcessorMetadata, type ProcessorOutput, type ProgressCallback, type ProgressItem, PropertiesTableInjector, PropertiesTableRestorer, PropertyMapper, type PropertyOption, type PropertySchema, type PropertyValue, type PullOptions, type PullResult, type PushOptions, type PushResult, RENDER_LINE_RULES, type RecordPendingInput, type RegisterFileInput, type RemoteChange, type RemoteObservation, type RemoteScanInfo, type RenameHints, type RenameKind, type RenderFinding, type RenderLineRule, type ResolutionChoice, type ResolutionResult, type RichTextSegment, type RowProgress, SAVED_STATE_TABLES_QUERY, SEPARATOR_BODY, STATE_DB_FILE, STATE_DB_MIGRATIONS, STATE_DB_PATH, SavedStateDbError, ScopedVaultFS, SidecarGenerator, type SidecarProperty, type SidecarView, StateDB, StateDbLockedError, type StateDbMigration, type StateDbTool, StateDbUnavailableError, StateLock, type StateLockClaim, type StateLockOwner, type StatusOptions, type StatusResult, type StyledRange, SyncBusyError, type SyncDirection, type SyncOptions, SyncOrchestrator, type SyncRecord, type SyncResult, type SyncStatus, type TableLineDesign, type TranslationResult, TreeMapper, UnsupportedBlockStripper, type UpsertSyncRecord, type VaultCompletenessReport, type VaultFS, type ViewConfig, ViewDataProvider, type ViewPropertyConfig, type ViewRenderData, type ViewType, WIKILINK_LOOKUPS, type WatchSyncOptions, type WatchSyncScope, WatchSyncService, type WikilinkEntry, type WikilinkLookup, WikilinkResolver, applicableChoices, assertNoPendingWal, basesViewMappingOf, basesViewTypeOf, choiceForStrategy, classifyBodyFidelity, columnLabel, compactNotionId, computeBufferHash, computeHash, configDirFrom, connectionFolderOf, createDefaultPipeline, extractCalendarEntries, filterByMonth, filterEntries, findConnectionFolders, formatUnifiedDiff, generateColorCSS, generateId, getLogger, getNotionBgColor, getNotionColor, getVisibleProperties, groupEntries, hasColumnLayout, hasDesignMarker, inAnyPathScope, isInternalPath, isMarkdownPath, isRemoteDeletion, lineDiff, lintRenderedMarkdown, matchesPathScope, normalizeNotionId, normalizeVaultPath, noteNameFromPath, notionIdFromInput, notionIdsEqual, operationLabel, parseDesignColor, pendingWalBytes, readColumnLayout, readDesignLine, resolvePropertyName, sanitizeFileName, setLogger, sortEntries, stateDbLockPath, stateDbWalPath, stripMarkdownExtension, summarizeFidelity, systemTimeZone, threeWayMerge, toVaultPath, translateNotionFilter, verifyDatabaseCompleteness, verifyPageCompleteness, wikilinkLookupsIn };
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { LogLevel, Client } from '@notionhq/client';
|
|
3
3
|
import { readFile, mkdir, writeFile, unlink, rename, stat, readdir } from 'fs/promises';
|
|
4
|
-
import { join, dirname,
|
|
4
|
+
import { join, dirname, sep, relative } from 'path';
|
|
5
5
|
import { mkdirSync, unlinkSync, utimesSync, openSync, writeSync, closeSync, readFileSync, statSync, existsSync, readlinkSync } from 'fs';
|
|
6
6
|
import Database from 'better-sqlite3';
|
|
7
7
|
import { randomUUID, createHash } from 'crypto';
|
|
@@ -455,6 +455,22 @@ function compactNotionId(id) {
|
|
|
455
455
|
return id.replace(/-/g, "").toLowerCase();
|
|
456
456
|
}
|
|
457
457
|
|
|
458
|
+
// src/utils/logger.ts
|
|
459
|
+
var consoleLogger = {
|
|
460
|
+
warn: (msg, ...args) => console.warn(msg, ...args),
|
|
461
|
+
error: (msg, ...args) => console.error(msg, ...args),
|
|
462
|
+
info: (msg, ...args) => console.info(msg, ...args),
|
|
463
|
+
debug: () => {
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
var currentLogger = consoleLogger;
|
|
467
|
+
function setLogger(logger) {
|
|
468
|
+
currentLogger = logger;
|
|
469
|
+
}
|
|
470
|
+
function getLogger() {
|
|
471
|
+
return currentLogger;
|
|
472
|
+
}
|
|
473
|
+
|
|
458
474
|
// src/state/migrations/001-initial.ts
|
|
459
475
|
var INITIAL_MIGRATION = `
|
|
460
476
|
CREATE TABLE IF NOT EXISTS sync_state (
|
|
@@ -649,24 +665,6 @@ function wikilinkLookupsIn(parentIds) {
|
|
|
649
665
|
}
|
|
650
666
|
];
|
|
651
667
|
}
|
|
652
|
-
|
|
653
|
-
// src/utils/logger.ts
|
|
654
|
-
var consoleLogger = {
|
|
655
|
-
warn: (msg, ...args) => console.warn(msg, ...args),
|
|
656
|
-
error: (msg, ...args) => console.error(msg, ...args),
|
|
657
|
-
info: (msg, ...args) => console.info(msg, ...args),
|
|
658
|
-
debug: () => {
|
|
659
|
-
}
|
|
660
|
-
};
|
|
661
|
-
var currentLogger = consoleLogger;
|
|
662
|
-
function setLogger(logger) {
|
|
663
|
-
currentLogger = logger;
|
|
664
|
-
}
|
|
665
|
-
function getLogger() {
|
|
666
|
-
return currentLogger;
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
// src/state/state-lock.ts
|
|
670
668
|
var STATE_LOCK_HEARTBEAT_MS = 3e4;
|
|
671
669
|
var STATE_LOCK_STALE_MS = STATE_LOCK_HEARTBEAT_MS * 4;
|
|
672
670
|
function stateDbLockPath(dbPath) {
|
|
@@ -903,7 +901,8 @@ var StateDB = class _StateDB {
|
|
|
903
901
|
}
|
|
904
902
|
}
|
|
905
903
|
static openLocked(dbPath, lock) {
|
|
906
|
-
const
|
|
904
|
+
const saved = existsSync(dbPath) ? statSync(dbPath) : null;
|
|
905
|
+
const savedBytes = saved?.isFile() ? saved.size : null;
|
|
907
906
|
if (savedBytes === 0) throw SavedStateDbError.noTables(0);
|
|
908
907
|
const db = new Database(dbPath);
|
|
909
908
|
try {
|
|
@@ -912,6 +911,7 @@ var StateDB = class _StateDB {
|
|
|
912
911
|
}
|
|
913
912
|
const stateDb = new _StateDB(db, lock);
|
|
914
913
|
stateDb.migrate();
|
|
914
|
+
if (process.platform === "win32") stateDb.normalizePathSeparators();
|
|
915
915
|
return stateDb;
|
|
916
916
|
} catch (error) {
|
|
917
917
|
db.close();
|
|
@@ -925,6 +925,57 @@ var StateDB = class _StateDB {
|
|
|
925
925
|
if (version < migration.version) this.db.exec(migration.sql);
|
|
926
926
|
}
|
|
927
927
|
}
|
|
928
|
+
/**
|
|
929
|
+
* `\` 로 이은 경로를 `/` 로 고쳐 적는다 — Windows 에서만 부른다(이슈 #8). 0.9.1 까지의 CLI 는 Windows 에서
|
|
930
|
+
* 볼트 경로를 `relative()` 그대로(`00 Me\a.md`) 적었다. 이제 목록은 `/` 로 이으므로 그대로 두면 그
|
|
931
|
+
* 노트들이 「볼트에서 지움」 과 「새 노트」 로 보인다.
|
|
932
|
+
*
|
|
933
|
+
* 같은 노트가 이미 `/` 경로로도 추적되고 있으면(pull 이 `/` 로 적은 노트를 `\` 목록이 새 노트로 보고 또
|
|
934
|
+
* 올렸다) `\` 쪽 기록을 버린다 — 그 기록의 Notion 페이지는 그때 생긴 사본이다. 지우지 않고 추적만 놓는다.
|
|
935
|
+
* Linux · macOS 에서는 `\` 가 파일 이름의 글자일 수 있어 부르지 않는다.
|
|
936
|
+
*
|
|
937
|
+
* @returns 고쳐 적은 경로 수와 버린 사본 기록 수
|
|
938
|
+
*/
|
|
939
|
+
normalizePathSeparators() {
|
|
940
|
+
let renamed = 0;
|
|
941
|
+
let dropped = 0;
|
|
942
|
+
const fix = (path) => path.replace(/\\/g, "/");
|
|
943
|
+
this.db.transaction(() => {
|
|
944
|
+
const unique = (table, column, key) => {
|
|
945
|
+
const rows = this.db.prepare(
|
|
946
|
+
`SELECT ${key} AS k, ${column} AS p FROM ${table} WHERE instr(${column}, '\\') > 0`
|
|
947
|
+
).all();
|
|
948
|
+
for (const row of rows) {
|
|
949
|
+
const taken = this.db.prepare(`SELECT 1 FROM ${table} WHERE ${column} = ?`).get(fix(row.p));
|
|
950
|
+
if (taken) {
|
|
951
|
+
this.db.prepare(`DELETE FROM ${table} WHERE ${key} = ?`).run(row.k);
|
|
952
|
+
if (table === "sync_state") dropped++;
|
|
953
|
+
} else {
|
|
954
|
+
this.db.prepare(`UPDATE ${table} SET ${column} = ? WHERE ${key} = ?`).run(fix(row.p), row.k);
|
|
955
|
+
if (table === "sync_state") renamed++;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
};
|
|
959
|
+
unique("sync_state", "obsidian_path", "id");
|
|
960
|
+
unique("wikilink_map", "obsidian_path", "obsidian_path");
|
|
961
|
+
unique("file_registry", "local_path", "id");
|
|
962
|
+
this.db.prepare("UPDATE image_registry SET local_path = replace(local_path, '\\', '/')").run();
|
|
963
|
+
const markers = this.db.prepare(
|
|
964
|
+
"SELECT key FROM sync_metadata WHERE key LIKE 'preserve_markers:%' AND instr(key, '\\') > 0"
|
|
965
|
+
).all();
|
|
966
|
+
for (const { key } of markers) {
|
|
967
|
+
const taken = this.db.prepare("SELECT 1 FROM sync_metadata WHERE key = ?").get(fix(key));
|
|
968
|
+
if (taken) this.db.prepare("DELETE FROM sync_metadata WHERE key = ?").run(key);
|
|
969
|
+
else this.db.prepare("UPDATE sync_metadata SET key = ? WHERE key = ?").run(fix(key), key);
|
|
970
|
+
}
|
|
971
|
+
})();
|
|
972
|
+
if (renamed + dropped > 0) {
|
|
973
|
+
getLogger().warn(
|
|
974
|
+
`[Im-Nobsidian] Windows \uACBD\uB85C ${renamed}\uAC1C\uB97C / \uB85C \uACE0\uCCD0 \uC801\uC74C` + (dropped > 0 ? ` \xB7 \uAC19\uC740 \uB178\uD2B8\uB97C \uB610 \uC62C\uB9B0 \uC0AC\uBCF8 \uAE30\uB85D ${dropped}\uAC1C\uB294 \uCD94\uC801\uC744 \uB193\uC74C(Notion \uD398\uC774\uC9C0\uB294 \uB0A8\uC74C)` : "")
|
|
975
|
+
);
|
|
976
|
+
}
|
|
977
|
+
return { renamed, dropped };
|
|
978
|
+
}
|
|
928
979
|
getSchemaVersion() {
|
|
929
980
|
try {
|
|
930
981
|
const row = this.db.prepare("SELECT value FROM sync_metadata WHERE key = 'schema_version'").get();
|
|
@@ -4835,10 +4886,16 @@ function hasRemotePage(record) {
|
|
|
4835
4886
|
return record !== null && Boolean(record.notionPageId);
|
|
4836
4887
|
}
|
|
4837
4888
|
var ChangeDetector = class {
|
|
4838
|
-
|
|
4889
|
+
/**
|
|
4890
|
+
* @param excludes 설정이 동기화에서 뺀 경로(이슈 #8) — 목록에 없어도 지운 것이 아니다. 예전에는 뺀 폴더의
|
|
4891
|
+
* 추적 노트를 「삭제」 로 보고, deleteSync 면 Notion 페이지를 지울 수 있었다.
|
|
4892
|
+
*/
|
|
4893
|
+
constructor(stateDb, excludes = () => false) {
|
|
4839
4894
|
this.stateDb = stateDb;
|
|
4895
|
+
this.excludes = excludes;
|
|
4840
4896
|
}
|
|
4841
4897
|
stateDb;
|
|
4898
|
+
excludes;
|
|
4842
4899
|
async detectLocalChangesFast(stats, readFile3, options) {
|
|
4843
4900
|
return (await this.scanLocalChangesFast(stats, readFile3, options)).changes;
|
|
4844
4901
|
}
|
|
@@ -4914,7 +4971,7 @@ var ChangeDetector = class {
|
|
|
4914
4971
|
}
|
|
4915
4972
|
const missing = this.stateDb.getByStatus("synced").filter(
|
|
4916
4973
|
(record) => hasRemotePage(record) && !existing.has(record.obsidianPath) && // 폴더 페이지는 파일이 아니다 — 폴더가 사라진 것은 파일 목록으로 알 수 없다.
|
|
4917
|
-
!isFolderRecord(record)
|
|
4974
|
+
!isFolderRecord(record) && !this.excludes(record.obsidianPath)
|
|
4918
4975
|
);
|
|
4919
4976
|
const pairs = pairLocalMoves(
|
|
4920
4977
|
missing.map((record) => ({
|
|
@@ -11131,6 +11188,32 @@ function unquoteCode(lines, bodies) {
|
|
|
11131
11188
|
return out;
|
|
11132
11189
|
}
|
|
11133
11190
|
|
|
11191
|
+
// src/converter/literal-angles.ts
|
|
11192
|
+
function unescapeAngleBrackets(content) {
|
|
11193
|
+
if (!content.includes("\\<") && !content.includes("\\>")) return content;
|
|
11194
|
+
return mapOutsideCodeFences(
|
|
11195
|
+
content,
|
|
11196
|
+
(segment) => segment.split("\n").map(unescapeLine).join("\n")
|
|
11197
|
+
);
|
|
11198
|
+
}
|
|
11199
|
+
var BLOCK_PREFIX_RE = /^[ \t]*(?:(?:>|[-*+]|\d+[.)])[ \t]*)*$/;
|
|
11200
|
+
var TAG_START_RE = /[A-Za-z/!?]/;
|
|
11201
|
+
function unescapeLine(line) {
|
|
11202
|
+
if (!line.includes("\\<") && !line.includes("\\>")) return line;
|
|
11203
|
+
const code = inlineCodeRanges(line);
|
|
11204
|
+
return line.replace(
|
|
11205
|
+
/(\\+)([<>])/g,
|
|
11206
|
+
(match, slashes, char, at) => {
|
|
11207
|
+
if (slashes.length % 2 === 0) return match;
|
|
11208
|
+
if (code.some(([start, end]) => at >= start && at < end)) return match;
|
|
11209
|
+
const escape = at + slashes.length - 1;
|
|
11210
|
+
if (char === ">" && BLOCK_PREFIX_RE.test(line.slice(0, escape))) return match;
|
|
11211
|
+
if (char === "<" && TAG_START_RE.test(line[at + match.length] ?? "")) return match;
|
|
11212
|
+
return `${slashes.slice(1)}${char}`;
|
|
11213
|
+
}
|
|
11214
|
+
);
|
|
11215
|
+
}
|
|
11216
|
+
|
|
11134
11217
|
// src/converter/enhanced-md-converter.ts
|
|
11135
11218
|
var NOTION_CALLOUT_RE = /^::: callout\n([\s\S]*?)\n:::/gm;
|
|
11136
11219
|
var NOTION_PAGE_MENTION_RE = /<mention-page id="([^"]+)">([\s\S]*?)<\/mention-page>/g;
|
|
@@ -11167,6 +11250,7 @@ function notionEnhancedToObsidian(enhanced, options) {
|
|
|
11167
11250
|
result = emptyBlocksToBreaks(result);
|
|
11168
11251
|
result = unescapePipes(result);
|
|
11169
11252
|
result = unescapeNotionChars(result);
|
|
11253
|
+
result = unescapeAngleBrackets(result);
|
|
11170
11254
|
result = unescapeBrackets(result);
|
|
11171
11255
|
result = receiveWikilinkText(result);
|
|
11172
11256
|
result = receiveLiteralBackslashes(result);
|
|
@@ -15579,6 +15663,8 @@ var DatabaseSyncer = class {
|
|
|
15579
15663
|
if (!wholeDbInScope && !scope.some((path) => matchesPathScope(path, dbConfig.localFolder))) {
|
|
15580
15664
|
return { kind: "out-of-scope" };
|
|
15581
15665
|
}
|
|
15666
|
+
if (this.vaultFs.excludesPath?.(dbConfig.localFolder, "folder"))
|
|
15667
|
+
return { kind: "out-of-scope" };
|
|
15582
15668
|
const meta = await this.notionClient.getDatabaseMeta(dbConfig.databaseId);
|
|
15583
15669
|
const pages = await this.notionClient.queryAllDatabasePages(
|
|
15584
15670
|
dbConfig.databaseId,
|
|
@@ -15615,6 +15701,7 @@ var DatabaseSyncer = class {
|
|
|
15615
15701
|
*/
|
|
15616
15702
|
async rowAction(dbConfig, page, record, wholeDbInScope, scope) {
|
|
15617
15703
|
if (!wholeDbInScope && !(record && inAnyPathScope(record.obsidianPath, scope))) return "skip";
|
|
15704
|
+
if (record && this.vaultFs.excludesPath?.(record.obsidianPath)) return "skip";
|
|
15618
15705
|
if (!record) return "pull";
|
|
15619
15706
|
const misplaced = !isDirectDbRowPath(dbConfig.localFolder, record.obsidianPath);
|
|
15620
15707
|
const changed = misplaced || this.remoteVerdict(record, page) !== "unchanged";
|
|
@@ -15678,7 +15765,7 @@ var DatabaseSyncer = class {
|
|
|
15678
15765
|
unlistedRows(dbConfig, pages, paths) {
|
|
15679
15766
|
const listed = new Set(pages.map((page) => normalizeNotionId(page.id)));
|
|
15680
15767
|
return this.stateDb.getAll().filter(
|
|
15681
|
-
(record) => record.fileType === "db-row" && record.notionPageId !== null && record.notionParentId !== null && notionIdsEqual(record.notionParentId, dbConfig.databaseId) && !listed.has(normalizeNotionId(record.notionPageId)) && inAnyPathScope(record.obsidianPath, paths)
|
|
15768
|
+
(record) => record.fileType === "db-row" && record.notionPageId !== null && record.notionParentId !== null && notionIdsEqual(record.notionParentId, dbConfig.databaseId) && !listed.has(normalizeNotionId(record.notionPageId)) && inAnyPathScope(record.obsidianPath, paths) && !(this.vaultFs.excludesPath?.(record.obsidianPath) ?? false)
|
|
15682
15769
|
);
|
|
15683
15770
|
}
|
|
15684
15771
|
/**
|
|
@@ -16631,6 +16718,10 @@ var ScopedVaultFS = class {
|
|
|
16631
16718
|
const joined = normalizeVaultPath(this.folder === "" ? path : `${this.folder}/${path}`);
|
|
16632
16719
|
return joined !== null && matchesPathScope(joined, this.folder) && connectionFolderOf(joined, this.nested) === null;
|
|
16633
16720
|
}
|
|
16721
|
+
excludesPath(path, kind) {
|
|
16722
|
+
const joined = normalizeVaultPath(this.folder === "" ? path : `${this.folder}/${path}`);
|
|
16723
|
+
return joined !== null && (this.base.excludesPath?.(joined, kind) ?? false);
|
|
16724
|
+
}
|
|
16634
16725
|
/** 이 연결의 경로 → 볼트 경로. */
|
|
16635
16726
|
toBase(path, write) {
|
|
16636
16727
|
const joined = normalizeVaultPath(this.folder === "" ? path : `${this.folder}/${path}`);
|
|
@@ -22348,7 +22439,7 @@ var RemoteDetector = class _RemoteDetector {
|
|
|
22348
22439
|
// src/sync/rerender-targets.ts
|
|
22349
22440
|
function rerenderTargets(stateDb, scope) {
|
|
22350
22441
|
return stateDb.getAll().filter(
|
|
22351
|
-
(record) => isRerenderable(record) && record.notionPageId !== null && !scope.queued.has(record.notionPageId) && !scope.adopting.has(record.id) && !(scope.localChangedPaths?.has(record.obsidianPath) ?? false) && inAnyPathScope(record.obsidianPath, scope.paths)
|
|
22442
|
+
(record) => isRerenderable(record) && record.notionPageId !== null && !scope.queued.has(record.notionPageId) && !scope.adopting.has(record.id) && !(scope.localChangedPaths?.has(record.obsidianPath) ?? false) && inAnyPathScope(record.obsidianPath, scope.paths) && !(scope.excludes?.(record.obsidianPath) ?? false)
|
|
22352
22443
|
).map((record) => ({
|
|
22353
22444
|
pageId: record.notionPageId,
|
|
22354
22445
|
type: "modified",
|
|
@@ -22376,7 +22467,8 @@ async function detectMissingLocalFiles(config, stateDb, vaultFs, paths) {
|
|
|
22376
22467
|
// 존재 확인을 넣어 이 제외가 비로소 근거를 갖는다(tests/sync/db-row-restore-deleted).
|
|
22377
22468
|
// push 가 만든 폴더 페이지(폴더 레코드)도 실체가 폴더다 — 안의 노트를 되살리면 폴더도
|
|
22378
22469
|
// 생긴다. 예전에는 폴더 경로에 확장자 없는 파일을 썼다(S-17).
|
|
22379
|
-
(r.fileType === "file" || r.fileType === "folder-note") && !isFolderRecord(r) && r.notionPageId !== null && inAnyPathScope(r.obsidianPath, paths)
|
|
22470
|
+
(r.fileType === "file" || r.fileType === "folder-note") && !isFolderRecord(r) && r.notionPageId !== null && inAnyPathScope(r.obsidianPath, paths) && // 설정이 뺀 노트는 목록에 없을 뿐 지운 것이 아니다 — 되살리지 않는다(이슈 #8).
|
|
22471
|
+
!(vaultFs.excludesPath?.(r.obsidianPath) ?? false)
|
|
22380
22472
|
)
|
|
22381
22473
|
);
|
|
22382
22474
|
if (scoped.length === 0) return [];
|
|
@@ -22705,7 +22797,10 @@ var SyncOrchestrator = class {
|
|
|
22705
22797
|
this.observation = new RunObservation(stateDb, notionClient);
|
|
22706
22798
|
this.recovery = new InterruptedSyncRecovery(stateDb, notionClient);
|
|
22707
22799
|
this.detector = new RemoteDetector(config, stateDb, notionClient, this.observation);
|
|
22708
|
-
const changeDetector = new ChangeDetector(
|
|
22800
|
+
const changeDetector = new ChangeDetector(
|
|
22801
|
+
stateDb,
|
|
22802
|
+
(path) => vaultFs.excludesPath?.(path) ?? false
|
|
22803
|
+
);
|
|
22709
22804
|
const pipeline = createDefaultPipeline({
|
|
22710
22805
|
wikilinkResolver: (text) => stateDb.resolveWikilink(text)
|
|
22711
22806
|
});
|
|
@@ -23315,10 +23410,12 @@ var SyncOrchestrator = class {
|
|
|
23315
23410
|
}
|
|
23316
23411
|
};
|
|
23317
23412
|
};
|
|
23318
|
-
const
|
|
23413
|
+
const excludes = (path) => this.vaultFs.excludesPath?.(path) ?? false;
|
|
23414
|
+
const filtered = remoteChanges.filter((c) => {
|
|
23319
23415
|
const record = this.stateDb.getByNotionId(c.pageId);
|
|
23320
|
-
|
|
23321
|
-
|
|
23416
|
+
if (record && excludes(record.obsidianPath)) return false;
|
|
23417
|
+
return !options?.paths || !!record && inAnyPathScope(record.obsidianPath, options.paths);
|
|
23418
|
+
});
|
|
23322
23419
|
const queuedIds = new Set(filtered.map((c) => c.pageId));
|
|
23323
23420
|
const missingLocal = localPlan ? await detectMissingLocalFiles(this.config, this.stateDb, this.vaultFs, options?.paths) : [];
|
|
23324
23421
|
const restoreChanges = missingLocal.filter(
|
|
@@ -23336,7 +23433,8 @@ var SyncOrchestrator = class {
|
|
|
23336
23433
|
localChangedPaths: localPlan ? new Set(localPlan.scan.changes.map((change) => change.path)) : null,
|
|
23337
23434
|
queued: /* @__PURE__ */ new Set([...queuedIds, ...restoreIds]),
|
|
23338
23435
|
adopting,
|
|
23339
|
-
paths: options.paths
|
|
23436
|
+
paths: options.paths,
|
|
23437
|
+
excludes
|
|
23340
23438
|
}) : [];
|
|
23341
23439
|
const workItems = [...filtered, ...restoreChanges, ...rerenderChanges];
|
|
23342
23440
|
if (options?.dryRun) {
|
|
@@ -23766,17 +23864,104 @@ function pushOperationOf(change) {
|
|
|
23766
23864
|
return "update";
|
|
23767
23865
|
}
|
|
23768
23866
|
}
|
|
23867
|
+
|
|
23868
|
+
// src/utils/path-filter.ts
|
|
23869
|
+
var PathFilter = class {
|
|
23870
|
+
include;
|
|
23871
|
+
exclude;
|
|
23872
|
+
constructor(include, exclude) {
|
|
23873
|
+
this.include = compileAll(include);
|
|
23874
|
+
this.exclude = compileAll(exclude);
|
|
23875
|
+
}
|
|
23876
|
+
/** 동기화하지 않는 노트 · 첨부 — 포함 패턴 어느 것에도 맞지 않거나 제외 패턴에 맞는다. */
|
|
23877
|
+
excludesFile(path) {
|
|
23878
|
+
if (this.include.length > 0 && !this.include.some((p) => matchesWithin(path, p))) return true;
|
|
23879
|
+
return this.excludesFolder(path);
|
|
23880
|
+
}
|
|
23881
|
+
/**
|
|
23882
|
+
* 걷지 않을 폴더 — 제외 패턴만 본다. 포함 패턴은 안의 파일이 정한다: `00 Me/**\/*.md` 는 폴더
|
|
23883
|
+
* `00 Me` 에 맞지 않지만 그 안의 노트에 맞는다.
|
|
23884
|
+
*/
|
|
23885
|
+
excludesFolder(path) {
|
|
23886
|
+
return this.exclude.some((p) => matchesWithin(path, p));
|
|
23887
|
+
}
|
|
23888
|
+
};
|
|
23889
|
+
function toVaultPath(relativePath, separator) {
|
|
23890
|
+
return separator === "/" ? relativePath : relativePath.split(separator).join("/");
|
|
23891
|
+
}
|
|
23892
|
+
function compileAll(patterns) {
|
|
23893
|
+
return patterns.flatMap((raw) => {
|
|
23894
|
+
const pattern = normalizePattern(raw);
|
|
23895
|
+
return pattern === null ? [] : [compile(pattern)];
|
|
23896
|
+
});
|
|
23897
|
+
}
|
|
23898
|
+
function normalizePattern(raw) {
|
|
23899
|
+
const pattern = raw.trim().replace(/\\/g, "/").replace(/^(?:\.\/|\/)+/, "").replace(/\/+$/, "");
|
|
23900
|
+
return pattern === "" ? null : pattern;
|
|
23901
|
+
}
|
|
23902
|
+
function compile(pattern) {
|
|
23903
|
+
if (pattern === "**" || pattern === "**/*") return { kind: "all" };
|
|
23904
|
+
if (!/[*?]/.test(pattern)) return { kind: "prefix", prefix: pattern };
|
|
23905
|
+
if (pattern.endsWith("/**") && !/[*?]/.test(pattern.slice(0, -3))) {
|
|
23906
|
+
return { kind: "prefix", prefix: pattern.slice(0, -3) };
|
|
23907
|
+
}
|
|
23908
|
+
const re = globToRegExp(pattern);
|
|
23909
|
+
return pattern.includes("/") ? { kind: "anchored", re } : { kind: "segment", re };
|
|
23910
|
+
}
|
|
23911
|
+
function globToRegExp(pattern) {
|
|
23912
|
+
let source = "";
|
|
23913
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
23914
|
+
const char = pattern[i];
|
|
23915
|
+
if (char === "*" && pattern[i + 1] === "*") {
|
|
23916
|
+
const segmentStart = i === 0 || pattern[i - 1] === "/";
|
|
23917
|
+
if (segmentStart && pattern[i + 2] === "/") {
|
|
23918
|
+
source += "(?:.*/)?";
|
|
23919
|
+
i += 2;
|
|
23920
|
+
} else {
|
|
23921
|
+
source += ".*";
|
|
23922
|
+
i += 1;
|
|
23923
|
+
}
|
|
23924
|
+
} else if (char === "*") {
|
|
23925
|
+
source += "[^/]*";
|
|
23926
|
+
} else if (char === "?") {
|
|
23927
|
+
source += "[^/]";
|
|
23928
|
+
} else {
|
|
23929
|
+
source += char.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
23930
|
+
}
|
|
23931
|
+
}
|
|
23932
|
+
return new RegExp(`^${source}$`);
|
|
23933
|
+
}
|
|
23934
|
+
function matchesWithin(path, pattern) {
|
|
23935
|
+
switch (pattern.kind) {
|
|
23936
|
+
case "all":
|
|
23937
|
+
return true;
|
|
23938
|
+
case "prefix":
|
|
23939
|
+
return path === pattern.prefix || path.startsWith(`${pattern.prefix}/`);
|
|
23940
|
+
case "segment":
|
|
23941
|
+
return path.split("/").some((segment) => pattern.re.test(segment));
|
|
23942
|
+
case "anchored": {
|
|
23943
|
+
const segments = path.split("/");
|
|
23944
|
+
for (let depth2 = 1; depth2 <= segments.length; depth2++) {
|
|
23945
|
+
if (pattern.re.test(segments.slice(0, depth2).join("/"))) return true;
|
|
23946
|
+
}
|
|
23947
|
+
return false;
|
|
23948
|
+
}
|
|
23949
|
+
}
|
|
23950
|
+
}
|
|
23951
|
+
|
|
23952
|
+
// src/sync/node-vault-fs.ts
|
|
23769
23953
|
var NodeVaultFS = class {
|
|
23770
23954
|
constructor(rootPath, pathConfig) {
|
|
23771
23955
|
this.rootPath = rootPath;
|
|
23772
|
-
this.includePatterns = pathConfig?.include ?? [];
|
|
23773
23956
|
const configExclude = pathConfig?.exclude ?? [];
|
|
23774
23957
|
const ignorePatterns = this.loadImNobsidianIgnore();
|
|
23775
|
-
this.
|
|
23958
|
+
this.filter = new PathFilter(pathConfig?.include ?? [], [...configExclude, ...ignorePatterns]);
|
|
23776
23959
|
}
|
|
23777
23960
|
rootPath;
|
|
23778
|
-
|
|
23779
|
-
|
|
23961
|
+
filter;
|
|
23962
|
+
excludesPath(path, kind = "file") {
|
|
23963
|
+
return kind === "folder" ? this.filter.excludesFolder(path) : this.filter.excludesFile(path);
|
|
23964
|
+
}
|
|
23780
23965
|
loadImNobsidianIgnore() {
|
|
23781
23966
|
try {
|
|
23782
23967
|
const ignorePath = join(this.rootPath, IGNORE_FILE);
|
|
@@ -23850,38 +24035,20 @@ var NodeVaultFS = class {
|
|
|
23850
24035
|
await this.walkDirNonMd(this.rootPath, files);
|
|
23851
24036
|
return files;
|
|
23852
24037
|
}
|
|
23853
|
-
|
|
23854
|
-
|
|
23855
|
-
|
|
23856
|
-
const dir = pattern.slice(0, -3);
|
|
23857
|
-
return relativePath.startsWith(dir + "/") || relativePath === dir;
|
|
23858
|
-
} else if (pattern.startsWith("*.")) {
|
|
23859
|
-
const ext = pattern.slice(1);
|
|
23860
|
-
return relativePath.endsWith(ext);
|
|
23861
|
-
} else if (pattern.includes("*")) {
|
|
23862
|
-
const regexStr = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\{\{GLOBSTAR\}\}/g, ".*");
|
|
23863
|
-
return new RegExp("^" + regexStr + "$").test(relativePath);
|
|
23864
|
-
} else {
|
|
23865
|
-
return relativePath === pattern || relativePath.startsWith(pattern + "/");
|
|
23866
|
-
}
|
|
23867
|
-
}
|
|
23868
|
-
isExcluded(relativePath) {
|
|
23869
|
-
if (this.includePatterns.length > 0) {
|
|
23870
|
-
const included = this.includePatterns.some((p) => this.matchesPattern(relativePath, p));
|
|
23871
|
-
if (!included) return true;
|
|
23872
|
-
}
|
|
23873
|
-
return this.excludePatterns.some((p) => this.matchesPattern(relativePath, p));
|
|
24038
|
+
/** 볼트 경로 — Windows 의 `relative()` 는 `\` 로 잇는다. 상태 DB · 패턴 · Notion 경로는 모두 `/` 다. */
|
|
24039
|
+
vaultPath(fullPath) {
|
|
24040
|
+
return toVaultPath(relative(this.rootPath, fullPath), sep);
|
|
23874
24041
|
}
|
|
23875
24042
|
async walkDirNonMd(dir, result) {
|
|
23876
24043
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
23877
24044
|
for (const entry of entries) {
|
|
23878
24045
|
const fullPath = join(dir, entry.name);
|
|
23879
24046
|
if (entry.name.startsWith(".")) continue;
|
|
23880
|
-
const relativePath =
|
|
23881
|
-
if (this.isExcluded(relativePath)) continue;
|
|
24047
|
+
const relativePath = this.vaultPath(fullPath);
|
|
23882
24048
|
if (entry.isDirectory()) {
|
|
24049
|
+
if (this.filter.excludesFolder(relativePath)) continue;
|
|
23883
24050
|
await this.walkDirNonMd(fullPath, result);
|
|
23884
|
-
} else if (entry.isFile() && !isMarkdownPath(entry.name)) {
|
|
24051
|
+
} else if (entry.isFile() && !isMarkdownPath(entry.name) && !this.filter.excludesFile(relativePath)) {
|
|
23885
24052
|
const fileStat = await stat(fullPath);
|
|
23886
24053
|
result.push({
|
|
23887
24054
|
path: relativePath,
|
|
@@ -23896,11 +24063,11 @@ var NodeVaultFS = class {
|
|
|
23896
24063
|
for (const entry of entries) {
|
|
23897
24064
|
const fullPath = join(dir, entry.name);
|
|
23898
24065
|
if (entry.name.startsWith(".")) continue;
|
|
23899
|
-
const relativePath =
|
|
23900
|
-
if (this.isExcluded(relativePath)) continue;
|
|
24066
|
+
const relativePath = this.vaultPath(fullPath);
|
|
23901
24067
|
if (entry.isDirectory()) {
|
|
24068
|
+
if (this.filter.excludesFolder(relativePath)) continue;
|
|
23902
24069
|
await this.walkDirStats(fullPath, result);
|
|
23903
|
-
} else if (entry.isFile() && isMarkdownPath(entry.name)) {
|
|
24070
|
+
} else if (entry.isFile() && isMarkdownPath(entry.name) && !this.filter.excludesFile(relativePath)) {
|
|
23904
24071
|
const fileStat = await stat(fullPath);
|
|
23905
24072
|
result.push({
|
|
23906
24073
|
path: relativePath,
|
|
@@ -23915,11 +24082,11 @@ var NodeVaultFS = class {
|
|
|
23915
24082
|
for (const entry of entries) {
|
|
23916
24083
|
const fullPath = join(dir, entry.name);
|
|
23917
24084
|
if (entry.name.startsWith(".")) continue;
|
|
23918
|
-
const relativePath =
|
|
23919
|
-
if (this.isExcluded(relativePath)) continue;
|
|
24085
|
+
const relativePath = this.vaultPath(fullPath);
|
|
23920
24086
|
if (entry.isDirectory()) {
|
|
24087
|
+
if (this.filter.excludesFolder(relativePath)) continue;
|
|
23921
24088
|
await this.walkDir(fullPath, result);
|
|
23922
|
-
} else if (entry.isFile() && isMarkdownPath(entry.name)) {
|
|
24089
|
+
} else if (entry.isFile() && isMarkdownPath(entry.name) && !this.filter.excludesFile(relativePath)) {
|
|
23923
24090
|
const content = await readFile(fullPath, "utf-8");
|
|
23924
24091
|
const fileStat = await stat(fullPath);
|
|
23925
24092
|
result.push({
|
|
@@ -24934,9 +25101,9 @@ function readColumnLayout(lines) {
|
|
|
24934
25101
|
close(i);
|
|
24935
25102
|
return;
|
|
24936
25103
|
}
|
|
24937
|
-
const
|
|
24938
|
-
if (
|
|
24939
|
-
const ratio =
|
|
25104
|
+
const sep3 = SEP_RE.exec(line);
|
|
25105
|
+
if (sep3) {
|
|
25106
|
+
const ratio = sep3[1] === void 0 ? void 0 : Number(sep3[1]);
|
|
24940
25107
|
open.columns.push({ line: i, ...Number.isFinite(ratio) ? { ratio } : {} });
|
|
24941
25108
|
}
|
|
24942
25109
|
});
|
|
@@ -24951,6 +25118,6 @@ function formatRatio(ratio) {
|
|
|
24951
25118
|
return String(Math.round(ratio * 100) / 100);
|
|
24952
25119
|
}
|
|
24953
25120
|
|
|
24954
|
-
export { BaseFileGenerator, BlankLineCollapser, CONFIG_FILE, CalloutRestorer, CalloutTransformer, ChangeDetector, ColorAnnotator, ConfigManager, ConfigSchema, ConflictResolver, ConnectionBoundaryError, ConnectionScopeError, ConversionPipeline, DB_VIEWS_FILE, DB_VIEWS_PATH, DEFAULT_CONFIG, DEFAULT_TITLE_PROPERTY, DEFAULT_WATCH_DEBOUNCE_MS, DIFF_SIGN, DatabaseSyncer, EmbedResolver, EntryEditor, FileHandler, FileWatcher, FrontmatterExtractor, FrontmatterGenerator, GITIGNORE_ENTRY, IGNORE_FILE, INTERNAL_DIR, INTERNAL_DIR_GLOB, ImageHandler, InlineAnnotationPreserver, InlineDBParser, LocalImageRestorer, MARKER_BRAND, MathNormalizer, MentionToWikilink, NOTION_COLOR_NAMES, NOTION_TOKEN_PREFIX, NOTION_TO_BASES_VIEW, NodeVaultFS, NotionBlockBuilder, NotionClient, OBSIDIAN_CONFIG_DIR, PendingWalError, PreserveMarkerCollector, PreserveMarkerInjector, PropertiesTableInjector, PropertiesTableRestorer, PropertyMapper, RENDER_LINE_RULES, SAVED_STATE_TABLES_QUERY, SEPARATOR_BODY, STATE_DB_FILE, STATE_DB_MIGRATIONS, STATE_DB_PATH, SavedStateDbError, ScopedVaultFS, SidecarGenerator, StateDB, StateDbLockedError, StateDbUnavailableError, StateLock, SyncBusyError, SyncOrchestrator, TreeMapper, UnsupportedBlockStripper, ViewDataProvider, WIKILINK_LOOKUPS, WatchSyncService, WikilinkResolver, applicableChoices, assertNoPendingWal, basesViewMappingOf, basesViewTypeOf, choiceForStrategy, classifyBodyFidelity, columnLabel, compactNotionId, computeBufferHash, computeHash, configDirFrom, connectionFolderOf, createDefaultPipeline, extractCalendarEntries, filterByMonth, filterEntries, findConnectionFolders, formatUnifiedDiff, generateColorCSS, generateId, getLogger, getNotionBgColor, getNotionColor, getVisibleProperties, groupEntries, hasColumnLayout, hasDesignMarker, inAnyPathScope, isInternalPath, isMarkdownPath, isRemoteDeletion, lineDiff, lintRenderedMarkdown, matchesPathScope, normalizeNotionId, normalizeVaultPath, noteNameFromPath, notionIdFromInput, notionIdsEqual, operationLabel, parseDesignColor, pendingWalBytes, readColumnLayout, readDesignLine, resolvePropertyName, sanitizeFileName, setLogger, sortEntries, stateDbLockPath, stateDbWalPath, stripMarkdownExtension, summarizeFidelity, systemTimeZone, threeWayMerge, translateNotionFilter, verifyDatabaseCompleteness, verifyPageCompleteness, wikilinkLookupsIn };
|
|
25121
|
+
export { BaseFileGenerator, BlankLineCollapser, CONFIG_FILE, CalloutRestorer, CalloutTransformer, ChangeDetector, ColorAnnotator, ConfigManager, ConfigSchema, ConflictResolver, ConnectionBoundaryError, ConnectionScopeError, ConversionPipeline, DB_VIEWS_FILE, DB_VIEWS_PATH, DEFAULT_CONFIG, DEFAULT_TITLE_PROPERTY, DEFAULT_WATCH_DEBOUNCE_MS, DIFF_SIGN, DatabaseSyncer, EmbedResolver, EntryEditor, FileHandler, FileWatcher, FrontmatterExtractor, FrontmatterGenerator, GITIGNORE_ENTRY, IGNORE_FILE, INTERNAL_DIR, INTERNAL_DIR_GLOB, ImageHandler, InlineAnnotationPreserver, InlineDBParser, LocalImageRestorer, MARKER_BRAND, MathNormalizer, MentionToWikilink, NOTION_COLOR_NAMES, NOTION_TOKEN_PREFIX, NOTION_TO_BASES_VIEW, NodeVaultFS, NotionBlockBuilder, NotionClient, OBSIDIAN_CONFIG_DIR, PathFilter, PendingWalError, PreserveMarkerCollector, PreserveMarkerInjector, PropertiesTableInjector, PropertiesTableRestorer, PropertyMapper, RENDER_LINE_RULES, SAVED_STATE_TABLES_QUERY, SEPARATOR_BODY, STATE_DB_FILE, STATE_DB_MIGRATIONS, STATE_DB_PATH, SavedStateDbError, ScopedVaultFS, SidecarGenerator, StateDB, StateDbLockedError, StateDbUnavailableError, StateLock, SyncBusyError, SyncOrchestrator, TreeMapper, UnsupportedBlockStripper, ViewDataProvider, WIKILINK_LOOKUPS, WatchSyncService, WikilinkResolver, applicableChoices, assertNoPendingWal, basesViewMappingOf, basesViewTypeOf, choiceForStrategy, classifyBodyFidelity, columnLabel, compactNotionId, computeBufferHash, computeHash, configDirFrom, connectionFolderOf, createDefaultPipeline, extractCalendarEntries, filterByMonth, filterEntries, findConnectionFolders, formatUnifiedDiff, generateColorCSS, generateId, getLogger, getNotionBgColor, getNotionColor, getVisibleProperties, groupEntries, hasColumnLayout, hasDesignMarker, inAnyPathScope, isInternalPath, isMarkdownPath, isRemoteDeletion, lineDiff, lintRenderedMarkdown, matchesPathScope, normalizeNotionId, normalizeVaultPath, noteNameFromPath, notionIdFromInput, notionIdsEqual, operationLabel, parseDesignColor, pendingWalBytes, readColumnLayout, readDesignLine, resolvePropertyName, sanitizeFileName, setLogger, sortEntries, stateDbLockPath, stateDbWalPath, stripMarkdownExtension, summarizeFidelity, systemTimeZone, threeWayMerge, toVaultPath, translateNotionFilter, verifyDatabaseCompleteness, verifyPageCompleteness, wikilinkLookupsIn };
|
|
24955
25122
|
//# sourceMappingURL=index.js.map
|
|
24956
25123
|
//# sourceMappingURL=index.js.map
|