@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 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
- constructor(stateDb: IStateDB);
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 includePatterns;
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
- private matchesPattern;
2569
- private isExcluded;
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, relative, sep } from 'path';
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 savedBytes = existsSync(dbPath) ? statSync(dbPath).size : null;
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
- constructor(stateDb) {
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(stateDb);
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 filtered = options?.paths ? remoteChanges.filter((c) => {
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
- return !!record && inAnyPathScope(record.obsidianPath, options.paths);
23321
- }) : remoteChanges;
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.excludePatterns = [...configExclude, ...ignorePatterns];
23958
+ this.filter = new PathFilter(pathConfig?.include ?? [], [...configExclude, ...ignorePatterns]);
23776
23959
  }
23777
23960
  rootPath;
23778
- includePatterns;
23779
- excludePatterns;
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
- matchesPattern(relativePath, pattern) {
23854
- if (pattern === "**/*" || pattern === "**") return true;
23855
- if (pattern.endsWith("/**")) {
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 = relative(this.rootPath, fullPath);
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 = relative(this.rootPath, fullPath);
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 = relative(this.rootPath, fullPath);
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 sep2 = SEP_RE.exec(line);
24938
- if (sep2) {
24939
- const ratio = sep2[1] === void 0 ? void 0 : Number(sep2[1]);
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